Skip to content

Lazy AsyncResult operators

AsyncResult — barrel export.

Re-exports all AsyncResult factories and operators.

Returns res2 if res1 is Ok, otherwise returns the original Err. Short-circuiting — res2 is not evaluated when res1 is Err.

export function and<T, U, E>(
res1: AsyncResult<T, E>,
res2: AsyncResult<U, E>,
): AsyncResult<U, E>;

Defined in: async-result/and.ts

import { ok, err } from '@sandlada/result/factories';
import { and, fromResult, match } from '@sandlada/result/async-result';
const first = fromResult(ok(1));
const second = fromResult(ok(2));
const combined = and(first, second);
const value = await match({ ok: (n) => n, err: (e) => -1 }, combined);
// value === 2
const short = and(fromResult(err('a')), second);
const errValue = await match({ ok: (n) => `ok:${n}`, err: (e) => `err:${e}` }, short);
// errValue === 'err:a'; and short-circuits without running second

Side-effect on success (sync or async), ignoring the callback’s result. Calls fn with the value on success and passes the original result through unchanged. Lazy — returns a new AsyncResult without executing the inner computation.

Throw policy: If the side-effect callback throws (or rejects), the result converts to err(caughtError) (canonical tap/tee policy — see AGENTS.md).

export function andTee<T, E>(
fn: (value: T) => void | unknown | Promise<void | unknown>,
): (ar: AsyncResult<T, E>) => AsyncResult<T, E>;
export function andTee<T, E>(
fn: (value: T) => void | unknown | Promise<void | unknown>,
ar: AsyncResult<T, E>,
): AsyncResult<T, E>;

Defined in: async-result/andTee.ts

import { ok, err } from '@sandlada/result/factories';
import { andTee, fromResult, match } from '@sandlada/result/async-result';
const input = fromResult(ok(42));
const teed = andTee((_v: number) => undefined, input);
const label = await match({ ok: (n) => `ok:${n}`, err: (e) => `err:${String(e)}` }, teed);
// label === 'ok:42'
const skipped = andTee((_v: number) => undefined, fromResult(err('skip')));
const errLabel = await match({ ok: (n) => `ok:${n}`, err: (e) => `err:${e}` }, skipped);
// errLabel === 'err:skip'; andTee short-circuits on Err

Side-effect on success that can propagate errors. Calls fn with the value on success; if fn fails the failure widens into the original error type.

export function andThrough<T, E, F>(
fn: (value: T) => AsyncResult<unknown, F> | Promise<IResultOfT<unknown, F>>,
): (ar: AsyncResult<T, E>) => AsyncResult<T, E | F>;
export function andThrough<T, E, F>(
fn: (value: T) => AsyncResult<unknown, F> | Promise<IResultOfT<unknown, F>>,
ar: AsyncResult<T, E>,
): AsyncResult<T, E | F>;

Defined in: async-result/andThrough.ts

import { ok, err } from '@sandlada/result/factories';
import { andThrough, fromResult, match } from '@sandlada/result/async-result';
const input = fromResult(ok(42));
const validated = andThrough(
(v: number) => (v > 0 ? fromResult(ok(undefined)) : fromResult(err('non-positive'))),
input,
);
const label = await match({ ok: (n) => `ok:${n}`, err: (e) => `err:${e}` }, validated);
// label === 'ok:42'
const bad = andThrough(
(v: number) => (v > 0 ? fromResult(ok(undefined)) : fromResult(err('non-positive'))),
fromResult(ok(-1)),
);
const errLabel = await match({ ok: (n) => `ok:${n}`, err: (e) => `err:${e}` }, bad);
// errLabel === 'err:non-positive'; andThrough replaces Ok with the side-effect failure

AsyncResult applicative ap — applies a function wrapped in an AsyncResult to a value wrapped in an AsyncResult. If either is a failure, the first failure propagates.

The function-result and value-result are allowed to have different error types — the combined error widens to E | F. Matches the sync ap from @sandlada/result/operators.

Mirrors the sync ap for AsyncResult pipelines, matching the fp-ts Apply shape.

Execution: both carriers are started when .run() is called and run concurrently - ap needs both sides, so it never short-circuits.

Throw policy: a throwing applied function is captured as Err(thrown).

export function ap<A, B, E, F>(
fnResult: AsyncResult<(a: A) => B, E>,
): (result: AsyncResult<A, F>) => AsyncResult<B, E | F>;
export function ap<A, B, E, F>(
fnResult: AsyncResult<(a: A) => B, E>,
result: AsyncResult<A, F>,
): AsyncResult<B, E | F>;

Defined in: async-result/ap.ts

import { ok, err } from '@sandlada/result/factories';
import { ap, fromResult, match } from '@sandlada/result/async-result';
const fnCarrier = fromResult(ok((x: number) => x * 2));
const valueCarrier = fromResult(ok(21));
const applied = ap(fnCarrier, valueCarrier);
const label = await match({ ok: (n) => `ok:${n}`, err: (e) => `err:${String(e)}` }, applied);
// label === 'ok:42'
const brokenFn = fromResult(err('fn'));
const stalled = ap(brokenFn, valueCarrier);
const errLabel = await match({ ok: (n) => n, err: (e) => e }, stalled);
// errLabel === 'fn'; ap propagates the function-side failure

Simultaneously maps both variants of an AsyncResult.

Throw policy: If onOk or onErr throws (or rejects), the result converts to err(caughtError). Pass errorFn to customise how the thrown/rejected value maps onto your error union.

export function bimap<T, E, U, F>(
onOk: (value: T) => U | Promise<U>,
onErr: (error: E) => F | Promise<F>,
errorFn?: (thrown: unknown) => unknown,
): (ar: AsyncResult<T, E>) => AsyncResult<U, F>;
export function bimap<T, E, U, F>(
onOk: (value: T) => U | Promise<U>,
onErr: (error: E) => F | Promise<F>,
ar: AsyncResult<T, E>,
errorFn?: (thrown: unknown) => F,
): AsyncResult<U, F>;

Defined in: async-result/bimap.ts

import { ok, err } from '@sandlada/result/factories';
import { bimap, fromResult, match } from '@sandlada/result/async-result';
const input = fromResult(ok(5));
const mapped = bimap(
(v: number) => v.toString(),
(e: string) => e.toUpperCase(),
input,
);
const label = await match({ ok: (s) => s, err: (e) => e }, mapped);
// label === '5'
const failed = bimap(
(v: number) => v.toString(),
(e: string) => e.toUpperCase(),
fromResult(err('oops')),
);
const errLabel = await match({ ok: (s) => s, err: (e) => e }, failed);
// errLabel === 'OOPS'

Chains an AsyncResult-returning function on success (monadic bind / flatMap). Supports interoperability with standard Promise<IResultOfT>. Lazy — returns a new AsyncResult without executing the inner computation.

The outer AsyncResult’s error type and the inner callback’s returned AsyncResult error type are independent — the combined result widens to E | F. Mirrors the sync operators/bind.ts so heterogeneous-error pipelines (AppError/DbError) compose without manual unification.

Throw policy: If fn throws (sync) or rejects (async), the result converts to err(caughtError). Pass errorFn to customise how the thrown/rejected value maps onto your error union — e.g. bind(fn, thrown => new MyError(String(thrown))).

export function bind<T, U, E, F>(
fn: (value: T) => AsyncResult<U, F> | Promise<IResultOfT<U, F>>,
errorFn?: (thrown: unknown) => unknown,
): (ar: AsyncResult<T, E>) => AsyncResult<U, E | F>;
export function bind<T, U, E, F>(
fn: (value: T) => AsyncResult<U, F> | Promise<IResultOfT<U, F>>,
ar: AsyncResult<T, E>,
errorFn?: (thrown: unknown) => E | F,
): AsyncResult<U, E | F>;

Defined in: async-result/bind.ts

import { ok, err } from '@sandlada/result/factories';
import { bind, fromResult, match } from '@sandlada/result/async-result';
const input = fromResult(ok(1));
const chained = bind((id: number) => fromResult(ok(id + 1)), input);
const value = await match({ ok: (n) => n, err: () => -1 }, chained);
// value === 2
const outerErr = fromResult(err('app'));
const short = bind((id: number) => fromResult(ok(id + 1)), outerErr);
const missed = await match({ ok: (n) => `ok:${n}`, err: (e) => `err:${e}` }, short);
// missed === 'err:app'; bind short-circuits when the carrier is Err

Async variant of catchErr for AsyncResult.

Recovers from an error by returning a fallback value T (or a Promise resolving to T), keeping the result track alive as a successful AsyncResult<T, never>.

Throw policy: failures inside onErr (sync throw or rejected Promise) are captured into Err(thrown), mirroring orElse / filterOrElse. A rejection of the source AsyncResult itself still propagates.

export function catchErr<A, E>(
onErr: (e: E) => A | Promise<A>,
): (ar: AsyncResult<A, E>) => AsyncResult<A, never>;
export function catchErr<A, E>(
onErr: (e: E) => A | Promise<A>,
ar: AsyncResult<A, E>,
): AsyncResult<A, never>;

Defined in: async-result/catchErr.ts

import { err, ok } from '@sandlada/result/factories';
import { catchErr, fromResult, map, match } from '@sandlada/result/async-result';
const input = fromResult(err('boom'));
const recovered = catchErr(() => 0, input);
const label = await match({ ok: (n) => `ok:${n}`, err: () => 'err' }, recovered);
// label === 'ok:0'
const healthy = catchErr(() => 0, fromResult(ok(21)));
const doubled = map((x: number) => x * 2, healthy);
const value = await match({ ok: (n) => n, err: () => -1 }, doubled);
// value === 42; catchErr passes Ok through unchanged

Combines an array of AsyncResults into a single AsyncResult of an array. Short-circuits on the first failure (like Promise.all). Lazy — returns a new AsyncResult without executing the inner computations.

export function combine<T, E>(
results: readonly AsyncResult<T, E>[],
): AsyncResult<T[], E>;

Defined in: async-result/combine.ts

import { ok, err } from '@sandlada/result/factories';
import { combine, fromResult, match } from '@sandlada/result/async-result';
const parts = [fromResult(ok(1)), fromResult(ok(2))];
const combined = combine(parts);
const label = await match(
{ ok: (xs) => xs.join(','), err: (e) => `err:${String(e)}` },
combined,
);
// label === '1,2'
const broken = combine([fromResult(ok(1)), fromResult(err('stop'))]);
const stopped = await broken.run();
// stopped.isFailure; combine short-circuits on the first Err

Combines AsyncResults accumulating all errors (validation aggregation). Unlike combine (short-circuit on first failure), this collects every error. Lazy — returns a new AsyncResult without executing the inner computations.

export function combineWithAllErrors<T, E>(
results: readonly AsyncResult<T, E>[],
): AsyncResult<T[], E[]>;

Defined in: async-result/combineWithAllErrors.ts

import { ok, err } from '@sandlada/result/factories';
import { combineWithAllErrors, fromResult, match } from '@sandlada/result/async-result';
const parts = [
fromResult(ok(1)),
fromResult(err('a')),
fromResult(err('b')),
];
const aggregated = combineWithAllErrors(parts);
const label = await match(
{ ok: (xs) => xs.join(','), err: (es) => es.join('|') },
aggregated,
);
// label === 'a|b'
const allOk = combineWithAllErrors([fromResult(ok(1)), fromResult(ok(2))]);
const okLabel = await match({ ok: (xs) => xs.length, err: () => 0 }, allOk);
// okLabel === 2

Returns a Promise<boolean> indicating if the AsyncResult is success and contains the given value.

export function contains<T>(
value: T,
): <E>(ar: AsyncResult<T, E>) => Promise<boolean>;
export function contains<T, E>(
value: T,
ar: AsyncResult<T, E>,
): Promise<boolean>;

Defined in: async-result/contains.ts

import { ok, err } from '@sandlada/result/factories';
import { contains, fromResult, map } from '@sandlada/result/async-result';
const input = map((x: number) => x, fromResult(ok(42)));
const hit = await contains(42, input);
const miss = await contains(0, input);
// hit === true, miss === false
const failed = contains(42, fromResult(err('skip')));
const onErr = await failed;
// onErr === false; contains is false when the carrier is Err

Returns true if the AsyncResult resolves to Err and contains the given value. Strict equality (===).

export function containsErr<T, E>(
error: E,
): (ar: AsyncResult<T, E>) => Promise<boolean>;
export function containsErr<T, E>(
error: E,
ar: AsyncResult<T, E>,
): Promise<boolean>;

Defined in: async-result/containsErr.ts

import { err, ok } from '@sandlada/result/factories';
import { containsErr, fromResult, mapErr } from '@sandlada/result/async-result';
const input = mapErr((e: string) => e, fromResult(err('boom')));
const hit = await containsErr('boom', input);
const miss = await containsErr('nope', input);
// hit === true, miss === false
const onOk = await containsErr('boom', fromResult(ok(1)));
// onOk === false; containsErr is false on Ok carriers

Returns a Promise<boolean> indicating if the AsyncResult is success and the predicate holds.

export function exists<T>(
predicate: (value: T) => boolean | Promise<boolean>,
): <E>(ar: AsyncResult<T, E>) => Promise<boolean>;
export function exists<T, E>(
predicate: (value: T) => boolean | Promise<boolean>,
ar: AsyncResult<T, E>,
): Promise<boolean>;

Defined in: async-result/exists.ts

import { ok, err } from '@sandlada/result/factories';
import { exists, fromResult, map } from '@sandlada/result/async-result';
const input = map((x: number) => x, fromResult(ok(42)));
const yes = await exists((v: number) => v > 0, input);
const no = await exists((v: number) => v < 0, input);
// yes === true, no === false
const onErr = await exists((v: number) => v > 0, fromResult(err('skip')));
// onErr === false; exists is false when the carrier is Err

Like unwrap but throws an Error carrying the supplied message. Useful for marking program-contract violations with a domain-specific message.

The original error value is preserved as Error.cause (and via the optional formatErr hook for custom string shaping), so structured E shapes don’t get clobbered to [object Object].

Pass formatErr to customise how the original error value is rendered in the message; pass throwingFn to fully replace the thrown Error class — e.g. expect(msg, ar, info => new MyError(info.message, info.value)).

export function expect<T, E>(
message: string,
ar: AsyncResult<T, E>,
formatErr?: (error: E) => string,
): Promise<T>;
export function expect<T, E>(
message: string,
ar: AsyncResult<T, E>,
formatErr: ((error: E) => string) | undefined,
throwingFn: (info: { message: string; value: E }) => Error,
): Promise<T>;

Defined in: async-result/expect.ts

import { err, ok } from '@sandlada/result/factories';
import { expect, fromResult, map } from '@sandlada/result/async-result';
const input = map((x: number) => x, fromResult(ok(42)));
const value = await expect('must succeed', input);
// value === 42
const doomed = expect('config must be valid', fromResult(err('boom')));
// await doomed throws Error with cause 'boom'

Like unwrapErr but throws an Error carrying the supplied message. Useful when reaching a success path is itself a contract violation.

The original success value is preserved as Error.cause (and via the optional throwingFn hook for full customisation), so structured T shapes don’t get lost.

export function expectErr<T, E>(
message: string,
ar: AsyncResult<T, E>,
throwingFn?: (info: { message: string; value: T }) => Error,
): Promise<E>;

Defined in: async-result/expectErr.ts

import { err, ok } from '@sandlada/result/factories';
import { expectErr, fromResult, mapErr } from '@sandlada/result/async-result';
const input = mapErr((e: string) => e, fromResult(err('boom')));
const error = await expectErr('must fail', input);
// error === 'boom'
const doomed = expectErr('should have failed', fromResult(ok(42)));
// await doomed throws Error with cause 42

Filters the success value of an AsyncResult with a predicate. If the predicate holds, the original success passes through. If it fails, returns err(errorFn(value)). Failures pass through unchanged. Lazy — returns a new AsyncResult without executing the inner computation.

Throw policy: If the predicate or errorFn throws synchronously or returns a rejected Promise, the error is caught and the result converts to err(caughtError) (canonical catch+convert policy — see AGENTS.md).

export function filterOrElse<T, E>(
predicate: (value: T) => boolean | Promise<boolean>,
errorFn: (value: T) => E | Promise<E>,
): (ar: AsyncResult<T, E>) => AsyncResult<T, E>;
export function filterOrElse<T, E>(
predicate: (value: T) => boolean | Promise<boolean>,
errorFn: (value: T) => E | Promise<E>,
ar: AsyncResult<T, E>,
): AsyncResult<T, E>;

Defined in: async-result/filterOrElse.ts

import { ok, err } from '@sandlada/result/factories';
import { filterOrElse, fromResult, match } from '@sandlada/result/async-result';
const input = fromResult(ok(42));
const kept = filterOrElse((x: number) => x > 0, (x: number) => `neg:${x}`, input);
const label = await match({ ok: (n) => `ok:${n}`, err: (e) => e }, kept);
// label === 'ok:42'
const rejected = filterOrElse(
(x: number) => x > 0,
(x: number) => `neg:${x}`,
fromResult(ok(-1)),
);
const errLabel = await match({ ok: (n) => `ok:${n}`, err: (e) => e }, rejected);
// errLabel === 'neg:-1'

Flattens a nested AsyncResult.

Single-step only: unwraps exactly one layer. Call flatten repeatedly to flatten deeper nests.

export function flatten<T, E>(
ar: AsyncResult<AsyncResult<T, E>, E>,
): AsyncResult<T, E>;

Defined in: async-result/flatten.ts

import { ok, err } from '@sandlada/result/factories';
import { flatten, fromResult, map, unwrap } from '@sandlada/result/async-result';
const nested = fromResult(ok(fromResult(ok(42))));
const flat = flatten(nested);
const doubled = map((x: number) => x * 2, flat);
const value = await unwrap(doubled);
// value === 84
const broken = flatten(fromResult(err('outer')));
const stopped = await broken.run();
// stopped stays Err('outer'); flatten short-circuits on outer failure

Creates an AsyncResult from a thunk that returns a Result or a Promise<Result>. The thunk is lazy - it won’t execute until .run() is called.

The carrier’s run() always returns a Promise: a thunk that returns a plain Result is awaited, and a thunk that throws synchronously surfaces as a rejected promise instead of escaping as a raw exception.

export function from<T, E = unknown>(
thunk: () => IResultOfT<T, E> | Promise<IResultOfT<T, E>>,
): AsyncResult<T, E>;

Defined in: async-result/from.ts

import { ok, err } from '@sandlada/result/factories';
import { from, map, match } from '@sandlada/result/async-result';
const input = from(() => Promise.resolve(ok(21)));
const doubled = map((x: number) => x * 2, input);
const label = await match(
{ ok: (n) => `ok:${n}`, err: (e) => `err:${String(e)}` },
doubled,
);
// label === 'ok:42'
const lazyErr = from(() => err('skip'));
const skipped = await map((x: number) => x * 2, lazyErr).run();
// skipped stays Err('skip'); map does not run the success mapper

Wraps a Promise<T> into an AsyncResult, catching rejections. The inner Promise is not yet created at construction time; the factory thunk is invoked lazily when .run() is called.

export function fromPromise<T, E = unknown>(
thunk: () => Promise<T>,
errorFn?: (error: unknown) => E,
): AsyncResult<T, E>;

Defined in: async-result/fromPromise.ts

import { fromPromise, map, match } from '@sandlada/result/async-result';
const input = fromPromise(() => Promise.resolve(21));
const doubled = map((x: number) => x * 2, input);
const label = await match(
{ ok: (n) => `ok:${n}`, err: (e) => `err:${String(e)}` },
doubled,
);
// label === 'ok:42'
const rejected = fromPromise(() => Promise.reject('net'));
const fallback = await match(
{ ok: (n) => n, err: (e) => -1 },
map((x: number) => x, rejected),
);
// fallback === -1; fromPromise turns rejection into Err before map runs

Wraps a sync IResultOfT into an AsyncResult (lifts a sync Result into the async world). For transforming the success value with an async callback on a sync Result, see bridge-result map instead.

export function fromResult<T, E = unknown>(
result: IResultOfT<T, E>,
): AsyncResult<T, E>;

Defined in: async-result/fromResult.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, map, match } from '@sandlada/result/async-result';
const syncOk = ok(21);
const input = fromResult(syncOk);
const doubled = map((x: number) => x * 2, input);
const label = await match(
{ ok: (n) => `ok:${n}`, err: (e) => `err:${String(e)}` },
doubled,
);
// label === 'ok:42'
const failed = fromResult(err('skip'));
const passthrough = await map((x: number) => x * 2, failed).run();
// passthrough stays Err('skip'); map short-circuits on failure

Returns true if the AsyncResult resolves to Err. Mirrors the IResultOfT.isFailure discriminator as a standalone function.

export function isErr<T, E>(ar: AsyncResult<T, E>): Promise<boolean>;

Defined in: async-result/isErr.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, isErr, mapErr } from '@sandlada/result/async-result';
const input = mapErr((e: string) => e, fromResult(err('x')));
const yes = await isErr(input);
const no = await isErr(fromResult(ok(42)));
// yes === true, no === false

Returns true if the AsyncResult resolves to Ok. Mirrors the IResultOfT.isSuccess discriminator as a standalone function.

export function isOk<T, E>(ar: AsyncResult<T, E>): Promise<boolean>;

Defined in: async-result/isOk.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, isOk, map } from '@sandlada/result/async-result';
const input = map((x: number) => x + 1, fromResult(ok(41)));
const yes = await isOk(input);
const no = await isOk(fromResult(err('x')));
// yes === true, no === false

Maps the success value of an AsyncResult using a synchronous function. Lazy — returns a new AsyncResult without executing the inner computation.

Throw policy: If fn throws, the result converts to err(caughtError). Pass errorFn to customise how the thrown value maps onto your error union.

export function map<T, U, E>(
fn: (value: T) => U,
errorFn?: (thrown: unknown) => unknown,
): (ar: AsyncResult<T, E>) => AsyncResult<U, E>;
export function map<T, U, E>(
fn: (value: T) => U,
ar: AsyncResult<T, E>,
errorFn?: (thrown: unknown) => E,
): AsyncResult<U, E>;

Defined in: async-result/map.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, map, match } from '@sandlada/result/async-result';
const input = fromResult(ok(21));
const doubled = map((x: number) => x * 2, input);
const label = await match(
{ ok: (n) => `ok:${n}`, err: (e) => `err:${String(e)}` },
doubled,
);
// label === 'ok:42'
const skipped = fromResult(err('skip'));
const passthrough = await map((x: number) => x * 2, skipped).run();
// passthrough stays Err('skip'); map short-circuits on failure

Maps the success value of an AsyncResult. The callback may be sync or async (U | Promise<U>); sync results are awaited internally.

Lazy — returns a new AsyncResult without executing the inner computation.

Throw policy: a synchronous throw from fn propagates as a rejection of .run(); a rejected Promise from fn likewise propagates. The optional errorFn only remaps the rejection payload — it does NOT convert the rejection into an Err. The thrown value passed to errorFn is whatever .run() would otherwise reject with; the returned value replaces that rejection reason. This matches the canonical AsyncResult “propagates” policy. For catch-and-convert behaviour (where the rejection becomes Err(mapped)), use map (which has different throw semantics) or wrap the operation in a try/catch.

export function mapAsync<T, U, E>(
fn: (value: T) => U | Promise<U>,
errorFn?: (thrown: unknown) => unknown,
): (ar: AsyncResult<T, E>) => AsyncResult<U, E>;
export function mapAsync<T, U, E>(
fn: (value: T) => U | Promise<U>,
ar: AsyncResult<T, E>,
errorFn?: (thrown: unknown) => E,
): AsyncResult<U, E>;

Defined in: async-result/mapAsync.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, mapAsync, match } from '@sandlada/result/async-result';
const input = fromResult(ok(21));
const doubled = mapAsync(async (x: number) => x * 2, input);
const label = await match(
{ ok: (n) => `ok:${n}`, err: (e) => `err:${String(e)}` },
doubled,
);
// label === 'ok:42'
const skipped = fromResult(err('skip'));
const passthrough = await mapAsync((x: number) => x * 2, skipped).run();
// passthrough stays Err('skip'); mapAsync short-circuits on failure

Maps the error of an AsyncResult using a synchronous function. Lazy — returns a new AsyncResult without executing the inner computation.

Throw policy: If fn throws, the result converts to err(caughtError). Pass errorFn to customise how the thrown value maps onto your error union.

export function mapErr<T, E, F>(
fn: (error: E) => F,
errorFn?: (thrown: unknown) => unknown,
): (ar: AsyncResult<T, E>) => AsyncResult<T, F>;
export function mapErr<T, E, F>(
fn: (error: E) => F,
ar: AsyncResult<T, E>,
errorFn?: (thrown: unknown) => F,
): AsyncResult<T, F>;

Defined in: async-result/mapErr.ts

import { err, ok } from '@sandlada/result/factories';
import { fromResult, mapErr, match } from '@sandlada/result/async-result';
const input = fromResult(err('oops'));
const mapped = mapErr((e: string) => e.toUpperCase(), input);
const errLabel = await match({ ok: () => 'ok', err: (e) => e }, mapped);
// errLabel === 'OOPS'
const healthy = mapErr((e: string) => e.toUpperCase(), fromResult(ok(21)));
const passthrough = await match({ ok: (n) => n, err: () => -1 }, healthy);
// passthrough === 21; mapErr leaves Ok values unchanged

Maps the error of an AsyncResult using an async function. Lazy — returns a new AsyncResult without executing the inner computation.

export function mapErrAsync<T, E, F>(
fn: (error: E) => F | Promise<F>,
): (ar: AsyncResult<T, E>) => AsyncResult<T, F>;
export function mapErrAsync<T, E, F>(
fn: (error: E) => F | Promise<F>,
ar: AsyncResult<T, E>,
): AsyncResult<T, F>;

Defined in: async-result/mapErrAsync.ts

import { err, ok } from '@sandlada/result/factories';
import { fromResult, mapErrAsync, match } from '@sandlada/result/async-result';
const input = fromResult(err('oops'));
const mapped = mapErrAsync(async (e: string) => e.toUpperCase(), input);
const errLabel = await match({ ok: () => 'ok', err: (e) => e }, mapped);
// errLabel === 'OOPS'
const healthy = mapErrAsync(async (e: string) => e.toUpperCase(), fromResult(ok(7)));
const passthrough = await match({ ok: (n) => n, err: () => -1 }, healthy);
// passthrough === 7; mapErrAsync skips the mapper on Ok

Maps the success value of an AsyncResult, or returns a default on failure. The mapper may be sync or async. Sync throws from the mapper are caught and converted to the default (canonical AsyncResult catch+convert policy).

export function mapOr<T, U, E>(
defaultValue: U,
fn: (value: T) => U | Promise<U>,
): (ar: AsyncResult<T, E>) => Promise<U>;
export function mapOr<T, U, E>(
defaultValue: U,
fn: (value: T) => U | Promise<U>,
ar: AsyncResult<T, E>,
): Promise<U>;

Defined in: async-result/mapOr.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, mapOr, map } from '@sandlada/result/async-result';
const input = map((x: number) => x, fromResult(ok(21)));
const mapped = await mapOr(-1, (x: number) => x * 2, input);
// mapped === 42
const fallback = await mapOr(-1, (x: number) => x * 2, fromResult(err('x')));
// fallback === -1; mapOr skips the mapper on Err

Maps the success value of an AsyncResult, or computes a default from the error on failure. Both callbacks may be sync or async. Lazy — onErr is only called on failure.

export function mapOrElse<T, U, E>(
onErr: (error: E) => U | Promise<U>,
fn: (value: T) => U | Promise<U>,
): (ar: AsyncResult<T, E>) => Promise<U>;
export function mapOrElse<T, U, E>(
onErr: (error: E) => U | Promise<U>,
fn: (value: T) => U | Promise<U>,
ar: AsyncResult<T, E>,
): Promise<U>;

Defined in: async-result/mapOrElse.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, mapOrElse, map } from '@sandlada/result/async-result';
const input = map((x: number) => x, fromResult(ok(21)));
const mapped = await mapOrElse((e: string) => -1, (x: number) => x * 2, input);
// mapped === 42
const fromErr = await mapOrElse((e: string) => -1, (x: number) => x * 2, fromResult(err('x')));
// fromErr === -1; mapOrElse uses onErr when the carrier is Err

Terminal operator — executes the AsyncResult and applies either the success handler or the error handler. Returns a Promise<U>.

export function match<T, E, U>(
handlers: { ok: (value: T) => U | Promise<U>; err: (error: E) => U | Promise<U> },
): (ar: AsyncResult<T, E>) => Promise<U>;
export function match<T, E, U>(
handlers: { ok: (value: T) => U | Promise<U>; err: (error: E) => U | Promise<U> },
ar: AsyncResult<T, E>,
): Promise<U>;

Defined in: async-result/match.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, map, match } from '@sandlada/result/async-result';
const input = fromResult(ok(21));
const pipeline = map((x: number) => x * 2, input);
const label = await match(
{ ok: (n) => `ok:${n}`, err: (e) => `err:${String(e)}` },
pipeline,
);
// label === 'ok:42'
const failed = map((x: number) => x * 2, fromResult(err('skip')));
const errLabel = await match(
{ ok: (n) => `ok:${n}`, err: (e) => `err:${String(e)}` },
failed,
);
// errLabel === 'err:skip'

Returns res1 if it is Ok, otherwise returns res2. Short-circuiting — res2 is not evaluated when res1 is Ok.

export function or<T, E, F>(
res1: AsyncResult<T, E>,
res2: AsyncResult<T, F>,
): AsyncResult<T, E | F>;

Defined in: async-result/or.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, match, or } from '@sandlada/result/async-result';
const primary = fromResult(ok(1));
const backup = fromResult(ok(2));
const picked = or(primary, backup);
const value = await match({ ok: (n) => n, err: () => -1 }, picked);
// value === 1
const failed = or(fromResult(err('a')), backup);
const recovered = await match({ ok: (n) => `ok:${n}`, err: (e) => `err:${e}` }, failed);
// recovered === 'ok:2'; or runs backup when primary is Err

Recovers from failure by chaining to an alternative AsyncResult or Promise<IResult>. Lazy — returns a new AsyncResult without executing the inner computation.

Throw policy: If fn throws (sync) or rejects (async), the result converts to err(caughtError). Pass errorFn to customise how the thrown value maps onto your error union.

export function orElse<T, E, F>(
fn: (error: E) => AsyncResult<T, F> | Promise<IResultOfT<T, F>>,
errorFn?: (thrown: unknown) => unknown,
): (ar: AsyncResult<T, E>) => AsyncResult<T, E | F>;
export function orElse<T, E, F>(
fn: (error: E) => AsyncResult<T, F> | Promise<IResultOfT<T, F>>,
ar: AsyncResult<T, E>,
errorFn?: (thrown: unknown) => E | F,
): AsyncResult<T, E | F>;

Defined in: async-result/orElse.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, map, orElse, match } from '@sandlada/result/async-result';
const input = fromResult(err('fail'));
const recovered = orElse(() => fromResult(ok(0)), input);
const value = await match({ ok: (n) => n, err: () => -1 }, recovered);
// value === 0
const healthy = orElse(() => fromResult(ok(0)), fromResult(ok(21)));
const doubled = map((x: number) => x * 2, healthy);
const label = await match({ ok: (n) => n, err: () => -1 }, doubled);
// label === 42; orElse leaves Ok carriers untouched

Side-effect on failure (sync or async), ignoring the callback’s result. Calls fn with the error on failure and passes the original result through unchanged. Lazy — returns a new AsyncResult without executing the inner computation.

Throw policy: If the side-effect callback throws (or rejects), the result converts to err(caughtError) (canonical tap/tee policy — see AGENTS.md).

export function orTee<T, E>(
fn: (error: E) => void | unknown | Promise<void | unknown>,
): (ar: AsyncResult<T, E>) => AsyncResult<T, E>;
export function orTee<T, E>(
fn: (error: E) => void | unknown | Promise<void | unknown>,
ar: AsyncResult<T, E>,
): AsyncResult<T, E>;

Defined in: async-result/orTee.ts

import { err, ok } from '@sandlada/result/factories';
import { fromResult, orTee, match } from '@sandlada/result/async-result';
const input = fromResult(err('oops'));
const teed = orTee((_e: string) => undefined, input);
const errLabel = await match({ ok: () => 'ok', err: (e) => e }, teed);
// errLabel === 'oops'
const healthy = orTee((_e: string) => undefined, fromResult(ok(21)));
const label = await match({ ok: (n) => n, err: () => -1 }, healthy);
// label === 21; orTee does not run the side effect on Ok

AsyncResult analogue of swap. Swaps the Ok and Err variants of an AsyncResult. Renamed from swap to align with mapAsync and other async-result operators.

export function swapAsync<T, E>(
ar: AsyncResult<T, E>,
): AsyncResult<E, T>;

Defined in: async-result/swapAsync.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, swapAsync, match } from '@sandlada/result/async-result';
const input = fromResult(ok(5));
const flipped = swapAsync(input);
const errSide = await match({ ok: () => 'ok', err: (e) => `err:${e}` }, flipped);
// errSide === 'err:5'
const fromErr = swapAsync(fromResult(err('x')));
const okSide = await match({ ok: (n) => n, err: () => 'fail' }, fromErr);
// okSide === 'x'; swapAsync moves failure payload to Ok

Side-effect on the success track. Calls fn with the value on success and passes the original result through unchanged. Lazy — returns a new AsyncResult without executing the inner computation.

The callback may be sync or async — fn is awaited internally so async rejections surface before the original result is returned.

Throw policy: If fn throws or rejects, the result converts to err(caughtError). Pass errorFn to customise how the thrown value maps onto your error union — e.g. tap(fn, thrown => new MyError(String(thrown))).

export function tap<T, E>(
fn: (value: T) => void | Promise<void>,
errorFn?: (thrown: unknown) => unknown,
): (ar: AsyncResult<T, E>) => AsyncResult<T, E>;
export function tap<T, E>(
fn: (value: T) => void | Promise<void>,
ar: AsyncResult<T, E>,
errorFn?: (thrown: unknown) => E,
): AsyncResult<T, E>;

Defined in: async-result/tap.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, tap, match } from '@sandlada/result/async-result';
const input = fromResult(ok(42));
const tapped = tap((_v: number) => undefined, input);
const label = await match({ ok: (n) => `ok:${n}`, err: (e) => `err:${String(e)}` }, tapped);
// label === 'ok:42'
const skipped = tap((_v: number) => undefined, fromResult(err('skip')));
const errLabel = await match({ ok: (n) => `ok:${n}`, err: (e) => `err:${e}` }, skipped);
// errLabel === 'err:skip'; tap short-circuits on Err without running the side effect

Side-effect on the success track using an async function. Lazy — returns a new AsyncResult without executing the inner computation.

export function tapAsync<T, E>(
fn: (value: T) => void | Promise<void>,
): (ar: AsyncResult<T, E>) => AsyncResult<T, E>;
export function tapAsync<T, E>(
fn: (value: T) => void | Promise<void>,
ar: AsyncResult<T, E>,
): AsyncResult<T, E>;

Defined in: async-result/tapAsync.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, tapAsync, match } from '@sandlada/result/async-result';
const input = fromResult(ok(42));
const tapped = tapAsync(async (_v: number) => undefined, input);
const label = await match({ ok: (n) => `ok:${n}`, err: (e) => `err:${String(e)}` }, tapped);
// label === 'ok:42'
const skipped = tapAsync(async (_v: number) => undefined, fromResult(err('skip')));
const errLabel = await match({ ok: (n) => `ok:${n}`, err: (e) => `err:${e}` }, skipped);
// errLabel === 'err:skip'; tapAsync does not invoke the callback on Err

Side-effect on the error track. Calls fn with the error on failure and passes the original result through unchanged. Lazy — returns a new AsyncResult without executing the inner computation.

The callback may be sync or async — fn is awaited internally so async rejections surface before the original result is returned.

Throw policy: If fn throws or rejects, the result converts to err(caughtError). Pass errorFn to customise how the thrown value maps onto your error union.

export function tapErr<T, E>(
fn: (error: E) => void | Promise<void>,
errorFn?: (thrown: unknown) => unknown,
): (ar: AsyncResult<T, E>) => AsyncResult<T, E>;
export function tapErr<T, E>(
fn: (error: E) => void | Promise<void>,
ar: AsyncResult<T, E>,
errorFn?: (thrown: unknown) => E,
): AsyncResult<T, E>;

Defined in: async-result/tapErr.ts

import { err, ok } from '@sandlada/result/factories';
import { fromResult, tapErr, match } from '@sandlada/result/async-result';
const input = fromResult(err('oops'));
const tapped = tapErr((_e: string) => undefined, input);
const errLabel = await match({ ok: () => 'ok', err: (e) => e }, tapped);
// errLabel === 'oops'
const healthy = tapErr((_e: string) => undefined, fromResult(ok(21)));
const label = await match({ ok: (n) => n, err: () => -1 }, healthy);
// label === 21; tapErr skips the callback on Ok

Side-effect on the error track using an async function. Lazy — returns a new AsyncResult without executing the inner computation.

export function tapErrAsync<T, E>(
fn: (error: E) => void | Promise<void>,
): (ar: AsyncResult<T, E>) => AsyncResult<T, E>;
export function tapErrAsync<T, E>(
fn: (error: E) => void | Promise<void>,
ar: AsyncResult<T, E>,
): AsyncResult<T, E>;

Defined in: async-result/tapErrAsync.ts

import { err, ok } from '@sandlada/result/factories';
import { fromResult, tapErrAsync, match } from '@sandlada/result/async-result';
const input = fromResult(err('oops'));
const tapped = tapErrAsync(async (_e: string) => undefined, input);
const errLabel = await match({ ok: () => 'ok', err: (e) => e }, tapped);
// errLabel === 'oops'
const healthy = tapErrAsync(async (_e: string) => undefined, fromResult(ok(9)));
const label = await match({ ok: (n) => n, err: () => -1 }, healthy);
// label === 9; tapErrAsync passes Ok through without calling the handler

Extracts the success value from an AsyncResult, or throws on failure. Use sparingly — prefer unwrapOr, unwrapOrElse, or match in most code.

The original error value is preserved as Error.cause (or via the optional formatErr hook), so structured E shapes don’t get clobbered to [object Object]. Pass throwingFn to fully replace the thrown Error class.

export function unwrap<T, E>(ar: AsyncResult<T, E>): Promise<T>;
export function unwrap<T, E>(
ar: AsyncResult<T, E>,
formatErr?: (error: E) => string,
): Promise<T>;
export function unwrap<T, E>(
ar: AsyncResult<T, E>,
formatErr: ((error: E) => string) | undefined,
throwingFn: (info: { message: string; value: E }) => Error,
): Promise<T>;

Defined in: async-result/unwrap.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, map, unwrap } from '@sandlada/result/async-result';
const input = map((x: number) => x * 2, fromResult(ok(21)));
const value = await unwrap(input);
// value === 42
const doomed = unwrap(fromResult(err('boom')));
// await doomed throws Error with cause 'boom'

Extracts the error from a failed AsyncResult, or throws on success. The dual of unwrap.

The original success value is preserved as Error.cause, so structured T shapes don’t get lost. Pass throwingFn to fully replace the thrown Error class.

export function unwrapErr<T, E>(ar: AsyncResult<T, E>): Promise<E>;
export function unwrapErr<T, E>(
ar: AsyncResult<T, E>,
throwingFn?: (info: { message: string; value: T }) => Error,
): Promise<E>;

Defined in: async-result/unwrapErr.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, mapErr, unwrapErr } from '@sandlada/result/async-result';
const input = mapErr((e: string) => e, fromResult(err('boom')));
const error = await unwrapErr(input);
// error === 'boom'
const doomed = unwrapErr(fromResult(ok(42)));
// await doomed throws Error with cause 42

Terminal operator — executes the AsyncResult and returns the success value, or a default value on failure. The default value may be sync or a Promise.

export function unwrapOr<T, E>(
defaultValue: T | Promise<T>,
): (ar: AsyncResult<T, E>) => Promise<T>;
export function unwrapOr<T, E>(
defaultValue: T | Promise<T>,
ar: AsyncResult<T, E>,
): Promise<T>;

Defined in: async-result/unwrapOr.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, map, unwrapOr } from '@sandlada/result/async-result';
const input = map((x: number) => x, fromResult(ok(42)));
const value = await unwrapOr(0, input);
// value === 42
const fallback = await unwrapOr(0, fromResult(err('fail')));
// fallback === 0; unwrapOr returns the default on Err

Extracts the success value from an AsyncResult, or computes a default from the error on failure. The handler may be sync or async. Lazy — the handler is only called on failure.

export function unwrapOrElse<T = unknown, E = unknown, U = unknown>(
onErr: (error: E) => U | Promise<U>,
): <T2 extends T>(ar: AsyncResult<T2, E>) => Promise<T2 | U>;
export function unwrapOrElse<T, E, U>(
onErr: (error: E) => U | Promise<U>,
ar: AsyncResult<T, E>,
): Promise<T | U>;

Defined in: async-result/unwrapOrElse.ts

import { ok, err } from '@sandlada/result/factories';
import { fromResult, map, unwrapOrElse } from '@sandlada/result/async-result';
const input = map((x: number) => x, fromResult(ok(42)));
const value = await unwrapOrElse(() => 0, input);
// value === 42
const computed = await unwrapOrElse((e: string) => -1, fromResult(err('boom')));
// computed === -1; unwrapOrElse calls the handler only on Err