diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 7e8acac..1fdc05e 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,8 @@ # These are supported funding model platforms -github: [lambdalisue] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +github: [ + lambdalisue, +] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username diff --git a/README.md b/README.md index b84978a..8e065e9 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ inference and type checking of the operator functions. Pipe a value through a series of operator functions. ```ts -import { pipe } from "@core/pipe"; +import { pipe } from "@core/pipe/pipe"; const result = pipe( 1, @@ -36,7 +36,7 @@ Or use `async` module to pipe a value through a series of asynchronous operator functions. ```ts -import { pipe } from "@core/pipe/async"; +import { pipe } from "@core/pipe/async/pipe"; const result = await pipe( 1, @@ -47,6 +47,65 @@ const result = await pipe( console.log(result); // "4" ``` +If you want to create a new function that composes multiple operators, use +`compose` like below. + +```ts +import { compose } from "@core/pipe/compose"; + +const operator = compose( + (v: number) => v + 1, // The first operator must be typed explicitly + (v) => v * 2, // inferred as (v: number) => number + (v) => v.toString(), // inferred as (v: number) => string +); +console.log(operator(1)); // "4" +``` + +Or use `async` module to compose multiple asynchronous operators. + +```ts +import { compose } from "@core/pipe/async/compose"; + +const operator = compose( + (v: number) => Promise.resolve(v + 1), // The first operator must be typed explicitly + (v) => Promise.resolve(v * 2), // inferred as (v: number) => number | Promise + (v) => Promise.resolve(v.toString()), // inferred as (v: number) => string | Promise +); +console.log(await operator(1)); // "4" +``` + +## Difference + +The `pipe` function in the root module is equivalent to function calls without +`await` like below. + +```ts +import { pipe } from "@core/pipe/pipe"; + +const a = (v: unknown) => v; +const b = (v: unknown) => v; +const c = (v: unknown) => v; + +// Equivalent +console.log(pipe(1, a, b, c)); // 1 +console.log(c(b(a(1)))); // 1 +``` + +The `pipe` function in the `async` module is equivalent to function calls with +`await` like below. + +```ts +import { pipe } from "@core/pipe/async/pipe"; + +const a = (v: unknown) => Promise.resolve(v); +const b = (v: unknown) => Promise.resolve(v); +const c = (v: unknown) => Promise.resolve(v); + +// Equivalent +console.log(await pipe(1, a, b, c)); // 1 +console.log(await c(await b(await a(1)))); // 1 +``` + ## License The code follows MIT license written in [LICENSE](./LICENSE). Contributors need diff --git a/_common.ts b/_common.ts new file mode 100644 index 0000000..a747774 --- /dev/null +++ b/_common.ts @@ -0,0 +1,8 @@ +import type { Operator } from "./operator.ts"; + +/** + * @internal + */ +export type LastOperatorReturn[]> = + T extends [...Operator[], Operator] ? R + : never; diff --git a/async/_common.ts b/async/_common.ts new file mode 100644 index 0000000..6e86efd --- /dev/null +++ b/async/_common.ts @@ -0,0 +1,10 @@ +import type { AsyncOperator } from "./operator.ts"; + +/** + * @internal + */ +export type LastAsyncOperatorReturn< + T extends AsyncOperator[], +> = T extends + [...AsyncOperator[], AsyncOperator] ? R + : never; diff --git a/async/compose.ts b/async/compose.ts new file mode 100644 index 0000000..e069659 --- /dev/null +++ b/async/compose.ts @@ -0,0 +1,470 @@ +import type { AsyncOperator } from "./operator.ts"; +import type { LastAsyncOperatorReturn } from "./_common.ts"; + +/** + * Composes a sequence of operators to create a new function. + * Supports type inference for operators and the return type of the final operator. + * + * > [!NOTE] + * > + * > If more than 20 operators are used, their types default to `AsyncOperator`, + * > requiring explicit type annotations. + * + * @param operators - A sequence of operators to apply to the value. + * @returns A function that processes the value through all operators and returns the final result. + * + * @example + * ```ts + * import { compose } from "@core/pipe/async"; + * + * const result = await compose( + * (v: number) => Promise.resolve(v + 1), // The first operator requires an explicit type + * (v) => Promise.resolve(v * 2), // inferred as (v: number) => number | Promise + * (v) => Promise.resolve(v.toString()), // inferred as (v: number) => string | Promise + * )(1); + * console.log(result); // "4" + * ``` + */ +export function compose( + o01: AsyncOperator, +): AsyncOperator; +export function compose( + o01: AsyncOperator, + o02: AsyncOperator, +): AsyncOperator; +export function compose( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, +): AsyncOperator; +export function compose( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, +): AsyncOperator; +export function compose( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, +): AsyncOperator; +export function compose( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, +): AsyncOperator; +export function compose( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, +): AsyncOperator; +export function compose( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, +): AsyncOperator; +export function compose( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, +): AsyncOperator; +export function compose( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, +): AsyncOperator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, +>( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, +): AsyncOperator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, +>( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, +): AsyncOperator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, +>( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, +): AsyncOperator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, +>( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, +): AsyncOperator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, +>( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, + o15: AsyncOperator, +): AsyncOperator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, +>( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, + o15: AsyncOperator, + o16: AsyncOperator, +): AsyncOperator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, +>( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, + o15: AsyncOperator, + o16: AsyncOperator, + o17: AsyncOperator, +): AsyncOperator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, +>( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, + o15: AsyncOperator, + o16: AsyncOperator, + o17: AsyncOperator, + o18: AsyncOperator, +): AsyncOperator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, + T19, +>( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, + o15: AsyncOperator, + o16: AsyncOperator, + o17: AsyncOperator, + o18: AsyncOperator, + o19: AsyncOperator, +): AsyncOperator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, + T19, + T20, +>( + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, + o15: AsyncOperator, + o16: AsyncOperator, + o17: AsyncOperator, + o18: AsyncOperator, + o19: AsyncOperator, + o20: AsyncOperator, +): AsyncOperator; + +export function compose< + V, + // deno-lint-ignore no-explicit-any + AsyncOperators extends AsyncOperator[], +>( + ...operators: AsyncOperators +): AsyncOperator>; + +// deno-lint-ignore no-explicit-any +export function compose(...operators: AsyncOperator[]) { + return async (value: V) => { + return await operators.reduce( + async (result, next) => next(await result), + Promise.resolve(value), + ); + }; +} diff --git a/async/compose_test.ts b/async/compose_test.ts new file mode 100644 index 0000000..83dacd7 --- /dev/null +++ b/async/compose_test.ts @@ -0,0 +1,158 @@ +import { test } from "@cross/test"; +import { assertEquals } from "@std/assert"; +import { assertType, type IsExact } from "@std/testing/types"; +import { compose } from "./compose.ts"; + +await test("compose with no operators should return an identity function", async () => { + assertEquals(await compose()(1), 1); +}); + +await test("compose with one operator should return the operator", async () => { + assertEquals(await compose((v: number) => Promise.resolve(v * 2))(1), 2); +}); + +await test("compose with one operator requires explicity type annotation", () => { + compose((v) => { + assertType>(true); + }); + compose((v: number) => { + assertType>(true); + return v.toString(); + }); +}); + +await test("compose with two operators should return a composed operator", async () => { + assertEquals( + await compose( + (v: number) => Promise.resolve(v * 2), + (v) => Promise.resolve(v * 2), + )(1), + 4, + ); +}); + +await test("compose with two operators should resolve the type properly", () => { + compose((v: number) => { + assertType>(true); + return v.toString(); + }, (v) => { + assertType>(true); + return v.length; + }); +}); + +await test("compose with three operators should return a composed operator", async () => { + assertEquals( + await compose( + (v: number) => Promise.resolve(v * 2), + (v) => Promise.resolve(v * 2), + (v) => Promise.resolve(v * 2), + )(1), + 8, + ); +}); + +await test("compose with three operators should resolve the type properly", () => { + compose((v: number) => { + assertType>(true); + return v.toString(); + }, (v) => { + assertType>(true); + return v.length; + }, (v) => { + assertType>(true); + return v.toString(); + }); +}); + +await test("compose with twenty operators should return a composed operator", async () => { + assertEquals( + await compose(...Array(20).fill((v: number) => Promise.resolve(v * 2)))(1), + 2 ** 20, + ); +}); + +await test("compose with twenty operators should resolve the type properly", () => { + compose( + (v: number) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + ); +}); diff --git a/async/mod.ts b/async/mod.ts index dc62e19..d32b5d9 100644 --- a/async/mod.ts +++ b/async/mod.ts @@ -1,490 +1,3 @@ -/** - * AsyncOperator function that takes a value and returns a new value. - */ -export type AsyncOperator = (v: A) => B | Promise; - -/** - * @internal - */ -type LastAsyncOperatorReturn< - T extends AsyncOperator[], -> = T extends - [...AsyncOperator[], AsyncOperator] ? R - : never; - -/** - * Pipes a value through a series of asynchronous operator functions. - * Supports type inference for both the operator functions and the return value of the final operator. - * - * > [!NOTE] - * > - * > If the number of operators exceeds 20, the operator functions' types will default to - * > `AsyncOperator`, requiring explicit type annotations. - * - * @param value - The initial value to be processed through the operators. - * @param operators - A sequence of functions to apply to the value. - * @returns The final value after being processed through all the operators. - * - * @example - * ```ts - * import { pipe } from "@core/pipe/async"; - * - * const result = await pipe( - * 1, - * (v) => Promise.resolve(v + 1), // inferred as (v: number) => number | Promise - * (v) => Promise.resolve(v * 2), // inferred as (v: number) => number | Promise - * (v) => Promise.resolve(v.toString()), // inferred as (v: number) => string | Promise - * ); - * console.log(result); // "4" - * ``` - */ -export function pipe(value: V): Promise; -export function pipe( - value: V, - o01: AsyncOperator, -): Promise; -export function pipe( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, -): Promise; -export function pipe( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, -): Promise; -export function pipe( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, -): Promise; -export function pipe( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, -): Promise; -export function pipe( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, -): Promise; -export function pipe( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, -): Promise; -export function pipe( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, -): Promise; -export function pipe( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, - o09: AsyncOperator, -): Promise; -export function pipe( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, - o09: AsyncOperator, - o10: AsyncOperator, -): Promise; -export function pipe( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, - o09: AsyncOperator, - o10: AsyncOperator, - o11: AsyncOperator, -): Promise; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, ->( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, - o09: AsyncOperator, - o10: AsyncOperator, - o11: AsyncOperator, - o12: AsyncOperator, -): Promise; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, ->( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, - o09: AsyncOperator, - o10: AsyncOperator, - o11: AsyncOperator, - o12: AsyncOperator, - o13: AsyncOperator, -): Promise; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, ->( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, - o09: AsyncOperator, - o10: AsyncOperator, - o11: AsyncOperator, - o12: AsyncOperator, - o13: AsyncOperator, - o14: AsyncOperator, -): Promise; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, - T15, ->( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, - o09: AsyncOperator, - o10: AsyncOperator, - o11: AsyncOperator, - o12: AsyncOperator, - o13: AsyncOperator, - o14: AsyncOperator, - o15: AsyncOperator, -): Promise; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, - T15, - T16, ->( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, - o09: AsyncOperator, - o10: AsyncOperator, - o11: AsyncOperator, - o12: AsyncOperator, - o13: AsyncOperator, - o14: AsyncOperator, - o15: AsyncOperator, - o16: AsyncOperator, -): Promise; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, - T15, - T16, - T17, ->( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, - o09: AsyncOperator, - o10: AsyncOperator, - o11: AsyncOperator, - o12: AsyncOperator, - o13: AsyncOperator, - o14: AsyncOperator, - o15: AsyncOperator, - o16: AsyncOperator, - o17: AsyncOperator, -): Promise; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, - T15, - T16, - T17, - T18, ->( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, - o09: AsyncOperator, - o10: AsyncOperator, - o11: AsyncOperator, - o12: AsyncOperator, - o13: AsyncOperator, - o14: AsyncOperator, - o15: AsyncOperator, - o16: AsyncOperator, - o17: AsyncOperator, - o18: AsyncOperator, -): Promise; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, - T15, - T16, - T17, - T18, - T19, ->( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, - o09: AsyncOperator, - o10: AsyncOperator, - o11: AsyncOperator, - o12: AsyncOperator, - o13: AsyncOperator, - o14: AsyncOperator, - o15: AsyncOperator, - o16: AsyncOperator, - o17: AsyncOperator, - o18: AsyncOperator, - o19: AsyncOperator, -): Promise; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, - T15, - T16, - T17, - T18, - T19, - T20, ->( - value: V, - o01: AsyncOperator, - o02: AsyncOperator, - o03: AsyncOperator, - o04: AsyncOperator, - o05: AsyncOperator, - o06: AsyncOperator, - o07: AsyncOperator, - o08: AsyncOperator, - o09: AsyncOperator, - o10: AsyncOperator, - o11: AsyncOperator, - o12: AsyncOperator, - o13: AsyncOperator, - o14: AsyncOperator, - o15: AsyncOperator, - o16: AsyncOperator, - o17: AsyncOperator, - o18: AsyncOperator, - o19: AsyncOperator, - o20: AsyncOperator, -): Promise; - -// deno-lint-ignore no-explicit-any -export function pipe[]>( - value: V, - ...operators: AsyncOperators -): Promise>; - -export async function pipe( - value: V, - // deno-lint-ignore no-explicit-any - ...operators: AsyncOperator[] -) { - return await operators.reduce( - async (result, next) => next(await result), - Promise.resolve(value), - ); -} +export type { AsyncOperator } from "./operator.ts"; +export * from "./compose.ts"; +export * from "./pipe.ts"; diff --git a/async/operator.ts b/async/operator.ts new file mode 100644 index 0000000..0bb9af2 --- /dev/null +++ b/async/operator.ts @@ -0,0 +1,4 @@ +/** + * AsyncOperator function that takes a value and returns a new value. + */ +export type AsyncOperator = (v: A) => B | Promise; diff --git a/async/pipe.ts b/async/pipe.ts new file mode 100644 index 0000000..e32c51f --- /dev/null +++ b/async/pipe.ts @@ -0,0 +1,479 @@ +import type { AsyncOperator } from "./operator.ts"; +import type { LastAsyncOperatorReturn } from "./_common.ts"; + +/** + * Pipes a value through a series of asynchronous operator functions. + * Supports type inference for both the operator functions and the return value of the final operator. + * + * > [!NOTE] + * > + * > If the number of operators exceeds 20, the operator functions' types will default to + * > `AsyncOperator`, requiring explicit type annotations. + * + * @param value - The initial value to be processed through the operators. + * @param operators - A sequence of functions to apply to the value. + * @returns The final value after being processed through all the operators. + * + * @example + * ```ts + * import { pipe } from "@core/pipe/async"; + * + * const result = await pipe( + * 1, + * (v) => Promise.resolve(v + 1), // inferred as (v: number) => number | Promise + * (v) => Promise.resolve(v * 2), // inferred as (v: number) => number | Promise + * (v) => Promise.resolve(v.toString()), // inferred as (v: number) => string | Promise + * ); + * console.log(result); // "4" + * ``` + */ +export function pipe(value: V): Promise; +export function pipe( + value: V, + o01: AsyncOperator, +): Promise; +export function pipe( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, +): Promise; +export function pipe( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, +): Promise; +export function pipe( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, +): Promise; +export function pipe( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, +): Promise; +export function pipe( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, +): Promise; +export function pipe( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, +): Promise; +export function pipe( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, +): Promise; +export function pipe( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, +): Promise; +export function pipe( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, +): Promise; +export function pipe( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, +): Promise; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, +>( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, +): Promise; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, +>( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, +): Promise; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, +>( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, +): Promise; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, +>( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, + o15: AsyncOperator, +): Promise; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, +>( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, + o15: AsyncOperator, + o16: AsyncOperator, +): Promise; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, +>( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, + o15: AsyncOperator, + o16: AsyncOperator, + o17: AsyncOperator, +): Promise; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, +>( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, + o15: AsyncOperator, + o16: AsyncOperator, + o17: AsyncOperator, + o18: AsyncOperator, +): Promise; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, + T19, +>( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, + o15: AsyncOperator, + o16: AsyncOperator, + o17: AsyncOperator, + o18: AsyncOperator, + o19: AsyncOperator, +): Promise; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, + T19, + T20, +>( + value: V, + o01: AsyncOperator, + o02: AsyncOperator, + o03: AsyncOperator, + o04: AsyncOperator, + o05: AsyncOperator, + o06: AsyncOperator, + o07: AsyncOperator, + o08: AsyncOperator, + o09: AsyncOperator, + o10: AsyncOperator, + o11: AsyncOperator, + o12: AsyncOperator, + o13: AsyncOperator, + o14: AsyncOperator, + o15: AsyncOperator, + o16: AsyncOperator, + o17: AsyncOperator, + o18: AsyncOperator, + o19: AsyncOperator, + o20: AsyncOperator, +): Promise; + +// deno-lint-ignore no-explicit-any +export function pipe[]>( + value: V, + ...operators: AsyncOperators +): Promise>; + +export async function pipe( + value: V, + // deno-lint-ignore no-explicit-any + ...operators: AsyncOperator[] +) { + return await operators.reduce( + async (result, next) => next(await result), + Promise.resolve(value), + ); +} diff --git a/async/mod_test.ts b/async/pipe_test.ts similarity index 99% rename from async/mod_test.ts rename to async/pipe_test.ts index f640b13..c1b65e2 100644 --- a/async/mod_test.ts +++ b/async/pipe_test.ts @@ -1,7 +1,7 @@ import { test } from "@cross/test"; import { assertEquals } from "@std/assert"; import { assertType, type IsExact } from "@std/testing/types"; -import { pipe } from "./mod.ts"; +import { pipe } from "./pipe.ts"; await test("pipe with no operators should return the input", async () => { assertEquals(await pipe(1), 1); diff --git a/compose.ts b/compose.ts new file mode 100644 index 0000000..038cfca --- /dev/null +++ b/compose.ts @@ -0,0 +1,467 @@ +import type { Operator } from "./operator.ts"; +import type { LastOperatorReturn } from "./_common.ts"; + +/** + * Composes a sequence of operators to create a new function. + * Supports type inference for operators and the return type of the final operator. + * + * > [!NOTE] + * > + * > If more than 20 operators are used, their types default to `Operator`, + * > requiring explicit type annotations. + * + * @param operators - A sequence of operators to apply to the value. + * @returns A function that processes the value through all operators and returns the final result. + * + * @example + * ```ts + * import { compose } from "@core/pipe"; + * + * const result = compose( + * (v: number) => v + 1, // The first operator requires an explicit type + * (v) => v * 2, // inferred as (v: number) => number + * (v) => v.toString(), // inferred as (v: number) => string + * )(1); + * console.log(result); // "4" + * ``` + */ +export function compose( + o01: Operator, +): Operator; +export function compose( + o01: Operator, + o02: Operator, +): Operator; +export function compose( + o01: Operator, + o02: Operator, + o03: Operator, +): Operator; +export function compose( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, +): Operator; +export function compose( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, +): Operator; +export function compose( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, +): Operator; +export function compose( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, +): Operator; +export function compose( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, +): Operator; +export function compose( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, +): Operator; +export function compose( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, +): Operator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, +>( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, +): Operator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, +>( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, +): Operator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, +>( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, +): Operator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, +>( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, +): Operator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, +>( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, + o15: Operator, +): Operator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, +>( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, + o15: Operator, + o16: Operator, +): Operator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, +>( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, + o15: Operator, + o16: Operator, + o17: Operator, +): Operator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, +>( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, + o15: Operator, + o16: Operator, + o17: Operator, + o18: Operator, +): Operator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, + T19, +>( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, + o15: Operator, + o16: Operator, + o17: Operator, + o18: Operator, + o19: Operator, +): Operator; +export function compose< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, + T19, + T20, +>( + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, + o15: Operator, + o16: Operator, + o17: Operator, + o18: Operator, + o19: Operator, + o20: Operator, +): Operator; + +// deno-lint-ignore no-explicit-any +export function compose[]>( + ...operators: Operators +): Operator>; + +// deno-lint-ignore no-explicit-any +export function compose(...operators: Operator[]) { + return (value: V) => { + return operators.reduce( + (result, next) => next(result), + value, + ); + }; +} diff --git a/compose_test.ts b/compose_test.ts new file mode 100644 index 0000000..e08042a --- /dev/null +++ b/compose_test.ts @@ -0,0 +1,142 @@ +import { test } from "@cross/test"; +import { assertEquals } from "@std/assert"; +import { assertType, type IsExact } from "@std/testing/types"; +import { compose } from "./compose.ts"; + +await test("compose with no operators should return an identity function", () => { + assertEquals(compose()(1), 1); +}); + +await test("compose with one operator should return the operator", () => { + assertEquals(compose((v: number) => v * 2)(1), 2); +}); + +await test("compose with one operator requires explicity type annotation", () => { + compose((v) => { + assertType>(true); + }); + compose((v: number) => { + assertType>(true); + return v.toString(); + }); +}); + +await test("compose with two operators should return a composed operator", () => { + assertEquals(compose((v: number) => v * 2, (v) => v * 2)(1), 4); +}); + +await test("compose with two operators should resolve the type properly", () => { + compose((v: number) => { + assertType>(true); + return v.toString(); + }, (v) => { + assertType>(true); + return v.length; + }); +}); + +await test("compose with three operators should return a composed operator", () => { + assertEquals(compose((v: number) => v * 2, (v) => v * 2, (v) => v * 2)(1), 8); +}); + +await test("compose with three operators should resolve the type properly", () => { + compose((v: number) => { + assertType>(true); + return v.toString(); + }, (v) => { + assertType>(true); + return v.length; + }, (v) => { + assertType>(true); + return v.toString(); + }); +}); + +await test("compose with twenty operators should return a composed operator", () => { + assertEquals(compose(...Array(20).fill((v: number) => v * 2))(1), 2 ** 20); +}); + +await test("compose with twenty operators should resolve the type properly", () => { + compose( + (v: number) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + (v) => { + assertType>(true); + return v; + }, + ); +}); diff --git a/deno.jsonc b/deno.jsonc index 5a3b249..c65f81f 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -3,7 +3,13 @@ "version": "0.0.0", "exports": { ".": "./mod.ts", - "./async": "./async/mod.ts" + "./async": "./async/mod.ts", + "./async/compose": "./async/compose.ts", + "./async/operator": "./async/operator.ts", + "./async/pipe": "./async/pipe.ts", + "./compose": "./compose.ts", + "./operator": "./operator.ts", + "./pipe": "./pipe.ts" }, "exclude": [ ".coverage/**" diff --git a/mod.ts b/mod.ts index 0e0c09c..083e9a6 100644 --- a/mod.ts +++ b/mod.ts @@ -1,482 +1,3 @@ -/** - * Operator function that takes a value and returns a new value. - */ -export type Operator = (v: A) => B; - -/** - * @internal - */ -type LastOperatorReturn[]> = T extends - [...Operator[], Operator] ? R - : never; - -/** - * Pipes a value through a series of operator functions. - * Supports type inference for both the operator functions and the return value of the final operator. - * - * > [!NOTE] - * > - * > If the number of operators exceeds 20, the operator functions' types will default to - * > `Operator`, requiring explicit type annotations. - * - * @param value - The initial value to be processed through the operators. - * @param operators - A sequence of functions to apply to the value. - * @returns The final value after being processed through all the operators. - * - * @example - * ```ts - * import { pipe } from "@core/pipe"; - * - * const result = pipe( - * 1, - * (v) => v + 1, // inferred as (v: number) => number - * (v) => v * 2, // inferred as (v: number) => number - * (v) => v.toString(), // inferred as (v: number) => string - * ); - * console.log(result); // "4" - * ``` - */ -export function pipe(value: V): V; -export function pipe(value: V, o01: Operator): T01; -export function pipe( - value: V, - o01: Operator, - o02: Operator, -): T02; -export function pipe( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, -): T03; -export function pipe( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, -): T04; -export function pipe( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, -): T05; -export function pipe( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, -): T06; -export function pipe( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, -): T07; -export function pipe( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, -): T08; -export function pipe( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, - o09: Operator, -): T09; -export function pipe( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, - o09: Operator, - o10: Operator, -): T10; -export function pipe( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, - o09: Operator, - o10: Operator, - o11: Operator, -): T11; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, ->( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, - o09: Operator, - o10: Operator, - o11: Operator, - o12: Operator, -): T12; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, ->( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, - o09: Operator, - o10: Operator, - o11: Operator, - o12: Operator, - o13: Operator, -): T13; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, ->( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, - o09: Operator, - o10: Operator, - o11: Operator, - o12: Operator, - o13: Operator, - o14: Operator, -): T14; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, - T15, ->( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, - o09: Operator, - o10: Operator, - o11: Operator, - o12: Operator, - o13: Operator, - o14: Operator, - o15: Operator, -): T15; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, - T15, - T16, ->( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, - o09: Operator, - o10: Operator, - o11: Operator, - o12: Operator, - o13: Operator, - o14: Operator, - o15: Operator, - o16: Operator, -): T16; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, - T15, - T16, - T17, ->( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, - o09: Operator, - o10: Operator, - o11: Operator, - o12: Operator, - o13: Operator, - o14: Operator, - o15: Operator, - o16: Operator, - o17: Operator, -): T17; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, - T15, - T16, - T17, - T18, ->( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, - o09: Operator, - o10: Operator, - o11: Operator, - o12: Operator, - o13: Operator, - o14: Operator, - o15: Operator, - o16: Operator, - o17: Operator, - o18: Operator, -): T18; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, - T15, - T16, - T17, - T18, - T19, ->( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, - o09: Operator, - o10: Operator, - o11: Operator, - o12: Operator, - o13: Operator, - o14: Operator, - o15: Operator, - o16: Operator, - o17: Operator, - o18: Operator, - o19: Operator, -): T19; -export function pipe< - V, - T01, - T02, - T03, - T04, - T05, - T06, - T07, - T08, - T09, - T10, - T11, - T12, - T13, - T14, - T15, - T16, - T17, - T18, - T19, - T20, ->( - value: V, - o01: Operator, - o02: Operator, - o03: Operator, - o04: Operator, - o05: Operator, - o06: Operator, - o07: Operator, - o08: Operator, - o09: Operator, - o10: Operator, - o11: Operator, - o12: Operator, - o13: Operator, - o14: Operator, - o15: Operator, - o16: Operator, - o17: Operator, - o18: Operator, - o19: Operator, - o20: Operator, -): T20; - -// deno-lint-ignore no-explicit-any -export function pipe[]>( - value: V, - ...operators: Operators -): LastOperatorReturn; - -// deno-lint-ignore no-explicit-any -export function pipe(value: V, ...operators: Operator[]) { - return operators.reduce( - (result, next) => next(result), - value, - ); -} +export type { Operator } from "./operator.ts"; +export * from "./compose.ts"; +export * from "./pipe.ts"; diff --git a/operator.ts b/operator.ts new file mode 100644 index 0000000..63af12d --- /dev/null +++ b/operator.ts @@ -0,0 +1,4 @@ +/** + * Operator function that takes a value and returns a new value. + */ +export type Operator = (v: A) => B; diff --git a/pipe.ts b/pipe.ts new file mode 100644 index 0000000..67d7f97 --- /dev/null +++ b/pipe.ts @@ -0,0 +1,473 @@ +import type { Operator } from "./operator.ts"; +import type { LastOperatorReturn } from "./_common.ts"; + +/** + * Pipes a value through a series of operator functions. + * Supports type inference for both the operator functions and the return value of the final operator. + * + * > [!NOTE] + * > + * > If the number of operators exceeds 20, the operator functions' types will default to + * > `Operator`, requiring explicit type annotations. + * + * @param value - The initial value to be processed through the operators. + * @param operators - A sequence of functions to apply to the value. + * @returns The final value after being processed through all the operators. + * + * @example + * ```ts + * import { pipe } from "@core/pipe"; + * + * const result = pipe( + * 1, + * (v) => v + 1, // inferred as (v: number) => number + * (v) => v * 2, // inferred as (v: number) => number + * (v) => v.toString(), // inferred as (v: number) => string + * ); + * console.log(result); // "4" + * ``` + */ +export function pipe(value: V): V; +export function pipe(value: V, o01: Operator): T01; +export function pipe( + value: V, + o01: Operator, + o02: Operator, +): T02; +export function pipe( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, +): T03; +export function pipe( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, +): T04; +export function pipe( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, +): T05; +export function pipe( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, +): T06; +export function pipe( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, +): T07; +export function pipe( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, +): T08; +export function pipe( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, +): T09; +export function pipe( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, +): T10; +export function pipe( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, +): T11; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, +>( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, +): T12; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, +>( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, +): T13; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, +>( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, +): T14; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, +>( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, + o15: Operator, +): T15; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, +>( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, + o15: Operator, + o16: Operator, +): T16; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, +>( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, + o15: Operator, + o16: Operator, + o17: Operator, +): T17; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, +>( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, + o15: Operator, + o16: Operator, + o17: Operator, + o18: Operator, +): T18; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, + T19, +>( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, + o15: Operator, + o16: Operator, + o17: Operator, + o18: Operator, + o19: Operator, +): T19; +export function pipe< + V, + T01, + T02, + T03, + T04, + T05, + T06, + T07, + T08, + T09, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, + T19, + T20, +>( + value: V, + o01: Operator, + o02: Operator, + o03: Operator, + o04: Operator, + o05: Operator, + o06: Operator, + o07: Operator, + o08: Operator, + o09: Operator, + o10: Operator, + o11: Operator, + o12: Operator, + o13: Operator, + o14: Operator, + o15: Operator, + o16: Operator, + o17: Operator, + o18: Operator, + o19: Operator, + o20: Operator, +): T20; + +// deno-lint-ignore no-explicit-any +export function pipe[]>( + value: V, + ...operators: Operators +): LastOperatorReturn; + +// deno-lint-ignore no-explicit-any +export function pipe(value: V, ...operators: Operator[]) { + return operators.reduce( + (result, next) => next(result), + value, + ); +} diff --git a/mod_test.ts b/pipe_test.ts similarity index 99% rename from mod_test.ts rename to pipe_test.ts index 339cf83..35fce9f 100644 --- a/mod_test.ts +++ b/pipe_test.ts @@ -1,7 +1,7 @@ import { test } from "@cross/test"; import { assertEquals } from "@std/assert"; import { assertType, type IsExact } from "@std/testing/types"; -import { pipe } from "./mod.ts"; +import { pipe } from "./pipe.ts"; await test("pipe with no operators should return the input", () => { assertEquals(pipe(1), 1);