Skip to content

Commit

Permalink
Merge pull request #140 from seasonedcc/composable-functions
Browse files Browse the repository at this point in the history
Composable functions
  • Loading branch information
gustavoguichard authored May 24, 2024
2 parents c81b27b + 5054a67 commit 8026bfb
Show file tree
Hide file tree
Showing 95 changed files with 11,335 additions and 11,077 deletions.
878 changes: 878 additions & 0 deletions API.md

Large diffs are not rendered by default.

986 changes: 162 additions & 824 deletions README.md

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"version": "2.6.0",
"version": "1.0.0-beta-20240523-3",
"tasks": {
"test": "deno test --allow-env --allow-net src",
"publish": "deno task build-npm && cd npm/ && npm publish",
"build-npm": "deno run -A scripts/build-npm.ts",
"docs": "deno doc --html --name='domain-functions' ./mod.ts"
"docs": "deno doc --html --name='composable-functions' ./mod.ts",
"docs-lint": "deno doc --lint ./mod.ts"
},
"lint": {
"include": [
Expand All @@ -16,5 +17,17 @@
"ban-types"
]
}
},
"compilerOptions": {
"types": ["./src/test.d.ts"]
},
"fmt": {
"useTabs": false,
"lineWidth": 80,
"indentWidth": 2,
"semiColons": false,
"singleQuote": true,
"proseWrap": "preserve",
"include": ["src/"]
}
}
87 changes: 37 additions & 50 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 92 additions & 0 deletions environments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
## Environments

Sometimes you want to ensure the safety of certain values that are constant accross sequential compositions.
This parameter is called an environment. And to simplify the composition of this kind of Composable
we always define it with a single input. Therefore we have the type

```ts
Composable<(input: I, environment: E) => O>
```

A common use case would be a sequence of functions that depend on an authorization system.
The currently authenticated user would have to be propagated every time there is a sequential composition.
To avoid such awkwardness we use environments:

```tsx
import { environment } from 'composable-functions'
const dangerousFunction = composable(async (input: string, { user } : { user: { name: string, admin: boolean } }) => {
// do something that only the admin can do
})

const carryUser = environment.pipe(gatherInput, dangerousFunction)
```

## Composing with environments

These combinators are useful for composing functions with environment. Note that the standard parallel compositions will work just fine with the concept of environments.

### `pipe`

The environment.pipe function allows you to compose multiple functions in a sequence, forwarding the environment to each function in the chain.

```ts
import { environment } from 'composable-functions'

const a = composable((str: string, env: { user: User }) => str === '1')
const b = composable((bool: boolean, env: { user: User }) => bool && env.user.admin)

const pipeline = environment.pipe(a, b)

const result = await pipeline('1', { user: { admin: true } })
/*
result = {
success: true,
data: true,
errors: []
}
*/
```

### `sequence`
The environment.sequence function works similarly to pipe, but it returns a tuple containing the result of each function in the sequence.

```ts
import { environment } from 'composable-functions'

const a = composable((str: string, env: { user: User }) => str === '1')
const b = composable((bool: boolean, env: { user: User }) => bool && env.user.admin)

const sequence = environment.sequence(a, b)

const result = await sequence('1', { user: { admin: true } })
/*
result = {
success: true,
data: [true, true],
errors: []
}
*/
```

### `branch`

The environment.branch function adds conditional logic to your compositions, forwarding the environment to each branch as needed.

```ts
import { composable, environment } from 'composable-functions'

const adminIncrement = composable((a: number, { user }: { user: { admin: boolean } }) =>
user.admin ? a + 1 : a
)
const adminMakeItEven = (sum: number) => sum % 2 != 0 ? adminIncrement : null
const incrementUntilEven = environment.branch(adminIncrement, adminMakeItEven)

const result = await incrementUntilEven(1, { user: { admin: true } })
/*
result = {
success: true,
data: 2,
errors: []
}
*/
```
23 changes: 23 additions & 0 deletions examples/arktype/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
9 changes: 9 additions & 0 deletions examples/arktype/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Use composable-functions with a custom parser

This simple example can be a reference to adapt composable-functions to any other parser library.

There are two approaches to use composable-functions with a custom parser:
- Create an adapter function that will receive a schema and return a schema in the shape of a `ParserSchena`. Example: [the `adapt` function](./src/adapters.ts).
- Create your custom `withSchema` and `applySchema` that will validate your input and environment and return a `Result`. Example: [the `withArkSchema` and `applyArkSchema` functions](./src/adapters.ts).

Check out the [`./src`](./src/) directory to understand how we implemented both approaches with [`arktype`](https://github.com/arktypeio/arktype).
18 changes: 18 additions & 0 deletions examples/arktype/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "composable-functions-arktype-example",
"private": true,
"version": "0.0.0",
"type": "module",
"main": "src/usage.ts",
"scripts": {
"dev": "tsx src/usage.ts"
},
"devDependencies": {
"tsx": "^4.7.2"
},
"dependencies": {
"arktype": "2.0.0-dev.7",
"composable-functions": "file:../../npm",
"typescript": "^5.4.5"
}
}
Loading

0 comments on commit 8026bfb

Please sign in to comment.