@signageos/fp
v0.4.0
Published
Functional programming utilities for signageOS
Downloads
56
Keywords
Readme
@signageos/fp
Functional programming utilities for signageOS. Zero runtime dependencies, fully typed, and designed for composition with pipe.
Installation
npm install @signageos/fpModules
All functions are curried where it makes sense and designed to work with pipe. Each module also exports a namespace object for convenient
access.
import { pipe } from '@signageos/fp/dist/fp/function';
import { Arr } from '@signageos/fp/dist/fp/array';
import { FpMap } from '@signageos/fp/dist/fp/map';
import { Record } from '@signageos/fp/dist/fp/record';
import { Result } from '@signageos/fp/dist/fp/result';
import { isDefined, assertDefined } from '@signageos/fp/dist/fp/defined';
import { FpPromise } from '@signageos/fp/dist/fp/promise';
import { Async } from '@signageos/fp/dist/fp/async';pipe and identity
Import: @signageos/fp/dist/fp/function Namespace: Func
Pipes a value through a sequence of unary functions, left to right. Type-safe up to 28 function arguments.
const orders = [
{ customer: 'Alice', amount: 120, status: 'paid' },
{ customer: 'Bob', amount: 80, status: 'pending' },
{ customer: 'Alice', amount: 50, status: 'paid' },
{ customer: 'Carol', amount: 200, status: 'paid' },
];
const totalPaid = pipe(
orders,
filter((o) => o.status === 'paid'),
pluck('amount'),
sum,
);
// 370| Function | Signature | Description |
| ---------- | --------------------------------------------------- | --------------------------------------------------- |
| pipe | <A, B, C, ...>(a: A, ab: (a: A) => B, ...) => ... | Pipes a value through a sequence of unary functions |
| identity | <T>(x: T) => T | Returns its input unchanged |
Array utilities
Import: @signageos/fp/dist/fp/array Namespace: Arr
Grouping and indexing
| Function | Signature | Description |
| ----------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------- |
| groupBy | <T, K>(discriminator: (item: T) => K) => (array: readonly T[]) => Map<K, T[]> | Groups elements by discriminator into a Map |
| groupByProperty | <T, K extends keyof T>(property: K) => (array: readonly T[]) => Map<T[K], T[]> | Groups elements by object property |
| indexBy | <T, K>(discriminator: (item: T) => K) => (array: readonly T[]) => Map<K, T> | Creates a Map keyed by discriminator (last value wins) |
| indexByProperty | <T, K extends keyof T>(property: K) => (array: readonly T[]) => Map<T[K], T> | Creates a Map keyed by object property (last value wins) |
const users = [
{ role: 'admin', name: 'Alice' },
{ role: 'user', name: 'Bob' },
{ role: 'admin', name: 'Carol' },
];
pipe(users, Arr.groupByProperty('role'));
// Map { 'admin' => [Alice, Carol], 'user' => [Bob] }
pipe(users, Arr.indexByProperty('name'));
// Map { 'Alice' => {...}, 'Bob' => {...}, 'Carol' => {...} }Deduplication
| Function | Signature | Description |
| ------------------ | ----------------------------------------------------------------------- | ----------------------------------------------- |
| uniqueBy | <T, K>(discriminator: (item: T) => K) => (array: readonly T[]) => T[] | Deduplicates by discriminator (last value wins) |
| uniqueByRef | <T>(array: readonly T[]) => T[] | Deduplicates by reference equality |
| uniqueByProperty | <T, K extends keyof T>(property: K) => (array: readonly T[]) => T[] | Deduplicates by property (last value wins) |
Transformation
| Function | Signature | Description |
| ------------------ | --------------------------------------------------------------------------------------- | -------------------------------------------------- |
| map | <T, R>(mapper: (value: T) => R) => (array: readonly T[]) => R[] | Curried Array.prototype.map |
| flatMap | <T, R>(mapper: (value: T) => readonly R[]) => (array: readonly T[]) => R[] | Curried Array.prototype.flatMap |
| flatten | <T>(array: readonly T[][]) => T[] | Flattens one level of nesting |
| filter | <T>(predicate: (value: T) => boolean) => (array: readonly T[]) => T[] | Curried Array.prototype.filter |
| filterW | <T, S extends T>(predicate: (value: T) => value is S) => (array: readonly T[]) => S[] | Filter with type narrowing |
| exclude | <A, B>(itemsToRemove: readonly A[]) => (array: readonly B[]) => Exclude<B, A>[] | Removes items with type narrowing |
| withoutOptionals | <T>(array: readonly (T \| undefined \| null)[]) => T[] | Removes undefined and null with type narrowing |
| withoutValues | <T>(values: readonly T[]) => (array: readonly T[]) => T[] | Removes specific values |
| reverse | <T>(array: readonly T[]) => T[] | Immutable reverse |
| sort | <T>(compare: (a: T, b: T) => number) => (array: readonly T[]) => T[] | Immutable sort |
| concat | <A, B>(a: readonly B[]) => (b: readonly A[]) => (A \| B)[] | Concatenates two arrays (b first, then a) |
| tap | <T>(sideEffect: (value: T) => void) => (array: T[]) => T[] | Executes side effect, returns same array |
| chunk | <T>(size: number) => (array: readonly T[]) => T[][] | Splits into fixed-size chunks |
pipe(
[1, 2, null, 3, undefined, 4],
Arr.withoutOptionals,
Arr.filter((n) => n > 2),
);
// [3, 4]Extraction
| Function | Signature | Description |
| ------------- | ----------------------------------------------------------------------- | ------------------------------------------- |
| pluck | <T, K extends keyof T>(key: K) => (objects: readonly T[]) => T[K][] | Extracts a single property from each object |
| pluckColumn | <T, K extends keyof T>(index: K) => (array: readonly T[]) => T[K][] | Extracts column from tuples |
| at | <T>(index: number) => (array: readonly T[]) => T \| undefined | Accesses element at index |
| first | <T>(array: readonly T[]) => T \| undefined | Returns first element |
| last | <T>(array: readonly T[]) => T \| undefined | Returns last element |
| init | <T>(array: readonly T[]) => T[] | All elements except the last |
| take | <T>(n: number) => (array: readonly T[]) => T[] | Takes first n elements |
| takeWhile | <T>(predicate: (value: T) => boolean) => (array: readonly T[]) => T[] | Takes elements while predicate holds |
Search
| Function | Signature | Description |
| -------- | -------------------------------------------------------------------------------------------------- | ----------------------------------- |
| find | <T>(predicate: (value: T) => boolean) => (array: readonly T[]) => T \| undefined | Finds first matching element |
| findW | <T, S extends T>(predicate: (value: T) => value is S) => (array: readonly T[]) => S \| undefined | Find with type narrowing |
| exists | <T>(predicate: (value: T) => boolean) => (array: readonly T[]) => boolean | Returns true if any element matches |
Reduction
| Function | Signature | Description |
| ----------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| reduce | <T, R>(reducer: (acc: R, value: T) => R, initialValue: R) => (array: readonly T[]) => R | Curried Array.prototype.reduce |
| scan | <T, R>(reducer: (acc: R, value: T) => R, initialValue: R) => (array: readonly T[]) => R[] | Like reduce, but tracks all intermediate values |
| sum | (array: readonly number[]) => number | Sums all numbers |
| max | <T>(compare: (a: T, b: T) => number) => (array: readonly T[]) => T \| undefined | Maximum element by compare function |
| maxNumber | (array: readonly number[]) => number \| undefined | Maximum number |
| mkString | (separator: string) => (array: readonly string[]) => string | Joins strings with separator |
Zipping and set operations
| Function | Signature | Description |
| ---------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------ |
| zipValues | <K, V>(values: readonly V[]) => (keys: readonly K[]) => Map<K, V> | Creates Map from keys and values arrays |
| zipKeys | <K, V>(keys: readonly K[]) => (values: readonly V[]) => Map<K, V> | Creates Map from keys and values arrays |
| zipMap | <K, V>(mapper: (value: K, index: number) => V) => (array: readonly K[]) => Map<K, V> | Creates Map by applying mapper to array elements |
| diff | <T>(completeArray: readonly T[]) => (incompleteArray: readonly T[]) => T[] | Elements in complete but not in incomplete |
| intersect | <T>(a: readonly T[]) => (b: readonly T[]) => T[] | Elements present in both arrays |
| union | <T>(a: readonly T[]) => (b: readonly T[]) => Set<T> | Union as Set (deduplicates by reference) |
| toMapFromPairs | <K, V>(pairs: readonly [K, V][]) => Map<K, V> | Creates Map from [key, value] pairs |
Window and async
| Function | Signature | Description |
| -------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| mapWindow | <T, R>(windowSize: number, mapper: (window: readonly T[], index: number) => R) => (array: readonly T[]) => R[] | Reduces over a sliding window |
| fromAsyncGenerator | <T>(generator: AsyncGenerator<T>) => Promise<T[]> | Collects async generator into an array |
Map utilities
Import: @signageos/fp/dist/fp/map Namespace: FpMap
Transformation
| Function | Signature | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| mapValues | <K, V1, V2>(mapper: (value: V1, key: K, index: number) => V2) => (map: ReadonlyMap<K, V1>) => Map<K, V2> | Maps values immutably |
| mapKeys | <K1, K2, V>(mapper: (key: K1, value: V, index: number) => K2) => (map: ReadonlyMap<K1, V>) => Map<K2, V> | Maps keys immutably |
| mapBoth | <K1, K2, V1, V2>(keyMapper: (key: K1) => K2, valueMapper: (value: V1) => V2) => (map: ReadonlyMap<K1, V1>) => Map<K2, V2> | Maps both keys and values |
| flatMapValues | <K, V1, V2>(mapper: (value: V1, key: K, index: number) => V2[]) => (map: ReadonlyMap<K, V1>) => V2[] | Maps values and flattens into array |
| filter | <K, V>(predicate: (value: V, key: K, index: number) => boolean) => (map: ReadonlyMap<K, V>) => Map<K, V> | Filters key-value pairs |
| filterW | <K, V, S extends V>(predicate: (value: V, key: K, index: number) => value is S) => (map: ReadonlyMap<K, V>) => Map<K, S> | Filter with type narrowing |
| withoutOptionals | <K, V>(map: ReadonlyMap<K, V \| null \| undefined>) => Map<K, V> | Removes undefined/null values |
| tapValues | <K, V>(sideEffect: (value: V, key: K, index: number) => void) => (map: Map<K, V>) => Map<K, V> | Executes side effects, returns same Map |
Lookup
| Function | Signature | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| getOrElse | <K, V>(key: K, orElse: () => V) => (map: ReadonlyMap<K, V>) => V | Gets value or calls fallback |
| getOrUndefined | <K, V>(key: K) => (map: ReadonlyMap<K, V>) => V \| undefined | Gets value or returns undefined |
| getByKeys | <K, V>(keys: K[]) => (map: ReadonlyMap<K, V>) => Map<K, V> | Selects subset by keys (preserving order) |
| find | <K, V>(predicate: (value: V, key: K, index: number) => boolean) => (map: ReadonlyMap<K, V>) => [K, V] \| undefined | Finds first matching entry |
Set operations
| Function | Signature | Description |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| concat | <K1, V1, K2, V2>(map1: ReadonlyMap<K1, V1>) => (map2: ReadonlyMap<K2, V2>) => Map<K1 \| K2, V1 \| V2> | Merges two Maps (map1 values take precedence) |
| intersect | <K, V>(map1: ReadonlyMap<K, V>, map2: ReadonlyMap<K, V>) => Map<K, V> | Keys present in both with equal values |
| diff | <K, V>(map1: ReadonlyMap<K, V>, map2: ReadonlyMap<K, V>, strictEquality: boolean) => Map<K, [V, V]> | Keys with missing or non-equal values |
| diffNonEqual | <K, V>(map1: ReadonlyMap<K, V>, map2: ReadonlyMap<K, V>, equal: (a: V, b: V) => boolean) => Map<K, [V \| undefined, V \| undefined]> | Diff using custom equality function |
Conversion
| Function | Signature | Description |
| ------------ | ---------------------------------------------------------- | -------------------------- |
| getValues | <V>(map: ReadonlyMap<unknown, V>) => V[] | Extracts values to array |
| getKeys | <K>(map: ReadonlyMap<K, unknown>) => K[] | Extracts keys to array |
| toArray | <K, V>(map: ReadonlyMap<K, V>) => [K, V][] | Converts to array of pairs |
| fromRecord | <K extends string, V>(record: Record<K, V>) => Map<K, V> | Creates Map from object |
| toRecord | <V>(map: ReadonlyMap<string, V>) => Record<string, V> | Converts Map to object |
const userMap = new Map([
['a', 1],
['b', 2],
['c', 3],
]);
pipe(
userMap,
FpMap.filter((v) => v > 1),
FpMap.mapValues((v) => v * 10),
);
// Map { 'b' => 20, 'c' => 30 }Record utilities
Import: @signageos/fp/dist/fp/record Namespace: Record
| Function | Signature | Description |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| map | <A extends Record<RecordKey, unknown>, V>(mapper: (value: A[keyof A], key: keyof A) => V) => (obj: A) => { [P in keyof A]: V } | Maps object values immutably, preserving key types |
| getOrInitOnPartial | <T, K extends keyof T>(key: K, valueFactory: () => NonNullable<T[K]>) => (obj: T) => NonNullable<T[K]> | Initializes property if unset (mutable!) |
| getTypedKeys | <T extends Record<RecordKey, unknown>>(obj: T) => (keyof T)[] | Object.keys with proper TypeScript typing |
| getTypedValues | <T extends Record<RecordKey, unknown>>(obj: T) => T[keyof T][] | Object.values with proper TypeScript typing |
| getTypedPairs | <T extends Record<RecordKey, unknown>>(obj: T) => [keyof T, T[keyof T]][] | Object.entries with proper TypeScript typing |
| getTypedFromEntries | <K extends RecordKey, V>(entries: readonly (readonly [K, V])[]) => Record<K, V> | Object.fromEntries with proper TypeScript typing |
const config = { width: 100, height: 200, depth: 50 };
pipe(
config,
Record.map((v) => v * 2),
);
// { width: 200, height: 400, depth: 100 }Defined utilities
Import: @signageos/fp/dist/fp/defined
| Function | Signature | Description |
| --------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| isDefined | <T>(value: T \| undefined \| null) => value is T | Type guard excluding undefined and null |
| assertDefined | <T>(error: () => Error) => (value: T \| undefined \| null) => T | Returns value if defined, throws otherwise |
| mapOptional | <T, R>(mapper: (value: T) => R) => (value: T \| undefined \| null) => R \| undefined \| null | Applies mapper only when value is defined; passes undefined/null through unchanged |
| orElse | <T, U>(fallback: () => U) => (value: T \| undefined \| null) => T \| U | Returns value if defined, otherwise fallback result |
pipe([1, null, 2, undefined, 3], Arr.filterW(isDefined));
// [1, 2, 3] with type number[]
pipe(
map.get('key'),
assertDefined(() => new Error('key not found')),
);
// value or throws
pipe(
map.get('userId'),
mapOptional((user) => user.name),
orElse(() => 'anonymous'),
);
// user.name or 'anonymous'Result monad
Import: @signageos/fp/dist/fp/result Namespace: Result
A typed Result monad (Result<T, TError>) for explicit error handling without exceptions.
type Result<T, TError = unknown> = ResultSuccess<T> | ResultFailure<TError>;
type ResultSuccess<T> = { success: true; value: T };
type ResultFailure<TError> = { success: false; value: TError };Creating results
| Function | Signature | Description |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| resolve | <T>(value: T) => Result<T, never> | Creates a successful Result |
| reject | <TError>(value: TError) => Result<never, TError> | Creates a failed Result |
| try | <T, TError extends ClassOfError>(tryFn: () => T, errors: TError[]) => Result<T, InstanceType<TError>> | Executes function, catches specified error types |
| fromThrowable | <T, R, TError extends ClassOfError>(fn: (value: T) => R, errors: TError[]) => (value: T) => Result<R, InstanceType<TError>> | Lifts a throwing function into one returning Result (pipe-friendly) |
| settle | <T>(p: Promise<T>) => Promise<Result<T, unknown>> | Wraps a Promise outcome in Result |
Checking results
| Function | Signature | Description |
| ----------- | --------------------------------------------------------------------------- | ---------------------- |
| isSuccess | <T, TError>(result: Result<T, TError>) => result is ResultSuccess<T> | Type guard for success |
| isFailure | <T, TError>(result: Result<T, TError>) => result is ResultFailure<TError> | Type guard for failure |
Lifting
| Function | Signature | Description |
| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| lift | <A, B, ..., R>(fn: (a: A, b: B, ...) => R) => (Result<A>, Result<B>, ...) => Result<R> (overloads 1–6 args) | Lifts a pure function into the Result context |
const add = (a: number, b: number) => a + b;
Result.lift(add)(Result.resolve(1), Result.resolve(2));
// { success: true, value: 3 }
Result.lift(add)(Result.resolve(1), Result.reject('err'));
// { success: false, value: 'err' }Transforming results
| Function | Signature | Description |
| -------------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| map | <T, U>(fn: (value: T) => U) => <TError>(result: Result<T, TError>) => Result<U, TError> | Maps the success value |
| flatMap | <T, U, UError>(fn: (value: T) => Result<U, UError>) => <TError>(result: Result<T, TError>) => Result<U, TError \| UError> | Maps with Result-returning function |
| recover | <TError, U, UError>(fn: (error: TError) => Result<U, UError>) => <T>(result: Result<T, TError>) => Result<T \| U, UError> | Recovers from failure |
| unwrap | <T>(result: Result<T>) => T | Extracts success value or throws |
| unwrapOrElse | <T, TError>(orElse: (error: TError) => T) => (result: Result<T, TError>) => T | Extracts success value or calls fallback |
Collection operations
| Function | Signature | Description |
| ---------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| traverse | <T, R, TError>(fn: (value: T) => Result<R, TError>) => (array: readonly T[]) => Result<R[], TError> | Applies Result-returning function to each element, short-circuits on first failure |
| sequence | <T, TError>(results: readonly Result<T, TError>[]) => Result<T[], TError> | Collects array of Results into Result of array, short-circuits on first failure |
const parseJson = (input: string) => Result.try(() => JSON.parse(input), [SyntaxError]);
pipe(
parseJson('{"a": 1}'),
Result.map((obj) => obj.a),
Result.unwrap,
);
// 1
pipe(
parseJson('invalid'),
Result.recover(() => Result.resolve({})),
);
// { success: true, value: {} }
// Lift a throwing function for pipe usage
pipe(
'[1,2,3]',
Result.fromThrowable(JSON.parse, [SyntaxError]),
Result.map((arr: number[]) => arr.length),
);
// { success: true, value: 3 }
// Lift a pure multi-argument function into Result context
const multiply = (a: number, b: number) => a * b;
Result.lift(multiply)(Result.resolve(3), Result.resolve(7));
// { success: true, value: 21 }
// Validate an array of inputs, short-circuit on first failure
pipe(
['1', '2', 'abc'],
Result.traverse((s) => {
const n = parseInt(s, 10);
return isNaN(n) ? Result.reject(`not a number: ${s}`) : Result.resolve(n);
}),
);
// { success: false, value: 'not a number: abc' }
// Collect multiple independent Results
Result.sequence([Result.resolve(1), Result.resolve(2), Result.resolve(3)]);
// { success: true, value: [1, 2, 3] }
// Settle a promise
const result = await Result.settle(fetch('/api/data'));
if (Result.isSuccess(result)) {
console.log(result.value);
}Promise utilities
Import: @signageos/fp/dist/fp/promise Namespace: FpPromise
| Function | Signature | Description |
| ----------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| map | <T, R>(fn: (value: T) => R) => (promise: Promise<T>) => Promise<R> | Transforms the resolved value with a synchronous function |
| flatMap | <T, R>(fn: (value: T) => Promise<R>) => (promise: Promise<T>) => Promise<R> | Chains with an async function, flattening the result |
| traversePromise | <T, R>(mapper: (item: T) => Promise<R>, parallelism?: number) => (array: readonly T[]) => Promise<R[]> | Maps each element through an async function with controlled concurrency (default 1 = sequential) |
import { map, flatMap, traversePromise } from '@signageos/fp/dist/fp/promise';
// Transform a resolved value
const name = await pipe(
Promise.resolve({ name: 'Alice' }),
map((u) => u.name),
);
// 'Alice'
// Chain async operations
await pipe(
Promise.resolve(userId),
flatMap((id) => fetchUser(id)),
map((user) => user.name),
);
// Sequential — one request at a time
const users = await pipe(userIds, traversePromise(fetchUser));
// Up to 5 concurrent requests
const users = await pipe(userIds, traversePromise(fetchUser, 5));Async monad
Import: @signageos/fp/dist/fp/async Namespace: Async
A lazy asynchronous monad (Async<T, TError>) that combines the laziness of a Task with the typed error handling of Result. Similar to
TaskEither in fp-ts.
The computation does not start until run is called, and each run starts a fresh execution.
type Async<T, TError = unknown> = () => Promise<Result<T, TError>>;Creating Async values
| Function | Signature | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| resolve | <T>(value: T) => Async<T, never> | Creates a successful Async |
| reject | <TError>(error: TError) => Async<never, TError> | Creates a failed Async |
| fromResult | <T, TError>(result: Result<T, TError>) => Async<T, TError> | Lifts a Result into Async |
| fromPromise | <T, TError>(promiseFn: () => Promise<T>, onRejected: (error: unknown) => TError) => Async<T, TError> | Wraps a Promise-returning thunk with typed errors |
| tryCatch | <T, TError extends ClassOfError>(tryFn: () => Promise<T>, errors: TError[]) => Async<T, InstanceType<TError>> | Catches only specified error classes; re-throws unmatched |
| fromThrowable | <T, R, TError extends ClassOfError>(fn: (value: T) => Promise<R>, errors: TError[]) => (value: T) => Async<R, InstanceType<TError>> | Curried, pipe-friendly version of tryCatch |
Lifting
| Function | Signature | Description |
| -------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| lift | <A, B, ..., R>(fn: (a: A, b: B, ...) => R) => (Async<A>, Async<B>, ...) => Async<R> (overloads 1–6 args) | Lifts a pure function into the Async context |
const add = (a: number, b: number) => a + b;
await Async.run(Async.lift(add)(Async.resolve(1), Async.resolve(2)));
// { success: true, value: 3 }Transforming Async values
| Function | Signature | Description |
| ---------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| map | <T, R>(fn: (value: T) => R) => <TError>(task: Async<T, TError>) => Async<R, TError> | Maps the success value |
| flatMap | <T, R, RError>(fn: (value: T) => Async<R, RError>) => <TError>(task: Async<T, TError>) => Async<R, TError \| RError> | Chains with an Async-returning function |
| recover | <TError, R, RError>(fn: (error: TError) => Async<R, RError>) => <T>(task: Async<T, TError>) => Async<T \| R, RError> | Recovers from failure |
| mapError | <TError, UError>(fn: (error: TError) => UError) => <T>(task: Async<T, TError>) => Async<T, UError> | Transforms the error value |
| tap | <T>(fn: (value: T) => void) => <TError>(task: Async<T, TError>) => Async<T, TError> | Side effect on success, value unchanged |
Running and unwrapping
| Function | Signature | Description |
| -------------- | ------------------------------------------------------------------------------------- | ------------------------------------------ |
| run | <T, TError>(task: Async<T, TError>) => Promise<Result<T, TError>> | Executes the Async, returns Result |
| unwrap | <T, TError>(task: Async<T, TError>) => Promise<T> | Executes and extracts value, or throws |
| unwrapOrElse | <T, TError>(orElse: (error: TError) => T) => (task: Async<T, TError>) => Promise<T> | Executes, extracts value or calls fallback |
Collection operations
| Function | Signature | Description |
| ---------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| traverse | <T, R, TError>(fn: (value: T) => Async<R, TError>) => (array: readonly T[]) => Async<R[], TError> | Sequential map, short-circuits on failure |
| sequence | <T, TError>(tasks: readonly Async<T, TError>[]) => Async<T[], TError> | Sequential collect, short-circuits on failure |
| all | <T, TError>(tasks: readonly Async<T, TError>[]) => Async<T[], TError> | Parallel collect (like Promise.all) |
// Basic pipeline — lazy until run is called
const getUser = pipe(
Async.resolve(userId),
Async.flatMap((id) =>
Async.fromPromise(
() => fetch(`/api/users/${id}`).then((r) => r.json()),
(err) => new HttpError(err),
),
),
Async.map((user) => user.name),
Async.recover(() => Async.resolve('anonymous')),
);
const result = await Async.run(getUser);
// { success: true, value: 'Alice' }
// Bridge from Promise world — typed errors instead of untyped rejections
const safeFetch = Async.fromThrowable((url: string) => fetch(url).then((r) => r.json()), [TypeError]);
const name = await pipe(
'/api/me',
safeFetch,
Async.map((u) => u.name),
Async.unwrapOrElse(() => 'guest'),
);
// Parallel fetching with typed errors
const users = await pipe(
Async.all(
userIds.map((id) =>
Async.fromPromise(
() => fetchUser(id),
(err) => `Failed to fetch ${id}: ${err}`,
),
),
),
Async.run,
);
// Sequential processing with short-circuit on first failure
const validated = await pipe(
rawInputs,
Async.traverse((input) =>
Async.fromPromise(
() => validateRemote(input),
(err) => `Validation failed for ${input}: ${err}`,
),
),
Async.run,
);Examples
See EXAMPLES.md for practical pipeline examples.
Design principles
- Curried for composition: Most functions take configuration first and data last, making them composable with
pipe. - Immutable by default: All operations return new instances. The sole exception is
Record.getOrInitOnPartial, which is explicitly marked as mutable. - Type-safe: Full TypeScript inference at every step. Functions with
Wsuffix (e.g.,filterW,findW) accept type guards for narrowing. - Zero dependencies: No runtime dependencies.
