From 67538da5f5803d668b0355e6c9f2bb9a403925dd Mon Sep 17 00:00:00 2001 From: krutoo Date: Sat, 21 Mar 2026 20:34:10 +0500 Subject: [PATCH] feat(misc): pipe() added --- scripts/setup.ts | 2 +- src/misc/__test__/pipe.test.ts | 59 ++++++++++++++++++++++++++++++ src/misc/mod.ts | 11 +++--- src/misc/pipe.ts | 61 ++++++++++++++++++++++++++++++++ src/misc/throttle.ts | 4 ++- src/react/use-stable-callback.ts | 5 +-- src/types/functions.ts | 1 + src/types/mod.ts | 1 + 8 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 src/misc/__test__/pipe.test.ts create mode 100644 src/misc/pipe.ts create mode 100644 src/types/functions.ts diff --git a/scripts/setup.ts b/scripts/setup.ts index 801b64c..5d31a23 100644 --- a/scripts/setup.ts +++ b/scripts/setup.ts @@ -20,7 +20,7 @@ const entries = [ enabled: config.values['vscode-typescript'] ?? config.values.vscode, async apply() { await new JsonFile('.vscode/settings.json').setProperty( - 'typescript.tsdk', + 'js/ts.tsdk.path', 'node_modules/typescript/lib', ); }, diff --git a/src/misc/__test__/pipe.test.ts b/src/misc/__test__/pipe.test.ts new file mode 100644 index 0000000..d3145d7 --- /dev/null +++ b/src/misc/__test__/pipe.test.ts @@ -0,0 +1,59 @@ +import { describe, test } from 'node:test'; +import { expect } from '@std/expect'; +import { pipe } from '../pipe.ts'; + +describe('pipe', () => { + test('should work correctly for zero functions', () => { + const flow = pipe(); + const result: void = flow(); + + expect(result).toBe(undefined); + }); + + test('should work correctly for single function', () => { + const flow = pipe(() => 123); + const result: number = flow(); + + expect(result).toBe(123); + }); + + test('should work correctly for two functions', () => { + const flow = pipe( + // + (a: number, b: number) => a + b, + (value: number) => value * 2, + ); + + expect(flow(2, 3)).toBe(10); + }); + + test('should work correctly for two functions', () => { + const flow = pipe( + // + (a: number, b: number) => a + b, + (value: number) => value * 2, + (value: number) => `value is ${value}`, + ); + + const cases: Array<{ input: [number, number]; output: string }> = [ + { + input: [1, 1], + output: 'value is 4', + }, + { + input: [2, 3], + output: 'value is 10', + }, + { + input: [3, 4], + output: 'value is 14', + }, + ]; + + for (const { input, output } of cases) { + const result: string = flow(...input); + + expect(result).toBe(output); + } + }); +}); diff --git a/src/misc/mod.ts b/src/misc/mod.ts index 8dc4e85..bf97d93 100644 --- a/src/misc/mod.ts +++ b/src/misc/mod.ts @@ -1,12 +1,13 @@ export * from './debounce.ts'; +export * from './identity.ts'; export * from './is-object.ts'; export * from './is-shallow-equal.ts'; +export * from './keys.ts'; +export * from './noop.ts'; +export * from './once.ts'; export * from './pairs.ts'; +export * from './pipe.ts'; export * from './plural.ts'; export * from './throttle.ts'; -export * from './wait.ts'; -export * from './noop.ts'; export * from './timer-pool.ts'; -export * from './identity.ts'; -export * from './keys.ts'; -export * from './once.ts'; +export * from './wait.ts'; diff --git a/src/misc/pipe.ts b/src/misc/pipe.ts new file mode 100644 index 0000000..fc92f4c --- /dev/null +++ b/src/misc/pipe.ts @@ -0,0 +1,61 @@ +import type { AnyFunction } from '../mod.ts'; +import { noop } from './noop.ts'; + +export type FunctionPipe = List extends [ + infer F1 extends AnyFunction, + infer F2 extends AnyFunction, + ...infer Rest extends AnyFunction[], +] + ? [ReturnType] extends [Parameters[0]] + ? [F1, ...FunctionPipe<[F2, ...Rest]>] + : never + : List; + +export type FirstItemParameters = List extends [ + infer FirstFn extends AnyFunction, + ...AnyFunction[], +] + ? Parameters + : never; + +export type LastItemReturnType = List extends [ + ...AnyFunction[], + infer LastFn extends AnyFunction, +] + ? ReturnType + : never; + +export function pipe(): () => void; + +export function pipe(func: F): (...args: Parameters) => ReturnType; + +export function pipe( + ...funcs: FunctionPipe & Funcs +): (...args: FirstItemParameters) => LastItemReturnType; + +/** + * Returns function that applies given functions from left to right. + * @param funcs Functions. + * @returns Function. + */ +export function pipe( + ...funcs: FunctionPipe & Funcs +): (...args: FirstItemParameters) => LastItemReturnType { + if (funcs.length === 0) { + return noop as AnyFunction; + } + + if (funcs.length === 1) { + return (...args: any[]) => funcs[0](...args); + } + + return (...args: any[]): any => { + let acc: any = funcs[0]!(...args); + + for (let i = 1; i < funcs.length; i++) { + acc = funcs[i]!(acc); + } + + return acc; + }; +} diff --git a/src/misc/throttle.ts b/src/misc/throttle.ts index 110f32f..4feeb55 100644 --- a/src/misc/throttle.ts +++ b/src/misc/throttle.ts @@ -1,3 +1,5 @@ +import type { AnyFunction } from '../mod.ts'; + export interface ThrottleOptions { leading?: boolean; trailing?: boolean; @@ -10,7 +12,7 @@ export interface ThrottleOptions { * @param options Options. * @returns Throttled function. */ -export function throttle any>( +export function throttle( fn: T, timeout = 0, { leading = true, trailing = false }: ThrottleOptions = {}, diff --git a/src/react/use-stable-callback.ts b/src/react/use-stable-callback.ts index 7475d9f..96faab3 100644 --- a/src/react/use-stable-callback.ts +++ b/src/react/use-stable-callback.ts @@ -1,6 +1,7 @@ import { useRef } from 'react'; +import type { AnyFunction } from '../mod.ts'; -interface HookState any> { +interface HookState { callback: T; stable: (...args: Parameters) => ReturnType; } @@ -15,7 +16,7 @@ interface HookState any> { * @param callback Callback. * @returns Stable callback. */ -export function useStableCallback any>( +export function useStableCallback( callback: T, ): (...args: Parameters) => ReturnType { const stateRef = useRef>(null); diff --git a/src/types/functions.ts b/src/types/functions.ts new file mode 100644 index 0000000..7c015ba --- /dev/null +++ b/src/types/functions.ts @@ -0,0 +1 @@ +export type AnyFunction = (...args: any[]) => any; diff --git a/src/types/mod.ts b/src/types/mod.ts index a247243..af35a9e 100644 --- a/src/types/mod.ts +++ b/src/types/mod.ts @@ -1,2 +1,3 @@ export type * from './distributive-omit.ts'; export type * from './remote-data.ts'; +export type * from './functions.ts';