@thuum/piper
Installation
Section titled “Installation”npm install @thuum/piperOverview
Section titled “Overview”@thuum/piper provides utilities for functional programming with support for both synchronous and asynchronous operations:
pipe(value)— Transform a value through a chain of synchronous functionsbuild<T>()— Compose synchronous functions into a reusable transformation pipelinepipe(value)(from@thuum/piper/async) — Transform values through chains that support both sync and async functionsbuild<T>()(from@thuum/piper/async) — Compose functions (sync or async) into a reusable async pipeline
Sync and async utilities share the same API shape but are imported from different subpaths:
import { pipe, build } from "@thuum/piper"; // syncimport { pipe, build } from "@thuum/piper/async"; // asyncSynchronous API
Section titled “Synchronous API”pipe(value)
Section titled “pipe(value)”Transforms a value through a chain of functions, evaluated left to right. Use pipe when you want to apply a sequence of transformations to a single value without nesting function calls. Each .pipe(fn) step receives the output of the previous step and full type inference is preserved throughout.
Basic arithmetic
Section titled “Basic arithmetic”import { pipe } from "@thuum/piper";
const { value } = pipe(1) .pipe((x) => x + 1) .pipe((x) => x * 2);
console.log(value); // 4Parsing and normalizing user input
Section titled “Parsing and normalizing user input”import { pipe } from "@thuum/piper";
const { value: slug } = pipe(" Hello World! ") .pipe((s) => s.trim()) .pipe((s) => s.toLowerCase()) .pipe((s) => s.replace(/\s+/g, "-")) .pipe((s) => s.replace(/[^a-z0-9-]/g, ""));
console.log(slug); // "hello-world"Reshaping an object through multiple steps
Section titled “Reshaping an object through multiple steps”import { pipe } from "@thuum/piper";
const { value: greeting } = pipe({ first: "Jane", last: "Doe", age: 28 }) .pipe((user) => ({ ...user, fullName: `${user.first} ${user.last}` })) .pipe((user) => `Hi ${user.fullName}, you are ${user.age} years old.`);
console.log(greeting); // "Hi Jane Doe, you are 28 years old."build<T>()
Section titled “build<T>()”Composes a sequence of functions into a single reusable function. Unlike pipe which transforms a concrete value immediately, build creates a reusable pipeline that can be applied to many inputs.
Basic composition
Section titled “Basic composition”import { build } from "@thuum/piper";
const { fn } = build<number>() .pipe((x) => x + 1) .pipe((x) => x * 2);
console.log(fn(1)); // 4console.log(fn(2)); // 6Building a URL slug generator
Section titled “Building a URL slug generator”import { build } from "@thuum/piper";
const { fn: toSlug } = build<string>() .pipe((s) => s.trim()) .pipe((s) => s.toLowerCase()) .pipe((s) => s.replace(/\s+/g, "-")) .pipe((s) => s.replace(/[^a-z0-9-]/g, ""));
console.log(toSlug(" Blog Post Title! ")); // "blog-post-title"console.log(toSlug("Another One ")); // "another-one"Domain-specific formatter
Section titled “Domain-specific formatter”import { build } from "@thuum/piper";
const { fn: formatPrice } = build<number>() .pipe((cents) => cents / 100) .pipe((dollars) => dollars.toFixed(2)) .pipe((str) => `$${str}`);
console.log(formatPrice(1999)); // "$19.99"console.log(formatPrice(50)); // "$0.50"Async API (@thuum/piper/async)
Section titled “Async API (@thuum/piper/async)”The async variants support both synchronous and asynchronous functions in the same pipeline using MaybePromise<T>. This allows you to seamlessly mix synchronous logic with async I/O (fetch, database queries, file reads, etc.) in a single readable pipeline.
pipe(value)
Section titled “pipe(value)”Works like the synchronous pipe, but each step can return either a plain value or a Promise. The initial value can also be a Promise.
Fetching and transforming API data
Section titled “Fetching and transforming API data”import { pipe } from "@thuum/piper/async";
const { value: userName } = pipe(fetch("/api/user/1")) .pipe((res) => res.json()) .pipe((data) => data.name) .pipe((name) => name.toUpperCase());
const result = await userName; // "ALICE"Mixing sync and async steps
Section titled “Mixing sync and async steps”import { pipe } from "@thuum/piper/async";
const { value } = pipe(5) .pipe((x) => x * 2) // sync .pipe(async (x) => await lookupLabel(x)) // async I/O .pipe((label) => label.trim()); // sync
const result = await value;Starting from an existing Promise
Section titled “Starting from an existing Promise”import { pipe } from "@thuum/piper/async";
const { value } = pipe(Promise.resolve("raw input")) .pipe((s) => s.trim()) .pipe(async (s) => await translate(s, "en", "fr"));
const result = await value;Error propagation — rejected promises short-circuit the chain
Section titled “Error propagation — rejected promises short-circuit the chain”import { pipe } from "@thuum/piper/async";
const { value } = pipe(1) .pipe(() => Promise.reject(new Error("boom"))) .pipe((x) => x + 1); // never called
await value; // throws Error("boom")build<T>()
Section titled “build<T>()”Composes a sequence of sync and async functions into a single reusable async function. Ideal for constructing reusable workflows that involve I/O — HTTP requests, database queries, file operations — interleaved with synchronous transforms.
Composing an API client handler
Section titled “Composing an API client handler”import { build } from "@thuum/piper/async";
const { fn: getUser } = build<number>() .pipe(async (id) => fetch(`/api/users/${id}`)) .pipe((res) => res.json()) .pipe((data) => ({ id: data.id, name: data.name }));
const user = await getUser(42);console.log(user.name); // "Alice"Reusable file processing pipeline
Section titled “Reusable file processing pipeline”import { build } from "@thuum/piper/async";
const { fn: processFile } = build<string>() .pipe(async (path) => await readFile(path, "utf-8")) .pipe((content) => content.split("\n")) .pipe((lines) => lines.filter((line) => line.trim() !== "")) .pipe((lines) => lines.length);
const count = await processFile("./data.txt");console.log(count); // 150Mixed sync/async with error short-circuiting
Section titled “Mixed sync/async with error short-circuiting”import { build } from "@thuum/piper/async";
const { fn: safeDivide } = build<{ a: number; b: number }>() .pipe(({ a, b }) => { if (b === 0) throw new Error("Division by zero"); return a / b; }) .pipe(async (result) => await saveToAuditLog(result)) .pipe((saved) => saved.id);
const id = await safeDivide({ a: 10, b: 2 });Type Signatures
Section titled “Type Signatures”type ValuePipe<T> = { pipe<R>(fn: (x: T) => R): ValuePipe<R>; readonly value: T;};
function pipe<T>(value: T): ValuePipe<T>;
type FunctionPipe<X, Y> = { pipe<Z>(fn: (y: Y) => Z): FunctionPipe<X, Z>; fn: (x: X) => Y;};
function build<X>(): FunctionPipe<X, X>;type MaybePromise<T> = T | Promise<T>;
type ValuePipe<T> = { pipe<R>(fn: (x: T) => MaybePromise<R>): ValuePipe<R>; readonly value: MaybePromise<T>;};
function pipe<T>(value: MaybePromise<T>): ValuePipe<T>;
type FunctionPipe<X, Y> = { pipe<Z>(fn: (y: Y) => MaybePromise<Z>): FunctionPipe<X, Z>; fn: (x: X) => MaybePromise<Y>;};
function build<X>(): FunctionPipe<X, X>;