From e6b576007a3a4a50149273a89618eff5ab20a8ec Mon Sep 17 00:00:00 2001 From: Guga Guichard Date: Tue, 25 Jun 2024 21:06:10 -0300 Subject: [PATCH] feat: Combinators accept any function instead of only composables --- src/combinators.ts | 53 ++++++++++++++++++++++---------- src/constructors.ts | 4 ++- src/context/combinators.ts | 53 +++++++++++++++++++++++--------- src/context/tests/branch.test.ts | 48 ++++++++++++++--------------- src/context/tests/types.test.ts | 49 +++++++++++++++-------------- src/context/types.ts | 4 ++- 6 files changed, 128 insertions(+), 83 deletions(-) diff --git a/src/combinators.ts b/src/combinators.ts index 2e60148..bb19da0 100644 --- a/src/combinators.ts +++ b/src/combinators.ts @@ -100,7 +100,7 @@ function all any>>( [k in keyof Fns]: UnpackData> } > { - return (async (...args) => { + const callable = (async (...args) => { const results = await Promise.all(fns.map((fn) => composable(fn)(...args))) if (results.some(({ success }) => success === false)) { @@ -113,6 +113,8 @@ function all any>>( [k in keyof Fns]: UnpackData> } > + callable.kind = 'composable' as const + return callable } /** @@ -175,7 +177,7 @@ function sequence< >( ...fns: Fns ): SequenceReturn> { - return (async (...args) => { + const callable = (async (...args) => { const [head, ...tail] = fns const res = await composable(head)(...args) @@ -189,6 +191,8 @@ function sequence< } return success(result) }) as SequenceReturn> + callable.kind = 'composable' as const + return callable } /** @@ -214,12 +218,14 @@ function map any, O>( ...originalInput: Parameters ) => O | Promise, ): Composable<(...args: Parameters) => O> { - return (async (...args) => { + const callable = (async (...args) => { const result = await composable(fn)(...args) if (!result.success) return failure(result.errors) return composable(mapper)(result.data, ...args) }) as Composable<(...args: Parameters) => O> + callable.kind = 'composable' as const + return callable } /** @@ -244,11 +250,13 @@ function mapParameters< fn: Fn, mapper: (...args: NewParameters) => Promise | MapperOutput, ): MapParametersReturn, NewParameters, MapperOutput> { - return (async (...args) => { + const callable = (async (...args) => { const output = await composable(mapper)(...args) if (!output.success) return failure(output.errors) return composable(fn)(...output.data) }) as MapParametersReturn, NewParameters, MapperOutput> + callable.kind = 'composable' as const + return callable } /** @@ -279,7 +287,7 @@ function catchFailure< : Awaited> | UnpackData> : Awaited> | UnpackData> > { - return (async (...args: Parameters) => { + const callable = (async (...args: Parameters) => { const res = await composable(fn)(...args) if (res.success) return success(res.data) return composable(catcher)(res.errors, ...(args as never)) @@ -291,6 +299,8 @@ function catchFailure< : Awaited> | UnpackData> : Awaited> | UnpackData> > + callable.kind = 'composable' as const + return callable } /** @@ -311,7 +321,7 @@ function mapErrors any>( fn: Fn, mapper: (err: Error[]) => Error[] | Promise, ): Composable { - return (async (...args) => { + const callable = (async (...args) => { const res = await composable(fn)(...args) if (res.success) return success(res.data) const mapped = await composable(mapper)(res.errors) @@ -321,6 +331,8 @@ function mapErrors any>( return failure(mapped.errors) } }) as Composable + callable.kind = 'composable' as const + return callable } /** @@ -349,12 +361,16 @@ function trace( ): any>( fn: Fn, ) => Composable { - return ((fn) => async (...args) => { - const originalResult = await composable(fn)(...args) - const traceResult = await composable(traceFn)(originalResult, ...args) - if (traceResult.success) return originalResult + return ((fn) => { + const callable = async (...args: any) => { + const originalResult = await composable(fn)(...args) + const traceResult = await composable(traceFn)(originalResult, ...args) + if (traceResult.success) return originalResult - return failure(traceResult.errors) + return failure(traceResult.errors) + } + callable.kind = 'composable' as const + return callable }) as any>( fn: Fn, ) => Composable @@ -382,16 +398,17 @@ function trace( * ``` */ function branch< - SourceComposable extends Composable, + SourceComposable extends (...args: any[]) => any, Resolver extends ( - o: UnpackData, + o: UnpackData>, ) => Composable | null | Promise, >( cf: SourceComposable, + // TODO: Accept any function as resolver resolver: Resolver, -): BranchReturn { - return (async (...args: Parameters) => { - const result = await cf(...args) +): BranchReturn, Resolver> { + const callable = (async (...args: Parameters) => { + const result = await composable(cf)(...args) if (!result.success) return result return composable(async () => { @@ -399,7 +416,9 @@ function branch< if (typeof nextComposable !== 'function') return result.data return fromSuccess(nextComposable)(result.data) })() - }) as BranchReturn + }) as BranchReturn, Resolver> + ;(callable as any).kind = 'composable' as const + return callable } export { diff --git a/src/constructors.ts b/src/constructors.ts index 383e8a4..955fa26 100644 --- a/src/constructors.ts +++ b/src/constructors.ts @@ -149,7 +149,7 @@ function applySchema( return ( fn: Composable<(input: Input, context: Context) => R>, ): ApplySchemaReturn => { - return ((input?: unknown, context?: unknown) => { + const callable = ((input?: unknown, context?: unknown) => { const ctxResult = (contextSchema ?? alwaysUnknownSchema).safeParse( context, ) @@ -166,6 +166,8 @@ function applySchema( } return fn(result.data as Input, ctxResult.data as Context) }) as ApplySchemaReturn + ;(callable as any).kind = 'composable' as const + return callable } } diff --git a/src/context/combinators.ts b/src/context/combinators.ts index 5ff9d6a..cca27ed 100644 --- a/src/context/combinators.ts +++ b/src/context/combinators.ts @@ -3,10 +3,18 @@ import * as A from '../combinators.ts' import { composable, fromSuccess } from '../constructors.ts' import type { BranchReturn, PipeReturn, SequenceReturn } from './types.ts' +type Composables any>> = { + [K in keyof Fns]: Composable +} + function applyContextToList< Fns extends Array<(input: unknown, context: unknown) => unknown>, >(fns: Fns, context: unknown) { - return fns.map((fn) => (input) => fn(input, context)) as [Composable] + return fns.map((fn) => { + const callable = ((input) => fn(input, context)) as Composable + callable.kind = 'composable' as const + return callable + }) as Composable[] } /** @@ -27,9 +35,16 @@ function applyContextToList< * // ^? ComposableWithSchema<{ aBoolean: boolean }> * ``` */ -function pipe(...fns: Fns): PipeReturn { - return ((input: any, context: any) => - A.pipe(...applyContextToList(fns, context))(input)) as PipeReturn +function pipe any>>( + ...fns: Fns +): PipeReturn> { + const callable = + ((input: any, context: any) => + A.pipe(...applyContextToList(fns, context) as any)(input)) as PipeReturn< + Composables + > + ;(callable as any).kind = 'composable' as const + return callable } /** @@ -47,28 +62,34 @@ function pipe(...fns: Fns): PipeReturn { * ``` */ -function sequence(...fns: Fns): SequenceReturn { - return ((input: any, context: any) => - A.sequence(...applyContextToList(fns, context))( - input, - )) as SequenceReturn +function sequence any>>( + ...fns: Fns +): SequenceReturn> { + const callable = + ((input: any, context: any) => + A.sequence(...applyContextToList(fns, context) as any)( + input, + )) as SequenceReturn> + ;(callable as any).kind = 'composable' as const + return callable } /** * Like branch but preserving the context parameter. */ function branch< - SourceComposable extends Composable, + SourceComposable extends (...args: any[]) => any, Resolver extends ( - o: UnpackData, + o: UnpackData>, ) => Composable | null | Promise, >( cf: SourceComposable, + // TODO: Accept any function as resolver resolver: Resolver, -): BranchReturn { - return (async (...args: Parameters) => { +): BranchReturn, Resolver> { + const callable = (async (...args: Parameters) => { const [input, context] = args - const result = await cf(input, context) + const result = await composable(cf)(input, context) if (!result.success) return result return composable(async () => { @@ -76,7 +97,9 @@ function branch< if (typeof nextFn !== 'function') return result.data return fromSuccess(nextFn)(result.data, context) })() - }) as BranchReturn + }) as BranchReturn, Resolver> + ;(callable as any).kind = 'composable' as const + return callable } export { branch, pipe, sequence } diff --git a/src/context/tests/branch.test.ts b/src/context/tests/branch.test.ts index 04ff69e..36fd66b 100644 --- a/src/context/tests/branch.test.ts +++ b/src/context/tests/branch.test.ts @@ -59,30 +59,30 @@ describe('branch', () => { assertEquals(await d({ id: 1 }), success(6)) }) - // it('should not pipe if the predicate returns null', async () => { - // const a = withSchema(z.object({ id: z.number() }))(({ id }) => ({ - // id: id + 2, - // next: 'multiply', - // })) - // const b = withSchema(z.object({ id: z.number() }))(({ id }) => String(id)) - // const d = context.branch(a, (output) => { - // type _Check = Expect>> - // return output.next === 'multiply' ? null : b - // }) - // type _R = Expect< - // Equal< - // typeof d, - // Composable< - // ( - // input?: unknown, - // context?: unknown, - // ) => string | { id: number; next: string } - // > - // > - // > - - // assertEquals(await d({ id: 1 }), success({ id: 3, next: 'multiply' })) - // }) + it('should not pipe if the predicate returns null', async () => { + const a = withSchema(z.object({ id: z.number() }))(({ id }) => ({ + id: id + 2, + next: 'multiply', + })) + const b = withSchema(z.object({ id: z.number() }))(({ id }) => String(id)) + const d = context.branch(a, (output) => { + type _Check = Expect>> + return output.next === 'multiply' ? null : b + }) + type _R = Expect< + Equal< + typeof d, + Composable< + ( + input?: unknown, + context?: unknown, + ) => string | { id: number; next: string } + > + > + > + + assertEquals(await d({ id: 1 }), success({ id: 3, next: 'multiply' })) + }) it('should use the same context in all composed functions', async () => { const a = composable((_input: unknown, { ctx }: { ctx: number }) => ({ diff --git a/src/context/tests/types.test.ts b/src/context/tests/types.test.ts index 1e46de9..cd35f62 100644 --- a/src/context/tests/types.test.ts +++ b/src/context/tests/types.test.ts @@ -136,31 +136,30 @@ namespace PipeReturn { > } -// namespace BranchReturn { -// type _ = Subject.BranchReturn< -// Composable<(a: number, e?: unknown) => number>, -// (a: number) => Composable<(a: number, e: number) => string> -// > -// // TODO: Fix this test -// // type testCommonCtx = Expect< -// // Equal< -// // Subject.BranchReturn< -// // Composable<(a: number, e?: unknown) => number>, -// // (a: number) => Composable<(a: number, e: number) => string> -// // >, -// // Composable<(a: number, e: number) => string> -// // > -// // > -// // type test = Expect< -// // Equal< -// // Subject.BranchReturn< -// // Composable<(a?: unknown, e?: unknown) => number>, -// // (a: number) => null | Composable<(a?: unknown, e?: unknown) => string> -// // >, -// // Composable<(a?: unknown, e?: unknown) => string | number> -// // > -// // > -// } +namespace BranchReturn { + type _ = Subject.BranchReturn< + Composable<(a: number, e?: unknown) => number>, + (a: number) => Composable<(a: number, e: number) => string> + > + type testCommonCtx = Expect< + Equal< + Subject.BranchReturn< + Composable<(a: number, e?: unknown) => number>, + (a: number) => Composable<(a: number, e: number) => string> + >, + Composable<(a: number, e: number) => string> + > + > + type test = Expect< + Equal< + Subject.BranchReturn< + Composable<(a?: unknown, e?: unknown) => number>, + (a: number) => null | Composable<(a?: unknown, e?: unknown) => string> + >, + Composable<(a?: unknown, e?: unknown) => string | number> + > + > +} namespace GetContext { type test1 = Expect< diff --git a/src/context/types.ts b/src/context/types.ts index 98b47f5..8c6c0c9 100644 --- a/src/context/types.ts +++ b/src/context/types.ts @@ -97,7 +97,9 @@ type BranchContext< ...args: any[] ) => Composable | null | Promise, > = Awaited> extends Composable - ? CommonContext<[SourceComposable, Awaited>]> + ? CommonContext< + [SourceComposable, NonNullable>>] + > : GetContext> type BranchReturn<