Skip to main content
pipe and flow are utility functions for composing sequences of transformations. Both are fully typed — each function’s return type becomes the next function’s argument type, with no loss of type information.
import { pipe, flow } from 'ts-chas/pipe';
Both support up to 10 functions in a single call. If you need more, break your pipeline into named intermediate steps.

pipe(value, ...fns)

Takes an initial value and passes it through a sequence of functions left-to-right. Returns the output of the final function.
import { pipe } from 'ts-chas/pipe';

const add5  = (x: number) => x + 5;
const double = (x: number) => x * 2;

const result = pipe(10, add5, double);
// add5(10) = 15, double(15) = 30
// result: 30
Each function’s return type informs the next function’s parameter type:
const result = pipe(
  'hello world',
  (s) => s.split(' '),        // string → string[]
  (words) => words.length,    // string[] → number
  (n) => `Word count: ${n}`   // number → string
);
// 'Word count: 2'

flow(...fns)

Composes functions into a single reusable function without an initial value. Call the result later with your input. Think of it as a named, reusable pipeline.
import { flow } from 'ts-chas/pipe';

const add5  = (x: number) => x + 5;
const double = (x: number) => x * 2;

const calculate = flow(add5, double);
// calculate is typed as (x: number) => number

calculate(10); // 30
calculate(0);  // 10
flow is useful for defining transforms you want to pass as callbacks or store as constants:
const normalizeEmail = flow(
  (s: string) => s.trim(),
  (s) => s.toLowerCase(),
  (s) => s.replace(/\s+/g, '')
);

['  Alice@Example.COM ', ' BOB@TEST.ORG'].map(normalizeEmail);
// ['alice@example.com', 'bob@test.org']

Built-in .pipe() on Result and ResultAsync

Result and ResultAsync have their own .pipe() method. It behaves like pipe, but is Result-aware: if any step returns an Err, the chain short-circuits and subsequent functions are skipped.
import { chas } from 'ts-chas';

const add5  = (x: number) => x + 5;
const double = (x: number) => x * 2;

const result = chas.ok(10).pipe(add5, double);
// Ok(30)

const errResult = chas.err('something went wrong').pipe(add5, double);
// Err('something went wrong') — add5 and double are never called
This also works on ResultAsync:
const asyncResult = chas.okAsync(10).pipe(add5, double);
// ResultAsync<30, never>

Complete example

This example shows all three forms working together in a data transformation pipeline:
import { pipe, flow } from 'ts-chas/pipe';
import { chas } from 'ts-chas';

interface RawProduct {
  name: string;
  price_cents: number;
  tags: string | null;
}

interface Product {
  name: string;
  priceUsd: number;
  tags: string[];
}

// Reusable transforms defined with flow
const centsToUsd = flow(
  (cents: number) => cents / 100,
  (usd) => Math.round(usd * 100) / 100
);

const parseTags = (raw: string | null): string[] =>
  raw ? raw.split(',').map(t => t.trim()).filter(Boolean) : [];

// A single-value transformation using pipe
function toProduct(raw: RawProduct): Product {
  return pipe(
    raw,
    (r) => ({ ...r, priceUsd: centsToUsd(r.price_cents) }),
    (r) => ({ ...r, tags: parseTags(r.tags) }),
    ({ name, priceUsd, tags }) => ({ name, priceUsd, tags })
  );
}

// Result-aware pipeline using the built-in .pipe() on Result
function parseAndTransform(input: unknown): chas.Result<Product, string> {
  return chas
    .tryCatch(
      () => input as RawProduct,
      () => 'Invalid product data'
    )
    .pipe(
      (raw) => ({ ...raw, name: raw.name.trim() }),
      toProduct
    );
}

// Usage
const result = parseAndTransform({ name: '  Widget ', price_cents: 1999, tags: 'sale, new' });

if (result.isOk()) {
  console.log(result.value);
  // { name: 'Widget', priceUsd: 19.99, tags: ['sale', 'new'] }
}