diff --git a/docs/pages/function/pipe.mdx b/docs/pages/function/pipe.mdx new file mode 100644 index 0000000..9a9b19b --- /dev/null +++ b/docs/pages/function/pipe.mdx @@ -0,0 +1,34 @@ +# pipe + +Pipes a value through multiple functions from left to right. Each function receives the output of the previous one. + +### Import + +```typescript copy +import { pipe } from '@fullstacksjs/toolbox'; +``` + +### Signature + +```typescript copy +function pipe(value: A, ...fns: Function[]): B; +``` + +### Examples + +```typescript copy +const add = (x: number) => x + 1; +const double = (x: number) => x * 2; +const toString = (x: number) => x.toString(); + +const result = pipe(5, add, double, toString); +console.log(result); // ((5 + 1) * 2).toString() => "12" + + +const split = (str: string) => str.split(' '); +const getLength = (arr: string[]) => arr.length; +const isEven = (num: number) => num % 2 === 0; + +const hasEvenWordCount = pipe('hello world from typescript', split, getLength, isEven); +console.log(hasEvenWordCount); // true (4 words) +``` \ No newline at end of file diff --git a/src/function/index.ts b/src/function/index.ts index 6d6ad37..9288199 100644 --- a/src/function/index.ts +++ b/src/function/index.ts @@ -4,6 +4,7 @@ export { debounce } from './debounce.ts'; export { formatSeconds } from './formatSeconds.ts'; export { noop } from './noop.ts'; export { not } from './not.ts'; +export { pipe } from './pipe.ts'; export { sleep } from './sleep.ts'; export { throttle } from './throttle.ts'; export { tryOr } from './tryOr.ts'; diff --git a/src/function/pipe.spec.ts b/src/function/pipe.spec.ts new file mode 100644 index 0000000..24edfbf --- /dev/null +++ b/src/function/pipe.spec.ts @@ -0,0 +1,53 @@ +import { pipe } from './pipe'; + +describe('pipe', () => { + it('should pipe value through functions from left to right', () => { + const add = (x: number) => x + 1; + const multiply = (x: number) => x * 2; + const result = pipe(5, add, multiply); + + expect(result).toBe(12); + }); + + it('should handle single function', () => { + const square = (x: number) => x * x; + const result = pipe(4, square); + + expect(result).toBe(16); + }); + + it('should work with same-type functions', () => { + const trim = (s: string) => s.trim(); + const toUpper = (s: string) => s.toUpperCase(); + const result = pipe(' hey ', trim, toUpper); + + expect(result).toBe('HEY'); + }); + + it('should work with different types', () => { + const toNumber = (s: string) => Number.parseInt(s, 10); + const double = (n: number) => n * 2; + const toString = (n: number) => n.toString(); + const result = pipe('5', toNumber, double, toString); + + expect(result).toBe('10'); + }); + + it('should pipe through 3+ functions', () => { + const add1 = (x: number) => x + 1; + const multiply2 = (x: number) => x * 2; + const subtract3 = (x: number) => x - 3; + const result = pipe(5, add1, multiply2, subtract3); + + expect(result).toBe(9); // ((5 + 1) * 2) - 3 = 9 + }); + + it('should preserve type safety across transformations', () => { + const split = (s: string) => s.split(','); + const count = (arr: string[]) => arr.length; + const isEven = (n: number) => n % 2 === 0; + + expect(pipe('a,b,c', split, count, isEven)).toBe(false); + expect(pipe('a,b,c,d', split, count, isEven)).toBe(true); + }); +}); diff --git a/src/function/pipe.ts b/src/function/pipe.ts new file mode 100644 index 0000000..644cf0c --- /dev/null +++ b/src/function/pipe.ts @@ -0,0 +1,66 @@ +type UnknownFunction = (arg: unknown) => unknown; + +/** + * Pipes a value through a series of functions, applying each function to the result of the previous one. + * The value flows from left to right through the function pipeline. + * + * @template A - The type of the initial value + * @template B - The type after the first transformation + * @param {A} value - The initial value to pipe through the functions + * @param {...Function} fns - The functions to apply in sequence + * @returns {B} The final transformed value + * + * @example + * + * const add = (x: number) => x + 1; + * const double = (x: number) => x * 2; + * const toString = (x: number) => x.toString(); + * + * const result = pipe(5, add, double, toString); + * console.log(result); // ((5 + 1) * 2).toString() => "12" + * + * @example + * + * const split = (str: string) => str.split(' '); + * const getLength = (arr: string[]) => arr.length; + * const isEven = (num: number) => num % 2 === 0; + * + * const hasEvenWordCount = pipe('hello world from typescript', split, getLength, isEven); + * console.log(hasEvenWordCount); // true (4 words) + */ +export function pipe(value: A, ab: (a: A) => B): B; +export function pipe(value: A, ab: (a: A) => B, bc: (b: B) => C): C; +export function pipe( + value: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, +): D; +export function pipe( + value: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, +): E; +export function pipe( + value: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, +): F; +export function pipe( + value: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, +): G; + +export function pipe(value: unknown, ...fns: UnknownFunction[]): unknown { + return fns.reduce((acc, fn) => fn(acc), value); +}