composable-functions
Advanced tools
Comparing version 4.1.0 to 4.2.0
101
API.md
@@ -181,3 +181,3 @@ # API Reference | ||
These combinators are useful for composing composables. They all return another `Composable`, thus allowing further application in more compositions. | ||
These combinators are useful for composing functions. They operate on either plain functions or composables. They all return a `Composable`, thus allowing further application in more compositions. | ||
@@ -191,5 +191,5 @@ ## all | ||
```ts | ||
const a = composable(({ id }: { id: number }) => String(id)) | ||
const b = composable(({ id }: { id: number }) => id + 1) | ||
const c = composable(({ id }: { id: number }) => Boolean(id)) | ||
const a = ({ id }: { id: number }) => String(id) | ||
const b = ({ id }: { id: number }) => id + 1 | ||
const c = ({ id }: { id: number }) => Boolean(id) | ||
@@ -216,5 +216,5 @@ const result = await all(a, b, c)({ id: 1 }) | ||
}) | ||
const b = composable(() => { | ||
const b = () => { | ||
throw new Error('Error') | ||
}) | ||
} | ||
@@ -239,11 +239,5 @@ const result = await all(a, b)({ id: '1' }) | ||
```ts | ||
const getIdOrEmail = composable((data: { id?: number, email?: string }) => { | ||
return data.id ?? data.email | ||
}) | ||
const findUserById = composable((id: number) => { | ||
return db.users.find({ id }) | ||
}) | ||
const findUserByEmail = composable((email: string) => { | ||
return db.users.find({ email }) | ||
}) | ||
const getIdOrEmail = (data: { id?: number, email?: string }) => data.id ?? data.email | ||
const findUserById = (id: number) => db.users.find({ id }) | ||
const findUserByEmail = (email: string) => db.users.find({ email }) | ||
const findUserByIdOrEmail = branch( | ||
@@ -266,4 +260,4 @@ getIdOrEmail, | ||
```ts | ||
const a = composable(() => 'a') | ||
const b = composable(() => 'b') | ||
const a = () => 'a' | ||
const b = () => 'b' | ||
const fn = branch(a, (data) => data === 'a' ? null : b) | ||
@@ -295,3 +289,3 @@ // ^? Composable<() => 'a' | 'b'> | ||
const getUser = composable((id: string) => fetchUser(id)) | ||
const getUser = (id: string) => fetchUser(id) | ||
// ^? Composable<(id: string) => User> | ||
@@ -312,5 +306,5 @@ const getOptionalUser = catchFailure(getUser, (errors, id) => { | ||
```ts | ||
const a = composable(() => '1') | ||
const b = composable(() => 2) | ||
const c = composable(() => true) | ||
const a = () => '1' | ||
const b = () => 2 | ||
const c = () => true | ||
@@ -340,3 +334,3 @@ const results = await collect({ a, b, c })({}) | ||
```ts | ||
const add = composable((a: number, b: number) => a + b) | ||
const add = (a: number, b: number) => a + b | ||
const addAndMultiplyBy2 = map(add, sum => sum * 2) | ||
@@ -348,8 +342,6 @@ ``` | ||
```ts | ||
const fetchAsText = composable( | ||
({ userId }: { userId: number }) => | ||
fetch(`https://reqres.in/api/users/${String(userId)}`).then((r) => | ||
r.json(), | ||
), | ||
) | ||
const fetchAsText = ({ userId }: { userId: number }) => { | ||
return fetch(`https://reqres.in/api/users/${String(userId)}`) | ||
.then((r) => r.json()) | ||
} | ||
const fullName = withSchema( | ||
@@ -381,3 +373,3 @@ z.object({ first_name: z.string(), last_name: z.string() }), | ||
```ts | ||
const add = composable((a: number, b: number) => a + b) | ||
const add = (a: number, b: number) => a + b | ||
const aggregateInputAndOutput = map(add, (result, a, b) => ({ result, a, b })) | ||
@@ -396,3 +388,3 @@ // ^? Composable<(a: number, b: number) => { result: number, a: number, b: number }> | ||
```ts | ||
const increment = composable((n: number) => { | ||
const increment = (n: number) => { | ||
if (Number.isNaN(n)) { | ||
@@ -402,3 +394,3 @@ throw new Error('Invalid input') | ||
return n + 1 | ||
}) | ||
} | ||
const summarizeErrors = (errors: Error[]) => | ||
@@ -425,8 +417,7 @@ [new Error('Number of errors: ' + errors.length)] | ||
```ts | ||
const getUser = composable(({ id }: { id: number }) => db.users.find({ id })) | ||
// ^? Composable<(input: { id: number }) => User> | ||
const getUser = ({ id }: { id: number }) => db.users.find({ id }) | ||
const getCurrentUser = mapParameters( | ||
getUser, | ||
(_input, user: { id: number }) => [{ id: user.id }] | ||
(_input: unknown, user: { id: number }) => [{ id: user.id }] | ||
) | ||
@@ -443,5 +434,5 @@ // ^? Composable<(input: unknown, ctx: { id: number }) => User> | ||
```ts | ||
const a = composable((aNumber: number) => String(aNumber)) | ||
const b = composable((aString: string) => aString == '1') | ||
const c = composable((aBoolean: boolean) => !aBoolean) | ||
const a = (aNumber: number) => String(aNumber) | ||
const b = (aString: string) => aString == '1' | ||
const c = (aBoolean: boolean) => !aBoolean | ||
@@ -472,5 +463,5 @@ const d = pipe(a, b, c) | ||
```ts | ||
const a = composable((aNumber: number) => String(aNumber)) | ||
const b = composable((aString: string) => aString == '1') | ||
const c = composable((aBoolean: boolean) => !aBoolean) | ||
const a = (aNumber: number) => String(aNumber) | ||
const b = (aString: string) => aString == '1' | ||
const c = (aBoolean: boolean) => !aBoolean | ||
@@ -496,4 +487,4 @@ const d = sequence(a, b, c) | ||
```ts | ||
const a = composable((aNumber: number) => String(aNumber)) | ||
const b = composable((aString: string) => aString === '1') | ||
const a = (aNumber: number) => String(aNumber) | ||
const b = (aString: string) => aString === '1' | ||
@@ -539,3 +530,3 @@ const c = map(sequence(a, b), ([a, b]) => ({ aString: a, aBoolean: b })) | ||
These functions are better suited for use with `withSchema` rather than `composable` since they deal with external data and `withSchema` will ensure type-safety in runtime. | ||
These functions are better suited for composables with runtime validation, such as those built with `withSchema` (or `applySchema`) since they deal with external data and `withSchema` will ensure type-safety in runtime. | ||
@@ -812,6 +803,6 @@ For more details on how to structure your data, refer to this [test file](./src/tests/input-resolvers.test.ts). | ||
const getIdOrEmail = composable((data: { id?: number, email?: string }) => { | ||
const getIdOrEmail = (data: { id?: number, email?: string }) => { | ||
return data.id ?? data.email | ||
}) | ||
const findUserById = composable((id: number, ctx: { user: User }) => { | ||
} | ||
const findUserById = (id: number, ctx: { user: User }) => { | ||
if (!ctx.user.admin) { | ||
@@ -821,4 +812,4 @@ throw new Error('Unauthorized') | ||
return db.users.find({ id }) | ||
}) | ||
const findUserByEmail = composable((email: string, ctx: { user: User }) => { | ||
} | ||
const findUserByEmail = (email: string, ctx: { user: User }) => { | ||
if (!ctx.user.admin) { | ||
@@ -828,3 +819,3 @@ throw new Error('Unauthorized') | ||
return db.users.find | ||
}) | ||
} | ||
const findUserByIdOrEmail = context.branch( | ||
@@ -842,5 +833,5 @@ getIdOrEmail, | ||
const a = composable((aNumber: number, ctx: { user: User }) => String(aNumber)) | ||
const b = composable((aString: string, ctx: { user: User }) => aString == '1') | ||
const c = composable((aBoolean: boolean, ctx: { user: User }) => aBoolean && ctx.user.admin) | ||
const a = (aNumber: number, ctx: { user: User }) => String(aNumber) | ||
const b = (aString: string, ctx: { user: User }) => aString == '1' | ||
const c = (aBoolean: boolean, ctx: { user: User }) => aBoolean && ctx.user.admin | ||
@@ -858,5 +849,5 @@ const d = context.pipe(a, b, c) | ||
const a = composable((aNumber: number, ctx: { user: User }) => String(aNumber)) | ||
const b = composable((aString: string, ctx: { user: User }) => aString === '1') | ||
const c = composable((aBoolean: boolean, ctx: { user: User }) => aBoolean && ctx.user.admin) | ||
const a = (aNumber: number, ctx: { user: User }) => String(aNumber) | ||
const b = (aString: string, ctx: { user: User }) => aString === '1' | ||
const c = (aBoolean: boolean, ctx: { user: User }) => aBoolean && ctx.user.admin | ||
@@ -863,0 +854,0 @@ const d = context.sequence(a, b, c) |
@@ -17,5 +17,5 @@ ## Context | ||
import { context } from 'composable-functions' | ||
const dangerousFunction = composable(async (input: string, { user } : { user: { name: string, admin: boolean } }) => { | ||
const dangerousFunction = async (input: string, { user } : { user: { name: string, admin: boolean } }) => { | ||
// do something that only the admin can do | ||
}) | ||
} | ||
@@ -36,4 +36,4 @@ const carryUser = context.pipe(gatherInput, dangerousFunction) | ||
const a = composable((str: string, ctx: { user: User }) => str === '1') | ||
const b = composable((bool: boolean, ctx: { user: User }) => bool && ctx.user.admin) | ||
const a = (str: string, ctx: { user: User }) => str === '1' | ||
const b = (bool: boolean, ctx: { user: User }) => bool && ctx.user.admin | ||
@@ -58,4 +58,4 @@ const pipeline = context.pipe(a, b) | ||
const a = composable((str: string, ctx: { user: User }) => str === '1') | ||
const b = composable((bool: boolean, ctx: { user: User }) => bool && ctx.user.admin) | ||
const a = (str: string, ctx: { user: User }) => str === '1' | ||
const b = (bool: boolean, ctx: { user: User }) => bool && ctx.user.admin | ||
@@ -81,5 +81,4 @@ const sequence = context.sequence(a, b) | ||
const adminIncrement = composable((a: number, { user }: { user: { admin: boolean } }) => | ||
const adminIncrement = (a: number, { user }: { user: { admin: boolean } }) => | ||
user.admin ? a + 1 : a | ||
) | ||
const adminMakeItEven = (sum: number) => sum % 2 != 0 ? adminIncrement : null | ||
@@ -86,0 +85,0 @@ const incrementUntilEven = context.branch(adminIncrement, adminMakeItEven) |
@@ -34,10 +34,6 @@ import { composable, failure, fromSuccess, success } from './constructors.js'; | ||
* ```ts | ||
* import { composable, pipe } from 'composable-functions' | ||
* import { pipe } from 'composable-functions' | ||
* | ||
* const a = composable( | ||
* ({ aNumber }: { aNumber: number }) => ({ aString: String(aNumber) }), | ||
* ) | ||
* const b = composable( | ||
* ({ aString }: { aString: string }) => ({ aBoolean: aString == '1' }), | ||
* ) | ||
* const a = ({ aNumber }: { aNumber: number }) => ({ aString: String(aNumber) }) | ||
* const b = ({ aString }: { aString: string }) => ({ aBoolean: aString == '1' }) | ||
* const d = pipe(a, b) | ||
@@ -62,7 +58,7 @@ * // ^? Composable<({ aNumber }: { aNumber: number }) => { aBoolean: boolean }> | ||
* ```ts | ||
* import { composable, all } from 'composable-functions' | ||
* import { all } from 'composable-functions' | ||
* | ||
* const a = composable((id: number) => id + 1) | ||
* const b = composable(String) | ||
* const c = composable(Boolean) | ||
* const a = (id: number) => id + 1 | ||
* const b = (x: unknown) => String(x) | ||
* const c = (x: unknown) => Boolean(x) | ||
* const cf = all(a, b, c) | ||
@@ -73,4 +69,4 @@ * // ^? Composable<(id: number) => [string, number, boolean]> | ||
function all(...fns) { | ||
return (async (...args) => { | ||
const results = await Promise.all(fns.map((fn) => fn(...args))); | ||
const callable = (async (...args) => { | ||
const results = await Promise.all(fns.map((fn) => composable(fn)(...args))); | ||
if (results.some(({ success }) => success === false)) { | ||
@@ -81,2 +77,4 @@ return failure(results.map(({ errors }) => errors).flat()); | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -89,6 +87,6 @@ /** | ||
* ```ts | ||
* import { composable, collect } from 'composable-functions' | ||
* import { collect } from 'composable-functions' | ||
* | ||
* const a = composable(() => '1') | ||
* const b = composable(() => 2) | ||
* const a = () => '1' | ||
* const b = () => 2 | ||
* const aComposable = collect({ a, b }) | ||
@@ -100,3 +98,4 @@ * // ^? Composable<() => { a: string, b: number }> | ||
const fnsWithKey = Object.entries(fns).map(([key, cf]) => map(cf, (result) => ({ [key]: result }))); | ||
return map(all(...fnsWithKey), mergeObjects); | ||
const allFns = all(...fnsWithKey); | ||
return map(allFns, mergeObjects); | ||
} | ||
@@ -109,6 +108,6 @@ /** | ||
* ```ts | ||
* import { composable, sequence } from 'composable-functions' | ||
* import { sequence } from 'composable-functions' | ||
* | ||
* const a = composable((aNumber: number) => String(aNumber)) | ||
* const b = composable((aString: string) => aString === '1') | ||
* const a = (aNumber: number) => String(aNumber) | ||
* const b = (aString: string) => aString === '1' | ||
* const cf = sequence(a, b) | ||
@@ -119,5 +118,5 @@ * // ^? Composable<(aNumber: number) => [string, boolean]> | ||
function sequence(...fns) { | ||
return (async (...args) => { | ||
const callable = (async (...args) => { | ||
const [head, ...tail] = fns; | ||
const res = await head(...args); | ||
const res = await composable(head)(...args); | ||
if (!res.success) | ||
@@ -127,3 +126,3 @@ return failure(res.errors); | ||
for await (const fn of tail) { | ||
const res = await fn(result.at(-1)); | ||
const res = await composable(fn)(result.at(-1)); | ||
if (!res.success) | ||
@@ -135,2 +134,4 @@ return failure(res.errors); | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -144,5 +145,5 @@ /** | ||
* ```ts | ||
* import { composable, map } from 'composable-functions' | ||
* import { map } from 'composable-functions' | ||
* | ||
* const increment = composable((n: number) => n + 1) | ||
* const increment = (n: number) => n + 1 | ||
* const incrementToString = map(increment, String) | ||
@@ -155,8 +156,10 @@ * // ^? Composable<(n: number) => string> | ||
function map(fn, mapper) { | ||
return async (...args) => { | ||
const result = await fn(...args); | ||
const callable = (async (...args) => { | ||
const result = await composable(fn)(...args); | ||
if (!result.success) | ||
return failure(result.errors); | ||
return composable(mapper)(result.data, ...args); | ||
}; | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -170,5 +173,5 @@ /** | ||
* ```ts | ||
* import { composable, mapParameters } from 'composable-functions' | ||
* import { mapParameters } from 'composable-functions' | ||
* | ||
* const incrementId = composable(({ id }: { id: number }) => id + 1) | ||
* const incrementId = ({ id }: { id: number }) => id + 1 | ||
* const increment = mapParameters(incrementId, (id: number) => [{ id }]) | ||
@@ -179,8 +182,10 @@ * // ^? Composable<(id: number) => number> | ||
function mapParameters(fn, mapper) { | ||
return async (...args) => { | ||
const callable = (async (...args) => { | ||
const output = await composable(mapper)(...args); | ||
if (!output.success) | ||
return failure(output.errors); | ||
return fn(...output.data); | ||
}; | ||
return composable(fn)(...output.data); | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -193,5 +198,5 @@ /** | ||
* ```ts | ||
* import { composable, catchFailure } from 'composable-functions' | ||
* import { catchFailure } from 'composable-functions' | ||
* | ||
* const increment = composable(({ id }: { id: number }) => id + 1) | ||
* const increment = ({ id }: { id: number }) => id + 1 | ||
* const negativeOnError = catchFailure(increment, (result, originalInput) => ( | ||
@@ -203,8 +208,10 @@ * originalInput.id * -1 | ||
function catchFailure(fn, catcher) { | ||
return async (...args) => { | ||
const res = await fn(...args); | ||
const callable = (async (...args) => { | ||
const res = await composable(fn)(...args); | ||
if (res.success) | ||
return success(res.data); | ||
return composable(catcher)(res.errors, ...args); | ||
}; | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -217,5 +224,5 @@ /** | ||
* ```ts | ||
* import { composable, mapErrors } from 'composable-functions' | ||
* import { mapErrors } from 'composable-functions' | ||
* | ||
* const increment = composable(({ id }: { id: number }) => id + 1) | ||
* const increment = ({ id }: { id: number }) => id + 1 | ||
* const incrementWithErrorSummary = mapErrors(increment, (result) => ({ | ||
@@ -227,4 +234,4 @@ * errors: [{ message: 'Errors count: ' + result.errors.length }], | ||
function mapErrors(fn, mapper) { | ||
return async (...args) => { | ||
const res = await fn(...args); | ||
const callable = (async (...args) => { | ||
const res = await composable(fn)(...args); | ||
if (res.success) | ||
@@ -239,3 +246,5 @@ return success(res.data); | ||
} | ||
}; | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -250,3 +259,3 @@ /** | ||
* ```ts | ||
* import { composable, trace } from 'composable-functions' | ||
* import { trace } from 'composable-functions' | ||
* | ||
@@ -256,3 +265,3 @@ * const trackErrors = trace((result, ...args) => { | ||
* }) | ||
* const increment = composable((id: number) => id + 1) | ||
* const increment = (id: number) => id + 1 | ||
* const incrementAndTrackErrors = trackErrors(increment) | ||
@@ -263,9 +272,13 @@ * // ^? Composable<(id: number) => number> | ||
function trace(traceFn) { | ||
return (fn) => async (...args) => { | ||
const originalResult = await fn(...args); | ||
const traceResult = await composable(traceFn)(originalResult, ...args); | ||
if (traceResult.success) | ||
return originalResult; | ||
return failure(traceResult.errors); | ||
}; | ||
return ((fn) => { | ||
const callable = async (...args) => { | ||
const originalResult = await composable(fn)(...args); | ||
const traceResult = await composable(traceFn)(originalResult, ...args); | ||
if (traceResult.success) | ||
return originalResult; | ||
return failure(traceResult.errors); | ||
}; | ||
callable.kind = 'composable'; | ||
return callable; | ||
}); | ||
} | ||
@@ -294,4 +307,4 @@ /** | ||
function branch(cf, resolver) { | ||
return (async (...args) => { | ||
const result = await cf(...args); | ||
const callable = (async (...args) => { | ||
const result = await composable(cf)(...args); | ||
if (!result.success) | ||
@@ -303,6 +316,8 @@ return result; | ||
return result.data; | ||
return fromSuccess(nextComposable)(result.data); | ||
return fromSuccess(composable(nextComposable))(result.data); | ||
})(); | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
export { all, branch, catchFailure, collect, map, mapErrors, mapParameters, mergeObjects, pipe, sequence, trace, }; |
@@ -1,2 +0,1 @@ | ||
import { mapErrors } from './combinators.js'; | ||
import { ContextError, ErrorList, InputError } from './errors.js'; | ||
@@ -31,3 +30,6 @@ /** | ||
function composable(fn) { | ||
return async (...args) => { | ||
if ('kind' in fn && fn.kind === 'composable') { | ||
return fn; | ||
} | ||
const callable = async (...args) => { | ||
try { | ||
@@ -45,2 +47,4 @@ // deno-lint-ignore no-explicit-any | ||
}; | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -63,32 +67,10 @@ /** | ||
function fromSuccess(fn, onError = (e) => e) { | ||
return async (...args) => { | ||
const result = await mapErrors(fn, onError)(...args); | ||
return (async (...args) => { | ||
const result = await fn(...args); | ||
if (result.success) | ||
return result.data; | ||
throw new ErrorList(result.errors); | ||
}; | ||
throw new ErrorList(await onError(result.errors)); | ||
}); | ||
} | ||
/** | ||
* Creates a composable with unknown input and context that uses schemas to parse them into known types. | ||
* This allows you to code the function with arbitrary types knowinng that they will be enforced in runtime. | ||
* Very useful when piping data coming from any external source into your composables. | ||
* After giving the input and context schemas, you can pass a handler function that takes type safe input and context. That function is gonna catch any errors and always return a Result. | ||
* @param inputSchema the schema for the input | ||
* @param contextSchema the schema for the context | ||
* @returns a handler function that takes type safe input and context | ||
* @example | ||
* const safeFunction = withSchema( | ||
* z.object({ greeting: z.string() }), | ||
* z.object({ | ||
* user: z.object({ name: z.string() }) | ||
* }), | ||
* ) | ||
* const safeGreet = safeFunction(({ greeting }, { user }) => ({ | ||
* message: `${greeting} ${user.name}` | ||
* }) | ||
*/ | ||
function withSchema(inputSchema, contextSchema) { | ||
return (handler) => applySchema(inputSchema, contextSchema)(composable(handler)); | ||
} | ||
/** | ||
* Takes a composable and creates a composable withSchema that will assert the input and context types according to the given schemas. | ||
@@ -116,4 +98,5 @@ * @param fn a composable function | ||
function applySchema(inputSchema, contextSchema) { | ||
// TODO: Accept plain functions and equalize with withSchema | ||
return (fn) => { | ||
return ((input, context) => { | ||
const callable = ((input, context) => { | ||
const ctxResult = (contextSchema ?? alwaysUnknownSchema).safeParse(context); | ||
@@ -128,4 +111,28 @@ const result = (inputSchema ?? alwaysUnknownSchema).safeParse(input); | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
}; | ||
} | ||
/** | ||
* Creates a composable with unknown input and context that uses schemas to parse them into known types. | ||
* This allows you to code the function with arbitrary types knowinng that they will be enforced in runtime. | ||
* Very useful when piping data coming from any external source into your composables. | ||
* After giving the input and context schemas, you can pass a handler function that takes type safe input and context. That function is gonna catch any errors and always return a Result. | ||
* @param inputSchema the schema for the input | ||
* @param contextSchema the schema for the context | ||
* @returns a handler function that takes type safe input and context | ||
* @example | ||
* const safeFunction = withSchema( | ||
* z.object({ greeting: z.string() }), | ||
* z.object({ | ||
* user: z.object({ name: z.string() }) | ||
* }), | ||
* ) | ||
* const safeGreet = safeFunction(({ greeting }, { user }) => ({ | ||
* message: `${greeting} ${user.name}` | ||
* }) | ||
*/ | ||
function withSchema(inputSchema, contextSchema) { | ||
return (handler) => applySchema(inputSchema, contextSchema)(composable(handler)); | ||
} | ||
const alwaysUnknownSchema = { | ||
@@ -132,0 +139,0 @@ safeParse: (data) => ({ success: true, data }), |
import * as A from '../combinators.js'; | ||
import { composable, fromSuccess } from '../constructors.js'; | ||
function applyContextToList(fns, context) { | ||
return fns.map((fn) => (input) => fn(input, context)); | ||
return fns.map((fn) => { | ||
const callable = ((input) => composable(fn)(input, context)); | ||
callable.kind = 'composable'; | ||
return callable; | ||
}); | ||
} | ||
@@ -25,3 +29,5 @@ /** | ||
function pipe(...fns) { | ||
return ((input, context) => A.pipe(...applyContextToList(fns, context))(input)); | ||
const callable = ((input, context) => A.pipe(...applyContextToList(fns, context))(input)); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -43,3 +49,5 @@ /** | ||
function sequence(...fns) { | ||
return ((input, context) => A.sequence(...applyContextToList(fns, context))(input)); | ||
const callable = ((input, context) => A.sequence(...applyContextToList(fns, context))(input)); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -50,5 +58,5 @@ /** | ||
function branch(cf, resolver) { | ||
return (async (...args) => { | ||
const callable = (async (...args) => { | ||
const [input, context] = args; | ||
const result = await cf(input, context); | ||
const result = await composable(cf)(input, context); | ||
if (!result.success) | ||
@@ -60,6 +68,8 @@ return result; | ||
return result.data; | ||
return fromSuccess(nextFn)(result.data, context); | ||
return fromSuccess(composable(nextFn))(result.data, context); | ||
})(); | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
export { branch, pipe, sequence }; |
@@ -9,3 +9,3 @@ # Migrating from domain-functions | ||
- 🛡️ Enhanced Type Safety: Enjoy robust **type-safety during function composition**. The improved type-checking mechanisms prevent incompatible functions from being composed, reducing runtime errors and improving code reliability. | ||
- 🤌 Simplified Function Creation: **No need to define schemas**. Create composable functions easily and efficiently without the overhead of schema definitions. | ||
- 🤌 Simplified Function Creation: **No need to define schemas**. Create composable functions easily and efficiently without the overhead of schema definitions. Work with plain functions in every combinator. | ||
- 🕵🏽 Runtime Validation: Use the [`withSchema`](./API.md#withschema) function for optional runtime validation of inputs and context. This provides flexibility to enforce data integrity when needed without mandating it for every function. Assuming you have a big chain of composables you can use [`applySchema`](./API.md#applyschema) to run your runtime validation only once **avoiding unnecessary processing**. | ||
@@ -106,6 +106,6 @@ - 🔀 Flexible Compositions: The new combinators, such as [`context.pipe`](./API.md#contextpipe), [`context.sequence`](./API.md#contextsequence), and [`context.branch`](./API.md#contextbranch), offer powerful ways to manage **typed context** which are contextual information across your compositions. | ||
### map | ||
The `map`'s mapper function now receives all the arguments given to the composable. In domain-functions the mapper would only work with the output of the first function, that limitation is gone therefore we can work with both input and output. | ||
The `map`'s mapper function now receives all the arguments given to the first function. In domain-functions the mapper would only work with the output of the first df, that limitation is gone therefore we can work with both input and output. | ||
```ts | ||
const add = composable((a: number, b: number) => a + b) | ||
const add = (a: number, b: number) => a + b | ||
const aggregateInputAndOutput = map(add, (result, a, b) => ({ result, a, b })) | ||
@@ -153,3 +153,3 @@ // ^? Composable<(a: number, b: number) => { result: number, a: number, b: number }> | ||
```ts | ||
const fn = composable((a: number, b: number, c: number) => a + b + c) | ||
const fn = (a: number, b: number, c: number) => a + b + c | ||
const withTrace = trace((...args) => console.log(...args))(fn) | ||
@@ -180,3 +180,3 @@ const result = await withTrace(1, 2, 3) | ||
// you can do | ||
const fn = map(sequence(nameDf, ageDf), ([name, age]) => ({ name, age })) | ||
const fn = map(sequence(nameFn, ageFn), ([name, age]) => ({ name, age })) | ||
``` | ||
@@ -197,4 +197,4 @@ | ||
// you can do | ||
const fn1 = composable(() => ({ firstName: 'John' })) | ||
const fn2 = composable(() => ({ lastName: 'Doe' })) | ||
const fn1 = () => ({ firstName: 'John' }) | ||
const fn2 = () => ({ lastName: 'Doe' }) | ||
const fn = map(all(fn1, fn2), mergeObjects) | ||
@@ -201,0 +201,0 @@ ``` |
{ | ||
"name": "composable-functions", | ||
"version": "4.1.0", | ||
"version": "4.2.0", | ||
"description": "Types and functions to make composition easy and safe", | ||
@@ -5,0 +5,0 @@ "author": "Seasoned", |
@@ -48,7 +48,7 @@ <p align="center"> | ||
const faultyAdd = composable((a: number, b: number) => { | ||
const faultyAdd = (a: number, b: number) => { | ||
if (a === 1) throw new Error('a is 1') | ||
return a + b | ||
}) | ||
const show = composable(String) | ||
} | ||
const show = (a: number) => String(a) | ||
const addAndShow = pipe(faultyAdd, show) | ||
@@ -120,3 +120,3 @@ | ||
So we can define the `add` and the `toString` functions as a `Composable`: | ||
We can create a `Composable` by wrapping a function with the `composable` method: | ||
@@ -128,9 +128,19 @@ ```typescript | ||
// ^? Composable<(a: number, b: number) => number> | ||
``` | ||
const toString = composable((a: unknown) => `${a}`) | ||
// ^? Composable<(a: unknown) => string> | ||
Or we can use combinators work with both plain functions and `Composable` to create other composables: | ||
```typescript | ||
import { composable, pipe } from 'composable-functions' | ||
const add = composable((a: number, b: number) => a + b) | ||
// ^? Composable<(a: number, b: number) => number> | ||
const toString = (a: unknown) => `${a}` | ||
const addAndReturnString = pipe(add, toString) | ||
// ^? Composable<(a: number, b: number) => string> | ||
``` | ||
## Sequential composition | ||
Now we can compose them using pipe to create `addAndReturnString`: | ||
We can compose the functions above using pipe to create `addAndReturnString`: | ||
@@ -156,6 +166,6 @@ ```typescript | ||
### Using non-composables (mapping) | ||
### Transforming the output (mapping) | ||
Sometimes we want to use a simple function in this sort of sequential composition. Imagine that `toString` is not a composable, and you just want to apply a plain old function to the result of `add` when it succeeds. | ||
The function `map` can be used for this, since we are mapping over the result of a `Composable`: | ||
Sometimes we want to use a simple function to transform the output of another function. Imagine you want to apply a plain old function to the result of `add` when it succeeds. | ||
The function `map` can be used for this: | ||
@@ -166,2 +176,3 @@ ```typescript | ||
const addAndReturnString = map(add, result => `${result}`) | ||
// ^? Composable<(a: number, b: number) => string> | ||
``` | ||
@@ -175,6 +186,6 @@ | ||
```typescript | ||
import { composable, all } from 'composable-functions' | ||
import { all } from 'composable-functions' | ||
const add = composable((a: number, b: number) => a + b) | ||
const mul = composable((a: number, b: number) => a * b) | ||
const add = (a: number, b: number) => a + b | ||
const mul = (a: number, b: number) => a * b | ||
const addAndMul = all(add, mul) | ||
@@ -186,2 +197,13 @@ // ^? Composable<(a: number, b: number) => [number, number]> | ||
If you want to work with records instead of tuples, you can use the `collect` function: | ||
```typescript | ||
import { collect } from 'composable-functions' | ||
const add = (a: number, b: number) => a + b | ||
const mul = (a: number, b: number) => a * b | ||
const addAndMul = collect({ add, mul }) | ||
// ^? Composable<(a: number, b: number) => { add: number, mul: number }> | ||
``` | ||
## Handling errors | ||
@@ -221,3 +243,3 @@ Since a `Composable` always return a type `Result<T>` that might be either a failure or a success, there are never exceptions to catch. Any exception inside a `Composable` will return as an object with the shape: `{ success: false, errors: Error[] }`. | ||
```typescript | ||
import { composable, catchFailure } from 'composable-functions' | ||
import { catchFailure } from 'composable-functions' | ||
@@ -224,0 +246,0 @@ // assuming we have the definitions from the previous example |
@@ -38,10 +38,6 @@ "use strict"; | ||
* ```ts | ||
* import { composable, pipe } from 'composable-functions' | ||
* import { pipe } from 'composable-functions' | ||
* | ||
* const a = composable( | ||
* ({ aNumber }: { aNumber: number }) => ({ aString: String(aNumber) }), | ||
* ) | ||
* const b = composable( | ||
* ({ aString }: { aString: string }) => ({ aBoolean: aString == '1' }), | ||
* ) | ||
* const a = ({ aNumber }: { aNumber: number }) => ({ aString: String(aNumber) }) | ||
* const b = ({ aString }: { aString: string }) => ({ aBoolean: aString == '1' }) | ||
* const d = pipe(a, b) | ||
@@ -67,7 +63,7 @@ * // ^? Composable<({ aNumber }: { aNumber: number }) => { aBoolean: boolean }> | ||
* ```ts | ||
* import { composable, all } from 'composable-functions' | ||
* import { all } from 'composable-functions' | ||
* | ||
* const a = composable((id: number) => id + 1) | ||
* const b = composable(String) | ||
* const c = composable(Boolean) | ||
* const a = (id: number) => id + 1 | ||
* const b = (x: unknown) => String(x) | ||
* const c = (x: unknown) => Boolean(x) | ||
* const cf = all(a, b, c) | ||
@@ -78,4 +74,4 @@ * // ^? Composable<(id: number) => [string, number, boolean]> | ||
function all(...fns) { | ||
return (async (...args) => { | ||
const results = await Promise.all(fns.map((fn) => fn(...args))); | ||
const callable = (async (...args) => { | ||
const results = await Promise.all(fns.map((fn) => (0, constructors_js_1.composable)(fn)(...args))); | ||
if (results.some(({ success }) => success === false)) { | ||
@@ -86,2 +82,4 @@ return (0, constructors_js_1.failure)(results.map(({ errors }) => errors).flat()); | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -95,6 +93,6 @@ exports.all = all; | ||
* ```ts | ||
* import { composable, collect } from 'composable-functions' | ||
* import { collect } from 'composable-functions' | ||
* | ||
* const a = composable(() => '1') | ||
* const b = composable(() => 2) | ||
* const a = () => '1' | ||
* const b = () => 2 | ||
* const aComposable = collect({ a, b }) | ||
@@ -106,3 +104,4 @@ * // ^? Composable<() => { a: string, b: number }> | ||
const fnsWithKey = Object.entries(fns).map(([key, cf]) => map(cf, (result) => ({ [key]: result }))); | ||
return map(all(...fnsWithKey), mergeObjects); | ||
const allFns = all(...fnsWithKey); | ||
return map(allFns, mergeObjects); | ||
} | ||
@@ -116,6 +115,6 @@ exports.collect = collect; | ||
* ```ts | ||
* import { composable, sequence } from 'composable-functions' | ||
* import { sequence } from 'composable-functions' | ||
* | ||
* const a = composable((aNumber: number) => String(aNumber)) | ||
* const b = composable((aString: string) => aString === '1') | ||
* const a = (aNumber: number) => String(aNumber) | ||
* const b = (aString: string) => aString === '1' | ||
* const cf = sequence(a, b) | ||
@@ -126,5 +125,5 @@ * // ^? Composable<(aNumber: number) => [string, boolean]> | ||
function sequence(...fns) { | ||
return (async (...args) => { | ||
const callable = (async (...args) => { | ||
const [head, ...tail] = fns; | ||
const res = await head(...args); | ||
const res = await (0, constructors_js_1.composable)(head)(...args); | ||
if (!res.success) | ||
@@ -134,3 +133,3 @@ return (0, constructors_js_1.failure)(res.errors); | ||
for await (const fn of tail) { | ||
const res = await fn(result.at(-1)); | ||
const res = await (0, constructors_js_1.composable)(fn)(result.at(-1)); | ||
if (!res.success) | ||
@@ -142,2 +141,4 @@ return (0, constructors_js_1.failure)(res.errors); | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -152,5 +153,5 @@ exports.sequence = sequence; | ||
* ```ts | ||
* import { composable, map } from 'composable-functions' | ||
* import { map } from 'composable-functions' | ||
* | ||
* const increment = composable((n: number) => n + 1) | ||
* const increment = (n: number) => n + 1 | ||
* const incrementToString = map(increment, String) | ||
@@ -163,8 +164,10 @@ * // ^? Composable<(n: number) => string> | ||
function map(fn, mapper) { | ||
return async (...args) => { | ||
const result = await fn(...args); | ||
const callable = (async (...args) => { | ||
const result = await (0, constructors_js_1.composable)(fn)(...args); | ||
if (!result.success) | ||
return (0, constructors_js_1.failure)(result.errors); | ||
return (0, constructors_js_1.composable)(mapper)(result.data, ...args); | ||
}; | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -179,5 +182,5 @@ exports.map = map; | ||
* ```ts | ||
* import { composable, mapParameters } from 'composable-functions' | ||
* import { mapParameters } from 'composable-functions' | ||
* | ||
* const incrementId = composable(({ id }: { id: number }) => id + 1) | ||
* const incrementId = ({ id }: { id: number }) => id + 1 | ||
* const increment = mapParameters(incrementId, (id: number) => [{ id }]) | ||
@@ -188,8 +191,10 @@ * // ^? Composable<(id: number) => number> | ||
function mapParameters(fn, mapper) { | ||
return async (...args) => { | ||
const callable = (async (...args) => { | ||
const output = await (0, constructors_js_1.composable)(mapper)(...args); | ||
if (!output.success) | ||
return (0, constructors_js_1.failure)(output.errors); | ||
return fn(...output.data); | ||
}; | ||
return (0, constructors_js_1.composable)(fn)(...output.data); | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -203,5 +208,5 @@ exports.mapParameters = mapParameters; | ||
* ```ts | ||
* import { composable, catchFailure } from 'composable-functions' | ||
* import { catchFailure } from 'composable-functions' | ||
* | ||
* const increment = composable(({ id }: { id: number }) => id + 1) | ||
* const increment = ({ id }: { id: number }) => id + 1 | ||
* const negativeOnError = catchFailure(increment, (result, originalInput) => ( | ||
@@ -213,8 +218,10 @@ * originalInput.id * -1 | ||
function catchFailure(fn, catcher) { | ||
return async (...args) => { | ||
const res = await fn(...args); | ||
const callable = (async (...args) => { | ||
const res = await (0, constructors_js_1.composable)(fn)(...args); | ||
if (res.success) | ||
return (0, constructors_js_1.success)(res.data); | ||
return (0, constructors_js_1.composable)(catcher)(res.errors, ...args); | ||
}; | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -228,5 +235,5 @@ exports.catchFailure = catchFailure; | ||
* ```ts | ||
* import { composable, mapErrors } from 'composable-functions' | ||
* import { mapErrors } from 'composable-functions' | ||
* | ||
* const increment = composable(({ id }: { id: number }) => id + 1) | ||
* const increment = ({ id }: { id: number }) => id + 1 | ||
* const incrementWithErrorSummary = mapErrors(increment, (result) => ({ | ||
@@ -238,4 +245,4 @@ * errors: [{ message: 'Errors count: ' + result.errors.length }], | ||
function mapErrors(fn, mapper) { | ||
return async (...args) => { | ||
const res = await fn(...args); | ||
const callable = (async (...args) => { | ||
const res = await (0, constructors_js_1.composable)(fn)(...args); | ||
if (res.success) | ||
@@ -250,3 +257,5 @@ return (0, constructors_js_1.success)(res.data); | ||
} | ||
}; | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -262,3 +271,3 @@ exports.mapErrors = mapErrors; | ||
* ```ts | ||
* import { composable, trace } from 'composable-functions' | ||
* import { trace } from 'composable-functions' | ||
* | ||
@@ -268,3 +277,3 @@ * const trackErrors = trace((result, ...args) => { | ||
* }) | ||
* const increment = composable((id: number) => id + 1) | ||
* const increment = (id: number) => id + 1 | ||
* const incrementAndTrackErrors = trackErrors(increment) | ||
@@ -275,9 +284,13 @@ * // ^? Composable<(id: number) => number> | ||
function trace(traceFn) { | ||
return (fn) => async (...args) => { | ||
const originalResult = await fn(...args); | ||
const traceResult = await (0, constructors_js_1.composable)(traceFn)(originalResult, ...args); | ||
if (traceResult.success) | ||
return originalResult; | ||
return (0, constructors_js_1.failure)(traceResult.errors); | ||
}; | ||
return ((fn) => { | ||
const callable = async (...args) => { | ||
const originalResult = await (0, constructors_js_1.composable)(fn)(...args); | ||
const traceResult = await (0, constructors_js_1.composable)(traceFn)(originalResult, ...args); | ||
if (traceResult.success) | ||
return originalResult; | ||
return (0, constructors_js_1.failure)(traceResult.errors); | ||
}; | ||
callable.kind = 'composable'; | ||
return callable; | ||
}); | ||
} | ||
@@ -307,4 +320,4 @@ exports.trace = trace; | ||
function branch(cf, resolver) { | ||
return (async (...args) => { | ||
const result = await cf(...args); | ||
const callable = (async (...args) => { | ||
const result = await (0, constructors_js_1.composable)(cf)(...args); | ||
if (!result.success) | ||
@@ -316,6 +329,8 @@ return result; | ||
return result.data; | ||
return (0, constructors_js_1.fromSuccess)(nextComposable)(result.data); | ||
return (0, constructors_js_1.fromSuccess)((0, constructors_js_1.composable)(nextComposable))(result.data); | ||
})(); | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
exports.branch = branch; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.withSchema = exports.success = exports.fromSuccess = exports.failure = exports.composable = exports.applySchema = void 0; | ||
const combinators_js_1 = require("./combinators.js"); | ||
const errors_js_1 = require("./errors.js"); | ||
@@ -36,3 +35,6 @@ /** | ||
function composable(fn) { | ||
return async (...args) => { | ||
if ('kind' in fn && fn.kind === 'composable') { | ||
return fn; | ||
} | ||
const callable = async (...args) => { | ||
try { | ||
@@ -50,2 +52,4 @@ // deno-lint-ignore no-explicit-any | ||
}; | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -69,34 +73,11 @@ exports.composable = composable; | ||
function fromSuccess(fn, onError = (e) => e) { | ||
return async (...args) => { | ||
const result = await (0, combinators_js_1.mapErrors)(fn, onError)(...args); | ||
return (async (...args) => { | ||
const result = await fn(...args); | ||
if (result.success) | ||
return result.data; | ||
throw new errors_js_1.ErrorList(result.errors); | ||
}; | ||
throw new errors_js_1.ErrorList(await onError(result.errors)); | ||
}); | ||
} | ||
exports.fromSuccess = fromSuccess; | ||
/** | ||
* Creates a composable with unknown input and context that uses schemas to parse them into known types. | ||
* This allows you to code the function with arbitrary types knowinng that they will be enforced in runtime. | ||
* Very useful when piping data coming from any external source into your composables. | ||
* After giving the input and context schemas, you can pass a handler function that takes type safe input and context. That function is gonna catch any errors and always return a Result. | ||
* @param inputSchema the schema for the input | ||
* @param contextSchema the schema for the context | ||
* @returns a handler function that takes type safe input and context | ||
* @example | ||
* const safeFunction = withSchema( | ||
* z.object({ greeting: z.string() }), | ||
* z.object({ | ||
* user: z.object({ name: z.string() }) | ||
* }), | ||
* ) | ||
* const safeGreet = safeFunction(({ greeting }, { user }) => ({ | ||
* message: `${greeting} ${user.name}` | ||
* }) | ||
*/ | ||
function withSchema(inputSchema, contextSchema) { | ||
return (handler) => applySchema(inputSchema, contextSchema)(composable(handler)); | ||
} | ||
exports.withSchema = withSchema; | ||
/** | ||
* Takes a composable and creates a composable withSchema that will assert the input and context types according to the given schemas. | ||
@@ -124,4 +105,5 @@ * @param fn a composable function | ||
function applySchema(inputSchema, contextSchema) { | ||
// TODO: Accept plain functions and equalize with withSchema | ||
return (fn) => { | ||
return ((input, context) => { | ||
const callable = ((input, context) => { | ||
const ctxResult = (contextSchema ?? alwaysUnknownSchema).safeParse(context); | ||
@@ -136,7 +118,32 @@ const result = (inputSchema ?? alwaysUnknownSchema).safeParse(input); | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
}; | ||
} | ||
exports.applySchema = applySchema; | ||
/** | ||
* Creates a composable with unknown input and context that uses schemas to parse them into known types. | ||
* This allows you to code the function with arbitrary types knowinng that they will be enforced in runtime. | ||
* Very useful when piping data coming from any external source into your composables. | ||
* After giving the input and context schemas, you can pass a handler function that takes type safe input and context. That function is gonna catch any errors and always return a Result. | ||
* @param inputSchema the schema for the input | ||
* @param contextSchema the schema for the context | ||
* @returns a handler function that takes type safe input and context | ||
* @example | ||
* const safeFunction = withSchema( | ||
* z.object({ greeting: z.string() }), | ||
* z.object({ | ||
* user: z.object({ name: z.string() }) | ||
* }), | ||
* ) | ||
* const safeGreet = safeFunction(({ greeting }, { user }) => ({ | ||
* message: `${greeting} ${user.name}` | ||
* }) | ||
*/ | ||
function withSchema(inputSchema, contextSchema) { | ||
return (handler) => applySchema(inputSchema, contextSchema)(composable(handler)); | ||
} | ||
exports.withSchema = withSchema; | ||
const alwaysUnknownSchema = { | ||
safeParse: (data) => ({ success: true, data }), | ||
}; |
@@ -30,3 +30,7 @@ "use strict"; | ||
function applyContextToList(fns, context) { | ||
return fns.map((fn) => (input) => fn(input, context)); | ||
return fns.map((fn) => { | ||
const callable = ((input) => (0, constructors_js_1.composable)(fn)(input, context)); | ||
callable.kind = 'composable'; | ||
return callable; | ||
}); | ||
} | ||
@@ -52,3 +56,5 @@ /** | ||
function pipe(...fns) { | ||
return ((input, context) => A.pipe(...applyContextToList(fns, context))(input)); | ||
const callable = ((input, context) => A.pipe(...applyContextToList(fns, context))(input)); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -71,3 +77,5 @@ exports.pipe = pipe; | ||
function sequence(...fns) { | ||
return ((input, context) => A.sequence(...applyContextToList(fns, context))(input)); | ||
const callable = ((input, context) => A.sequence(...applyContextToList(fns, context))(input)); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
@@ -79,5 +87,5 @@ exports.sequence = sequence; | ||
function branch(cf, resolver) { | ||
return (async (...args) => { | ||
const callable = (async (...args) => { | ||
const [input, context] = args; | ||
const result = await cf(input, context); | ||
const result = await (0, constructors_js_1.composable)(cf)(input, context); | ||
if (!result.success) | ||
@@ -89,6 +97,8 @@ return result; | ||
return result.data; | ||
return (0, constructors_js_1.fromSuccess)(nextFn)(result.data, context); | ||
return (0, constructors_js_1.fromSuccess)((0, constructors_js_1.composable)(nextFn))(result.data, context); | ||
})(); | ||
}); | ||
callable.kind = 'composable'; | ||
return callable; | ||
} | ||
exports.branch = branch; |
import type { BranchReturn, CanComposeInParallel, CanComposeInSequence, Composable, MapParametersReturn, MergeObjects, PipeReturn, RecordToTuple, Result, SequenceReturn, UnpackData } from './types.js'; | ||
import type { Internal } from './internal/types.js'; | ||
/** | ||
@@ -32,10 +33,6 @@ * Merges a list of objects into a single object. | ||
* ```ts | ||
* import { composable, pipe } from 'composable-functions' | ||
* import { pipe } from 'composable-functions' | ||
* | ||
* const a = composable( | ||
* ({ aNumber }: { aNumber: number }) => ({ aString: String(aNumber) }), | ||
* ) | ||
* const b = composable( | ||
* ({ aString }: { aString: string }) => ({ aBoolean: aString == '1' }), | ||
* ) | ||
* const a = ({ aNumber }: { aNumber: number }) => ({ aString: String(aNumber) }) | ||
* const b = ({ aString }: { aString: string }) => ({ aBoolean: aString == '1' }) | ||
* const d = pipe(a, b) | ||
@@ -45,3 +42,3 @@ * // ^? Composable<({ aNumber }: { aNumber: number }) => { aBoolean: boolean }> | ||
*/ | ||
declare function pipe<Fns extends [Composable, ...Composable[]]>(...fns: Fns): PipeReturn<CanComposeInSequence<Fns>>; | ||
declare function pipe<Fns extends [Internal.AnyFn, ...Internal.AnyFn[]]>(...fns: Fns): PipeReturn<CanComposeInSequence<Internal.Composables<Fns>>>; | ||
/** | ||
@@ -58,7 +55,7 @@ * Composes functions to run in parallel returning a tuple of all results when all are successful. | ||
* ```ts | ||
* import { composable, all } from 'composable-functions' | ||
* import { all } from 'composable-functions' | ||
* | ||
* const a = composable((id: number) => id + 1) | ||
* const b = composable(String) | ||
* const c = composable(Boolean) | ||
* const a = (id: number) => id + 1 | ||
* const b = (x: unknown) => String(x) | ||
* const c = (x: unknown) => Boolean(x) | ||
* const cf = all(a, b, c) | ||
@@ -68,4 +65,4 @@ * // ^? Composable<(id: number) => [string, number, boolean]> | ||
*/ | ||
declare function all<Fns extends Composable[]>(...fns: Fns): Composable<(...args: Parameters<NonNullable<CanComposeInParallel<Fns>[0]>>) => { | ||
[k in keyof Fns]: UnpackData<Fns[k]>; | ||
declare function all<Fns extends Internal.AnyFn[]>(...fns: Fns): Composable<(...args: Parameters<NonNullable<CanComposeInParallel<Internal.Composables<Fns>>[0]>>) => { | ||
[k in keyof Fns]: UnpackData<Internal.Composables<Fns>[k]>; | ||
}>; | ||
@@ -78,6 +75,6 @@ /** | ||
* ```ts | ||
* import { composable, collect } from 'composable-functions' | ||
* import { collect } from 'composable-functions' | ||
* | ||
* const a = composable(() => '1') | ||
* const b = composable(() => 2) | ||
* const a = () => '1' | ||
* const b = () => 2 | ||
* const aComposable = collect({ a, b }) | ||
@@ -87,4 +84,4 @@ * // ^? Composable<() => { a: string, b: number }> | ||
*/ | ||
declare function collect<Fns extends Record<string, Composable>>(fns: Fns): Composable<(...args: Parameters<Exclude<CanComposeInParallel<RecordToTuple<Fns>>[0], undefined>>) => { | ||
[key in keyof Fns]: UnpackData<Fns[key]>; | ||
declare function collect<Fns extends Record<string, Internal.AnyFn>>(fns: Fns): Composable<(...args: Parameters<Exclude<CanComposeInParallel<RecordToTuple<Internal.Composables<Fns>>>[0], undefined>>) => { | ||
[key in keyof Fns]: UnpackData<Composable<Fns[key]>>; | ||
}>; | ||
@@ -97,6 +94,6 @@ /** | ||
* ```ts | ||
* import { composable, sequence } from 'composable-functions' | ||
* import { sequence } from 'composable-functions' | ||
* | ||
* const a = composable((aNumber: number) => String(aNumber)) | ||
* const b = composable((aString: string) => aString === '1') | ||
* const a = (aNumber: number) => String(aNumber) | ||
* const b = (aString: string) => aString === '1' | ||
* const cf = sequence(a, b) | ||
@@ -106,3 +103,3 @@ * // ^? Composable<(aNumber: number) => [string, boolean]> | ||
*/ | ||
declare function sequence<Fns extends [Composable, ...Composable[]]>(...fns: Fns): SequenceReturn<CanComposeInSequence<Fns>>; | ||
declare function sequence<Fns extends [Internal.AnyFn, ...Internal.AnyFn[]]>(...fns: Fns): SequenceReturn<CanComposeInSequence<Internal.Composables<Fns>>>; | ||
/** | ||
@@ -115,5 +112,5 @@ * It takes a Composable and a mapper to apply a transformation over the resulting output. It only runs if the function was successfull. When the given function fails, its error is returned wihout changes. | ||
* ```ts | ||
* import { composable, map } from 'composable-functions' | ||
* import { map } from 'composable-functions' | ||
* | ||
* const increment = composable((n: number) => n + 1) | ||
* const increment = (n: number) => n + 1 | ||
* const incrementToString = map(increment, String) | ||
@@ -125,3 +122,3 @@ * // ^? Composable<(n: number) => string> | ||
*/ | ||
declare function map<Fn extends Composable, O>(fn: Fn, mapper: (res: UnpackData<Fn>, ...originalInput: Parameters<Fn>) => O | Promise<O>): Composable<(...args: Parameters<Fn>) => O>; | ||
declare function map<Fn extends Internal.AnyFn, O>(fn: Fn, mapper: (res: UnpackData<Composable<Fn>>, ...originalInput: Parameters<Fn>) => O | Promise<O>): Composable<(...args: Parameters<Fn>) => O>; | ||
/** | ||
@@ -134,5 +131,5 @@ * It takes a Composable and a function that will map the input parameters to the expected input of the given Composable. Good to adequate the output of a composable into the input of the next composable in a composition. The function must return an array of parameters that will be passed to the Composable. | ||
* ```ts | ||
* import { composable, mapParameters } from 'composable-functions' | ||
* import { mapParameters } from 'composable-functions' | ||
* | ||
* const incrementId = composable(({ id }: { id: number }) => id + 1) | ||
* const incrementId = ({ id }: { id: number }) => id + 1 | ||
* const increment = mapParameters(incrementId, (id: number) => [{ id }]) | ||
@@ -142,3 +139,3 @@ * // ^? Composable<(id: number) => number> | ||
*/ | ||
declare function mapParameters<Fn extends Composable, NewParameters extends unknown[], const MapperOutput extends Parameters<Fn>>(fn: Fn, mapper: (...args: NewParameters) => Promise<MapperOutput> | MapperOutput): MapParametersReturn<Fn, NewParameters, MapperOutput>; | ||
declare function mapParameters<Fn extends Internal.AnyFn, NewParameters extends unknown[], const MapperOutput extends Parameters<Composable<Fn>>>(fn: Fn, mapper: (...args: NewParameters) => Promise<MapperOutput> | MapperOutput): MapParametersReturn<Composable<Fn>, NewParameters, MapperOutput>; | ||
/** | ||
@@ -150,5 +147,5 @@ * Try to recover from a resulting Failure. When the given function succeeds, its result is returned without changes. | ||
* ```ts | ||
* import { composable, catchFailure } from 'composable-functions' | ||
* import { catchFailure } from 'composable-functions' | ||
* | ||
* const increment = composable(({ id }: { id: number }) => id + 1) | ||
* const increment = ({ id }: { id: number }) => id + 1 | ||
* const negativeOnError = catchFailure(increment, (result, originalInput) => ( | ||
@@ -159,3 +156,3 @@ * originalInput.id * -1 | ||
*/ | ||
declare function catchFailure<Fn extends Composable, C extends (err: Error[], ...originalInput: Parameters<Fn>) => any>(fn: Fn, catcher: C): Composable<(...args: Parameters<Fn>) => Awaited<ReturnType<C>> extends never[] ? UnpackData<Fn> extends any[] ? UnpackData<Fn> : Awaited<ReturnType<C>> | UnpackData<Fn> : Awaited<ReturnType<C>> | UnpackData<Fn>>; | ||
declare function catchFailure<Fn extends Internal.AnyFn, C extends (err: Error[], ...originalInput: Parameters<Fn>) => any>(fn: Fn, catcher: C): Composable<(...args: Parameters<Fn>) => Awaited<ReturnType<C>> extends never[] ? UnpackData<Composable<Fn>> extends any[] ? UnpackData<Composable<Fn>> : Awaited<ReturnType<C>> | UnpackData<Composable<Fn>> : Awaited<ReturnType<C>> | UnpackData<Composable<Fn>>>; | ||
/** | ||
@@ -167,5 +164,5 @@ * Creates a new function that will apply a transformation over the list of Errors of a Failure from a given function. When the given function succeeds, its result is returned without changes. | ||
* ```ts | ||
* import { composable, mapErrors } from 'composable-functions' | ||
* import { mapErrors } from 'composable-functions' | ||
* | ||
* const increment = composable(({ id }: { id: number }) => id + 1) | ||
* const increment = ({ id }: { id: number }) => id + 1 | ||
* const incrementWithErrorSummary = mapErrors(increment, (result) => ({ | ||
@@ -176,3 +173,3 @@ * errors: [{ message: 'Errors count: ' + result.errors.length }], | ||
*/ | ||
declare function mapErrors<P extends unknown[], Output>(fn: Composable<(...args: P) => Output>, mapper: (err: Error[]) => Error[] | Promise<Error[]>): Composable<(...args: P) => Output>; | ||
declare function mapErrors<Fn extends Internal.AnyFn>(fn: Fn, mapper: (err: Error[]) => Error[] | Promise<Error[]>): Composable<Fn>; | ||
/** | ||
@@ -186,3 +183,3 @@ * Whenever you need to intercept inputs and a composable result without changing them you can use this function. | ||
* ```ts | ||
* import { composable, trace } from 'composable-functions' | ||
* import { trace } from 'composable-functions' | ||
* | ||
@@ -192,3 +189,3 @@ * const trackErrors = trace((result, ...args) => { | ||
* }) | ||
* const increment = composable((id: number) => id + 1) | ||
* const increment = (id: number) => id + 1 | ||
* const incrementAndTrackErrors = trackErrors(increment) | ||
@@ -198,3 +195,3 @@ * // ^? Composable<(id: number) => number> | ||
*/ | ||
declare function trace(traceFn: (result: Result<unknown>, ...originalInput: unknown[]) => Promise<void> | void): <P extends unknown[], Output>(fn: Composable<(...args: P) => Output>) => Composable<(...args: P) => Output>; | ||
declare function trace(traceFn: (result: Result<unknown>, ...originalInput: unknown[]) => Promise<void> | void): <Fn extends Internal.AnyFn>(fn: Fn) => Composable<Fn>; | ||
/** | ||
@@ -221,3 +218,3 @@ * Compose 2 functions conditionally. | ||
*/ | ||
declare function branch<SourceComposable extends Composable, Resolver extends (o: UnpackData<SourceComposable>) => Composable | null | Promise<Composable | null>>(cf: SourceComposable, resolver: Resolver): BranchReturn<SourceComposable, Resolver>; | ||
declare function branch<SourceComposable extends Internal.AnyFn, Resolver extends (o: UnpackData<Composable<SourceComposable>>) => Internal.AnyFn | null | Promise<Internal.AnyFn | null>>(cf: SourceComposable, resolver: Resolver): BranchReturn<Composable<SourceComposable>, Resolver>; | ||
export { all, branch, catchFailure, collect, map, mapErrors, mapParameters, mergeObjects, pipe, sequence, trace, }; |
@@ -1,2 +0,3 @@ | ||
import type { ApplySchemaReturn, Composable, ComposableWithSchema, Failure, ParserSchema, Success } from './types.js'; | ||
import type { Internal } from './internal/types.js'; | ||
import type { ApplySchemaReturn, Composable, ComposableWithSchema, Failure, ParserSchema, Success, UnpackData } from './types.js'; | ||
/** | ||
@@ -15,3 +16,3 @@ * It receives any data (T) and returns a Success<T> object. | ||
*/ | ||
declare function composable<T extends Function>(fn: T): Composable<T extends (...args: any[]) => any ? T : never>; | ||
declare function composable<T extends Function>(fn: T): Composable<T extends Internal.AnyFn ? T : never>; | ||
/** | ||
@@ -34,22 +35,2 @@ * It can be used to call a composable from another composable. It will return the output of the given function if it was successfull, otherwise it will throw a `ErrorList` that will bubble up to the parent function. | ||
/** | ||
* Creates a composable with unknown input and context that uses schemas to parse them into known types. | ||
* This allows you to code the function with arbitrary types knowinng that they will be enforced in runtime. | ||
* Very useful when piping data coming from any external source into your composables. | ||
* After giving the input and context schemas, you can pass a handler function that takes type safe input and context. That function is gonna catch any errors and always return a Result. | ||
* @param inputSchema the schema for the input | ||
* @param contextSchema the schema for the context | ||
* @returns a handler function that takes type safe input and context | ||
* @example | ||
* const safeFunction = withSchema( | ||
* z.object({ greeting: z.string() }), | ||
* z.object({ | ||
* user: z.object({ name: z.string() }) | ||
* }), | ||
* ) | ||
* const safeGreet = safeFunction(({ greeting }, { user }) => ({ | ||
* message: `${greeting} ${user.name}` | ||
* }) | ||
*/ | ||
declare function withSchema<I, C>(inputSchema?: ParserSchema<I>, contextSchema?: ParserSchema<C>): <Output>(hander: (input: I, context: C) => Output) => ComposableWithSchema<Output>; | ||
/** | ||
* Takes a composable and creates a composable withSchema that will assert the input and context types according to the given schemas. | ||
@@ -76,3 +57,27 @@ * @param fn a composable function | ||
*/ | ||
declare function applySchema<ParsedInput, ParsedContext>(inputSchema?: ParserSchema<ParsedInput>, contextSchema?: ParserSchema<ParsedContext>): <R, Input, Context>(fn: Composable<(input?: Input | undefined, context?: Context | undefined) => R>) => ApplySchemaReturn<ParsedInput, ParsedContext, Composable<(input?: Input | undefined, context?: Context | undefined) => R>>; | ||
declare function applySchema<ParsedInput, ParsedContext>(inputSchema?: ParserSchema<ParsedInput>, contextSchema?: ParserSchema<ParsedContext>): <R, Input, Context>(fn: ((input: Input, context: Context) => Promise<import("./types.js").Result<Awaited<R>>>) & { | ||
kind: "composable"; | ||
}) => ApplySchemaReturn<ParsedInput, ParsedContext, ((input: Input, context: Context) => Promise<import("./types.js").Result<Awaited<R>>>) & { | ||
kind: "composable"; | ||
}>; | ||
/** | ||
* Creates a composable with unknown input and context that uses schemas to parse them into known types. | ||
* This allows you to code the function with arbitrary types knowinng that they will be enforced in runtime. | ||
* Very useful when piping data coming from any external source into your composables. | ||
* After giving the input and context schemas, you can pass a handler function that takes type safe input and context. That function is gonna catch any errors and always return a Result. | ||
* @param inputSchema the schema for the input | ||
* @param contextSchema the schema for the context | ||
* @returns a handler function that takes type safe input and context | ||
* @example | ||
* const safeFunction = withSchema( | ||
* z.object({ greeting: z.string() }), | ||
* z.object({ | ||
* user: z.object({ name: z.string() }) | ||
* }), | ||
* ) | ||
* const safeGreet = safeFunction(({ greeting }, { user }) => ({ | ||
* message: `${greeting} ${user.name}` | ||
* }) | ||
*/ | ||
declare function withSchema<I, C>(inputSchema?: ParserSchema<I>, contextSchema?: ParserSchema<C>): <Fn extends (input: I, context: C) => unknown>(fn: Fn) => ComposableWithSchema<UnpackData<Composable<Fn>>>; | ||
export { applySchema, composable, failure, fromSuccess, success, withSchema }; |
import type { Composable, UnpackData } from '../types.js'; | ||
import type { BranchReturn, PipeReturn, SequenceReturn } from './types.js'; | ||
import type { Internal } from '../internal/types.js'; | ||
/** | ||
@@ -21,3 +22,3 @@ * Creates a single composable out of a chain of multiple functions. It will pass the same context to all given functions, and it will pass the output of a function as the next function's input in left-to-right order. The resulting data will be the output of the rightmost function. | ||
*/ | ||
declare function pipe<Fns extends Composable[]>(...fns: Fns): PipeReturn<Fns>; | ||
declare function pipe<Fns extends Internal.AnyFn[]>(...fns: Fns): PipeReturn<Internal.Composables<Fns>>; | ||
/** | ||
@@ -37,7 +38,7 @@ * Works like `context.pipe` but it will collect the output of every function in a tuple. | ||
*/ | ||
declare function sequence<Fns extends Composable[]>(...fns: Fns): SequenceReturn<Fns>; | ||
declare function sequence<Fns extends Internal.AnyFn[]>(...fns: Fns): SequenceReturn<Internal.Composables<Fns>>; | ||
/** | ||
* Like branch but preserving the context parameter. | ||
*/ | ||
declare function branch<SourceComposable extends Composable, Resolver extends (o: UnpackData<SourceComposable>) => Composable | null | Promise<Composable | null>>(cf: SourceComposable, resolver: Resolver): BranchReturn<SourceComposable, Resolver>; | ||
declare function branch<SourceComposable extends Internal.AnyFn, Resolver extends (o: UnpackData<Composable<SourceComposable>>) => Internal.AnyFn | null | Promise<Internal.AnyFn | null>>(cf: SourceComposable, resolver: Resolver): BranchReturn<Composable<SourceComposable>, Resolver>; | ||
export { branch, pipe, sequence }; |
@@ -23,5 +23,8 @@ import type { Internal } from '../internal/types.js'; | ||
type SetContext<Params extends unknown[], Ctx extends [unknown?] = [unknown?]> = Params extends [infer firstMandatory, ...any] ? [firstMandatory, ...Ctx] : Params extends [...Partial<[infer firstOptional, ...any]>] ? [firstOptional?, ...Ctx] : never; | ||
type BranchContext<SourceComposable extends Composable, Resolver extends (...args: any[]) => Composable | null | Promise<Composable | null>> = Awaited<ReturnType<Resolver>> extends Composable<any> ? CommonContext<[SourceComposable, Awaited<ReturnType<Resolver>>]> : GetContext<Parameters<SourceComposable>>; | ||
type BranchReturn<SourceComposable extends Composable, Resolver extends (...args: any[]) => Composable | null | Promise<Composable | null>> = CanComposeInSequence<[ | ||
type BranchContext<SourceComposable extends Composable, Resolver extends (...args: any[]) => Internal.AnyFn | null | Promise<Internal.AnyFn | null>> = Awaited<ReturnType<Resolver>> extends Internal.AnyFn ? CommonContext<[ | ||
SourceComposable, | ||
Composable<NonNullable<Awaited<ReturnType<Resolver>>>> | ||
]> : GetContext<Parameters<SourceComposable>>; | ||
type BranchReturn<SourceComposable extends Composable, Resolver extends (...args: any[]) => Internal.AnyFn | null | Promise<Internal.AnyFn | null>> = CanComposeInSequence<[ | ||
SourceComposable, | ||
Composable<Resolver> | ||
@@ -34,3 +37,3 @@ ]> extends Composable[] ? Awaited<ReturnType<Resolver>> extends null ? SourceComposable : CanComposeInSequence<[ | ||
Awaited<ReturnType<Resolver>> | ||
]>[0]>, BranchContext<SourceComposable, Resolver>>) => null extends Awaited<ReturnType<Resolver>> ? UnpackData<SourceComposable> | UnpackData<Extract<Awaited<ReturnType<Resolver>>, Composable>> : UnpackData<Extract<Awaited<ReturnType<Resolver>>, Composable>>> : CanComposeInSequence<[SourceComposable, Awaited<ReturnType<Resolver>>]> : CanComposeInSequence<[SourceComposable, Composable<Resolver>]>; | ||
]>[0]>, BranchContext<SourceComposable, Resolver>>) => null extends Awaited<ReturnType<Resolver>> ? UnpackData<SourceComposable> | UnpackData<Composable<NonNullable<Awaited<ReturnType<Resolver>>>>> : UnpackData<Composable<NonNullable<Awaited<ReturnType<Resolver>>>>>> : CanComposeInSequence<[SourceComposable, Awaited<ReturnType<Resolver>>]> : CanComposeInSequence<[SourceComposable, Composable<Resolver>]>; | ||
export type { BranchReturn, CommonContext, GetContext, PipeReturn, SequenceReturn, SetContext, }; |
@@ -0,1 +1,2 @@ | ||
import type { Composable } from '../types.js'; | ||
declare namespace Internal { | ||
@@ -10,2 +11,3 @@ type IncompatibleArguments = { | ||
}; | ||
type AnyFn = (...args: any[]) => any; | ||
type Prettify<T> = { | ||
@@ -15,3 +17,6 @@ [K in keyof T]: T[K]; | ||
type IsNever<A> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends never ? 1 : 2) ? true : false; | ||
type ApplyArgumentsToFns<Fns extends any[], Args extends any[], Output extends any[] = []> = Fns extends [(...a: any[]) => infer OA, ...infer restA] ? ApplyArgumentsToFns<restA, Args, [...Output, (...a: Args) => OA]> : Output; | ||
type ApplyArgumentsToFns<Fns extends unknown[], Args extends unknown[], Output extends Composable[] = []> = Fns extends [Composable<(...a: any[]) => infer OA>, ...infer restA] ? ApplyArgumentsToFns<restA, Args, [ | ||
...Output, | ||
Composable<(...a: Args) => OA> | ||
]> : Output; | ||
type UnionToTuple<T> = ((T extends any ? (t: T) => T : never) extends infer U ? (U extends any ? (u: U) => any : never) extends (v: infer V) => any ? V : never : never) extends (_: any) => infer W ? [...UnionToTuple<Exclude<T, W>>, W] : []; | ||
@@ -45,3 +50,6 @@ type Keys<R extends Record<string, any>> = UnionToTuple<keyof R>; | ||
} ? B : A extends Record<PropertyKey, unknown> ? B extends Record<PropertyKey, unknown> ? Prettify<A & B> : FailToCompose<A, B> : FailToCompose<A, B>; | ||
type Composables<Fns extends Record<string, AnyFn> | Array<AnyFn>> = { | ||
[K in keyof Fns]: Composable<Extract<Fns[K], AnyFn>>; | ||
}; | ||
} | ||
export type { Internal }; |
@@ -41,3 +41,7 @@ import type { Internal } from './internal/types.js'; | ||
*/ | ||
type Composable<T extends (...args: any[]) => any = (...args: any[]) => any> = (...args: Parameters<T>) => Promise<Result<Awaited<ReturnType<T>>>>; | ||
type Composable<T extends Internal.AnyFn = Internal.AnyFn> = T extends { | ||
kind: 'composable'; | ||
} ? T : ((...args: Parameters<T>) => Promise<Result<Awaited<ReturnType<T>>>>) & { | ||
kind: 'composable'; | ||
}; | ||
/** | ||
@@ -80,3 +84,3 @@ * A composable async function with schema validation at runtime. | ||
*/ | ||
type CanComposeInSequence<Fns extends any[], Arguments extends any[] = []> = Fns extends [Composable<(...a: infer PA) => infer OA>, ...infer restA] ? restA extends [ | ||
type CanComposeInSequence<Fns extends unknown[], Arguments extends unknown[] = []> = Fns extends [Composable<(...a: infer PA) => infer OA>, ...infer restA] ? restA extends [ | ||
Composable<(firstParameter: infer FirstBParameter, ...b: infer PB) => any>, | ||
@@ -91,3 +95,3 @@ ...unknown[] | ||
*/ | ||
type CanComposeInParallel<Fns extends any[], OriginalFns extends any[] = Fns> = Fns extends [Composable<(...a: infer PA) => any>, ...infer restA] ? restA extends [Composable<(...b: infer PB) => infer OB>, ...infer restB] ? Internal.SubtypesTuple<PA, PB> extends [...infer MergedP] ? CanComposeInParallel<[ | ||
type CanComposeInParallel<Fns extends unknown[], OriginalFns extends unknown[] = Fns> = Fns extends [Composable<(...a: infer PA) => unknown>, ...infer restA] ? restA extends [Composable<(...b: infer PB) => infer OB>, ...infer restB] ? Internal.SubtypesTuple<PA, PB> extends [...infer MergedP] ? CanComposeInParallel<[ | ||
Composable<(...args: MergedP) => OB>, | ||
@@ -99,3 +103,3 @@ ...restB | ||
*/ | ||
type RecordToTuple<T extends Record<string, Composable>> = Internal.RecordValuesFromKeysTuple<T, Internal.Keys<T>>; | ||
type RecordToTuple<T extends Record<string, (...args: any) => any>> = Internal.RecordValuesFromKeysTuple<T, Internal.Keys<T>>; | ||
/** | ||
@@ -141,3 +145,3 @@ * A serializable error object. | ||
*/ | ||
type BranchReturn<SourceComposable extends Composable, Resolver extends (...args: any[]) => Composable | null | Promise<Composable | null>> = CanComposeInSequence<[ | ||
type BranchReturn<SourceComposable extends Composable, Resolver extends (...args: any[]) => Internal.AnyFn | null | Promise<Internal.AnyFn | null>> = CanComposeInSequence<[ | ||
SourceComposable, | ||
@@ -151,3 +155,3 @@ Composable<Resolver> | ||
Awaited<ReturnType<Resolver>> | ||
]>[0]>) => null extends Awaited<ReturnType<Resolver>> ? UnpackData<SourceComposable> | UnpackData<Extract<Awaited<ReturnType<Resolver>>, Composable>> : UnpackData<Extract<Awaited<ReturnType<Resolver>>, Composable>>> : CanComposeInSequence<[SourceComposable, Awaited<ReturnType<Resolver>>]> : CanComposeInSequence<[SourceComposable, Composable<Resolver>]>; | ||
]>[0]>) => null extends Awaited<ReturnType<Resolver>> ? UnpackData<SourceComposable> | UnpackData<Composable<NonNullable<Awaited<ReturnType<Resolver>>>>> : UnpackData<Composable<NonNullable<Awaited<ReturnType<Resolver>>>>>> : CanComposeInSequence<[SourceComposable, Awaited<ReturnType<Resolver>>]> : CanComposeInSequence<[SourceComposable, Composable<Resolver>]>; | ||
/** | ||
@@ -160,3 +164,3 @@ * Ensure that schemas are compatible with composable input and context otherwise return a FailToCompose. | ||
*/ | ||
type MapParametersReturn<Fn extends Composable, NewParams extends any[], O extends Parameters<Fn>> = Composable<(...args: NewParams) => Internal.IsNever<O> extends true ? never : UnpackData<Fn>>; | ||
type MapParametersReturn<Fn extends Composable, NewParams extends unknown[], O extends Parameters<Fn>> = Composable<(...args: NewParams) => Internal.IsNever<O> extends true ? never : UnpackData<Fn>>; | ||
/** | ||
@@ -163,0 +167,0 @@ * A type that represents an error when composing functions with incompatible arguments. |
192634
2537
321