npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@signageos/fp

v0.4.0

Published

Functional programming utilities for signageOS

Downloads

56

Readme

@signageos/fp

Functional programming utilities for signageOS. Zero runtime dependencies, fully typed, and designed for composition with pipe.

Installation

npm install @signageos/fp

Modules

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 W suffix (e.g., filterW, findW) accept type guards for narrowing.
  • Zero dependencies: No runtime dependencies.