Skip to content

Commit

Permalink
Merge pull request #112 from seasonedcc/improved-branch
Browse files Browse the repository at this point in the history
Improved `branch` method to act like conditional `pipe`
  • Loading branch information
gustavoguichard authored Sep 12, 2023
2 parents b147337 + 78a0cb8 commit 5e878e5
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 7 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ For the example above, the result type will be `Result<{ aString: string, aBoole

Use `branch` to add conditional logic to your domain functions' compositions.

It receives a domain function and a predicate function that should return the next domain function to be executed based on the previous domain function's output.
It receives a domain function and a predicate function that should return the next domain function to be executed based on the previous domain function's output, like `pipe`.

```ts
const getIdOrEmail = mdf(z.object({ id: z.number().optional, email: z.string().optional() }))((data) => {
Expand Down Expand Up @@ -684,6 +684,14 @@ For the example above, the result type will be `Result<User>`:
environmentErrors: [],
}
```
If you don't want to pipe when a certain condition is matched, you can return `null` like so:
```ts
const a = mdf()(() => 'a')
const b = mdf()(() => 'b')
const df = branch(a, (output) => output === 'a' ? null : b)
// ^? DomainFunction<'a' | 'b'>
```

If any function fails, execution halts and the error is returned.
The predicate function will return an `ErrorResult` type in case it throws:
```ts
Expand Down
21 changes: 21 additions & 0 deletions src/branch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ describe('branch', () => {
})
})

it('should not pipe if the predicate returns null', async () => {
const a = mdf(z.object({ id: z.number() }))(({ id }) => ({
id: id + 2,
next: 'multiply',
}))
const b = mdf(z.object({ id: z.number() }))(({ id }) => String(id))
const d = branch(a, (output) => (output.next === 'multiply' ? null : b))
type _R = Expect<
Equal<typeof d, DomainFunction<string | { id: number; next: string }>>
>

assertEquals(await d({ id: 1 }), {
success: true,
data: { id: 3, next: 'multiply' },
errors: [],
inputErrors: [],
environmentErrors: [],
})
})

it('should use the same environment in all composed functions', async () => {
const a = mdf(
z.undefined(),
Expand Down Expand Up @@ -122,6 +142,7 @@ describe('branch', () => {
const b = mdf(z.object({ id: z.number() }))(({ id }) => id - 1)
const c = branch(a, (_) => {
throw new Error('condition function failed')
// deno-lint-ignore no-unreachable
return b
})
type _R = Expect<Equal<typeof c, DomainFunction<number>>>
Expand Down
24 changes: 18 additions & 6 deletions src/domain-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ function map<O, R>(

/**
* Use it to add conditional logic to your domain functions' compositions.
* It receives a domain function and a predicate function that should return the next domain function to be executed based on the previous domain function's output.
* It receives a domain function and a predicate function that should return the next domain function to be executed based on the previous domain function's output, like `pipe`. If the predicate returns `null` the result of the previous domain function will be returned and it won't be piped.
* @example
* import { mdf, branch } from 'domain-functions'
*
Expand All @@ -238,20 +238,32 @@ function map<O, R>(
* (output) => (typeof output === "number" ? findUserById : findUserByEmail)
* )
* // ^? DomainFunction<User>
*
* const getStock = mdf(z.any(), z.object({ id: z.number() }))(_, ({ id }) => db.stocks.find({ id }))
* const getExtraStock = mdf(z.any(), z.object({ id: z.number() }))(_, ({ id }) => db.stockes.find({ id, extra: true }))
*
* const getStockOrExtraStock = branch(
* getStock,
* ({ items }) => (items.length >= 0 ? null : getExtraStock)
* )
* // ^? DomainFunction<{ items: Item[] }>
*/
function branch<T, Df extends DomainFunction>(
function branch<T, R extends DomainFunction | null>(
dfn: DomainFunction<T>,
resolver: (o: T) => Promise<Df> | Df,
): DomainFunction<UnpackData<Df>> {
return async (input, environment) => {
resolver: (o: T) => Promise<R> | R,
) {
return (async (input, environment) => {
const result = await dfn(input, environment)
if (!result.success) return result

return safeResult(async () => {
const nextDf = await resolver(result.data)
if (typeof nextDf !== 'function') return result.data
return fromSuccess(nextDf)(result.data, environment)
})
}
}) as DomainFunction<
R extends DomainFunction<infer U> ? U : UnpackData<NonNullable<R>> | T
>
}

/**
Expand Down

0 comments on commit 5e878e5

Please sign in to comment.