Skip to content
VersionSize

pipe

The pipe utility performs functional composition from left to right. It takes multiple functions and returns a single function that passes its result from one call to the next, creating a processing pipeline.

Implementation

View Source Code
ts
/** biome-ignore-all lint/suspicious/noExplicitAny: - */

import { isPromise } from '../typed/isPromise';
import type { FnDynamic } from '../types';
import { assert } from './assert';

type FirstParameters<T> = T extends [infer First extends FnDynamic, ...any] ? Parameters<First> : never;
type LastReturnType<T> = T extends [...any, infer Last extends FnDynamic] ? ReturnType<Last> : never;
type PipeReturn<T extends FnDynamic[]> = (
  ...args: FirstParameters<T>
) => LastReturnType<T> extends Promise<any> ? Promise<Awaited<LastReturnType<T>>> : LastReturnType<T>;

/**
 * Pipes multiple functions into a single function. It starts from the leftmost function and proceeds to the right.
 *
 * @example
 * ```ts
 * const add = (x) => x + 2;
 * const multiply = (x) => x * 3;
 * const subtract = (x) => x - 4;
 * const pipedFn = pipe(subtract, multiply, add);
 *
 * pipedFn(5); // ((5-4) * 3) + 2 = 5
 * ```
 *
 * @example
 * ```ts
 * const square = async (x) => x * x;
 * const add = async (x) => x + 2;
 * const pipedFn = pipe(square, add);
 *
 * await pipedFn(4); // (4 * 4) + 2 = 18
 * ```
 *
 * @param fns - List of functions to be piped.
 *
 * @returns A new function that is the pipe of the input functions.
 */
export function pipe<T extends FnDynamic[]>(...fns: T) {
  assert(fns.length > 0, 'pipe requires at least one function', { args: { fns } });

  const firstFn = fns[0];
  const restFns = fns.slice(1);

  return ((...args: FirstParameters<T>) =>
    restFns.reduce((prev, fn) => (isPromise(prev) ? prev.then(fn) : fn(prev)), firstFn(...args))) as PipeReturn<T>;
}

Features

  • Isomorphic: Works in both Browser and Node.js.
  • Async Support: Automatically handles Promises. If any function in the pipe returns a Promise, the final result will be a Promise.
  • Type-safe: Properly infers input and output types through the entire pipeline.
  • Left-to-Right: Executes functions in the order they are provided.

API

ts
function pipe<T extends any[], R>(
  ...fns: [(...args: T) => any, ...Array<(arg: any) => any>, (arg: any) => R]
): (...args: T) => R | Promise<R>;

Parameters

  • ...fns: A sequence of functions to be composed.

Returns

  • A new function that represents the pipeline.

Examples

Synchronous Pipeline

ts
import { pipe } from '@vielzeug/toolkit';

const trim = (s: string) => s.trim();
const capitalize = (s: string) => s.toUpperCase();
const exclaim = (s: string) => `${s}!`;

const process = pipe(trim, capitalize, exclaim);

process('  hello  '); // 'HELLO!'

Asynchronous Pipeline

ts
import { pipe, delay } from '@vielzeug/toolkit';

const fetchUser = async (id: number) => {
  await delay(10);
  return { id, name: 'Alice' };
};

const getDisplayName = (user: { name: string }) => user.name;

const getUserName = pipe(fetchUser, getDisplayName);

await getUserName(1); // 'Alice'

Implementation Notes

  • If only one function is provided, it is returned as-is.
  • Uses reduce internally to chain function calls.
  • Throws TypeError if any provided argument is not a function.

See Also

  • compose: Functional composition from right to left.
  • fp: Wrap functions for better functional programming support.
  • map: Use pipe within a map for complex transformations.