Skip to content

Result operators: map, bind, match

Sync operators — barrel export.

Re-exports all synchronous operators for working with Result values.

Logical AND — returns other if r is success, otherwise returns the original failure.

Rust equivalent: result.and(other)

export function and<B, F>(other: IResultOfT<B, F>): <A, E>(r: IResultOfT<A, E>) => IResultOfT<B, E | F>;
export function and<A, E, B, F>(other: IResultOfT<B, F>, r: IResultOfT<A, E>): IResultOfT<B, E | F>;

Defined in: operators/and.ts

import { and, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok(1);
const merged = and(ok(2), input);
const value = match((n) => String(n), (e) => String(e), merged);
// value === '2'
const failed = err('fail');
const kept = and(ok(2), failed);
// kept stays Err('fail'); and short-circuits when the carrier fails

Side-effect on the success track. Calls fn with the value on success and passes the original result through unchanged. Unlike bind, fn’s return value (a IResultOfT) is ignored — even if fn returns a failure, the original success is preserved.

Throw policy: If fn throws, the result converts to err(caughtError). The thrown value is captured verbatim and cast to the declared error type, so without an errorFn the declared type is a structural claim only. Pass errorFn to customise how the thrown value maps onto your error type (canonical tap/tee policy).

Phantom types — B, F: These generics appear only to type fn’s callback return (IResultOfT<B, F>). The operator discards fn’s return value entirely; the produced carrier uses the input’s A, E types. They are intentionally inert — callers who rely on B/F for constraint resolution will be surprised.

Curried form.

export function andTee<A, B, F>(
fn: (a: A) => IResultOfT<B, F>,
errorFn?: (thrown: unknown) => unknown,
): <E>(r: IResultOfT<A, E>) => IResultOfT<A, E>;
export function andTee<A, E, B, F>(
fn: (a: A) => IResultOfT<B, F>,
r: IResultOfT<A, E>,
errorFn?: (thrown: unknown) => E,
): IResultOfT<A, E>;

Defined in: operators/andTee.ts

A

— Input value type (carried through).

B

— Phantom: callback’s success type, ignored at runtime.

F

— Phantom: callback’s error type, ignored at runtime.

E

— Input error type (carried through).

import { andTee, match } from '@sandlada/result/operators';
import { pipe } from '@sandlada/result/composition';
import { ok, err } from '@sandlada/result/factories';
const input = ok('hello');
const teed = pipe(input, andTee((_v) => ok('ignored')));
const kept = match((s) => s, (e) => String(e), teed);
// kept === 'hello'; andTee ignores the callback result
const ignoredErr = pipe(input, andTee((_v) => err('ignored-error')));
// ignoredErr stays Ok('hello'); callback failures are ignored

Direct form.


Side-effect on the success track that can propagate errors. Calls fn with the value on success. If fn returns a failure, that failure replaces the original result (error propagates). If fn returns a success, the original result passes through unchanged.

The key difference from bind is that andThrough preserves the original success value on success, while bind replaces it with fn’s result.

Throw policy: If fn throws, the result converts to err(caughtError). The thrown value is captured verbatim and cast to the declared error type, so without an errorFn the declared type is a structural claim only. Pass errorFn to customise how the thrown value maps onto your error union - e.g. andThrough(fn, thrown => new MyError(String(thrown))).

export function andThrough<A, B, F>(
fn: (a: A) => IResultOfT<B, F>,
errorFn?: (thrown: unknown) => unknown,
): <E>(r: IResultOfT<A, E>) => IResultOfT<A, E | F>;
export function andThrough<A, E, B, F>(
fn: (a: A) => IResultOfT<B, F>,
r: IResultOfT<A, E>,
errorFn?: (thrown: unknown) => E | F,
): IResultOfT<A, E | F>;

Defined in: operators/andThrough.ts

import { andThrough, match } from '@sandlada/result/operators';
import { pipe } from '@sandlada/result/composition';
import { ok, err } from '@sandlada/result/factories';
const input = ok('data');
const through = pipe(
input,
andThrough((v: string) => (v.length > 0 ? ok(v) : err('empty'))),
);
const value = match((s) => s, (e) => String(e), through);
// value === 'data' when the side step succeeds
const bad = ok('');
const failed = pipe(bad, andThrough((v: string) => (v.length > 0 ? ok(v) : err('empty'))));
// failed is Err('empty'); andThrough propagates callback failure

Applicative ap — applies a function wrapped in a Result to a value wrapped in a Result. If either the function or the value 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. This matches the sibling bind operator and supports heterogeneous-error pipelines.

fp-ts equivalent: ap / ap(applyToValue, wrappedFn)

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

Defined in: operators/ap.ts

import { ap, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const fnResult = ok((x: number) => x * 2);
const valueResult = ok(21);
const applied = ap(fnResult, valueResult);
const out = match((n) => String(n), (e) => String(e), applied);
// out === '42'
const fnFailed = err('fn failed');
const short = ap(fnFailed, valueResult);
// short stays Err('fn failed'); ap returns the first failure

Simultaneous map over both success and failure variants.

Throw policy: If either onOk or onErr throws, the result converts to err(caughtError). The thrown value is captured verbatim and cast to the declared error type, so without an errorFn the declared type is a structural claim only. Pass errorFn to customise how the thrown value maps onto your error union.

The curried form defers <A2, E2> to the application site so an input with a different value or error type than the callbacks’ parameter types still typechecks — mirrors map / mapErr’s design.

export function bimap<A, E, C, F>(
onOk: (a: A) => C,
onErr: (e: E) => F,
errorFn?: (thrown: unknown) => unknown,
): <A2 extends A, E2 extends E>(r: IResultOfT<A2, E2>) => IResultOfT<C, F>;
export function bimap<A, E, C, F>(
onOk: (a: A) => C,
onErr: (e: E) => F,
r: IResultOfT<A, E>,
errorFn?: (thrown: unknown) => F,
): IResultOfT<C, F>;

Defined in: operators/bimap.ts

import { bimap, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok(21);
const both = bimap((x: number) => x * 2, (e: string) => `!${e}`, input);
const value = match((n) => String(n), (e) => String(e), both);
// value === '42'
const failed = err('oops');
const mappedErr = bimap((x: number) => x * 2, (e: string) => `!${e}`, failed);
// mappedErr is Err('!oops')

Chains a result-producing function (monadic bind). On success, calls f with the value and returns its result. On failure, short-circuits. The error type widens to E | F.

Throw policy: a synchronous throw from f propagates to the caller — it is NOT caught and converted to Err. Matches the canonical Result throw policy (“sync throws propagate, async rejections are caught”). Use bindAsync or wrap the call site with tryCatch if you need different semantics.

F# equivalent: Result.bind f r

export function bind<A, B, F>(
f: (a: A) => IResultOfT<B, F>,
): <E>(r: IResultOfT<A, E>) => IResultOfT<B, E | F>;
export function bind<A, B, E, F>(
f: (a: A) => IResultOfT<B, F>,
r: IResultOfT<A, E>,
): IResultOfT<B, E | F>;

Defined in: operators/bind.ts

import { bind, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok('Alice');
const checked = bind((name: string) => (name.length > 0 ? ok(name) : err('required')), input);
const label = match((n) => String(n), (e) => String(e), checked);
// label === 'Alice'
const upstream = err('fail');
const skipped = bind((name: string) => ok(name), upstream);
// skipped stays Err('fail'); bind short-circuits on failure

Converts a failure into a success value, maintaining the Result track.

The recovery handler’s return type (B) is independent of the input’s value type (A), so the fallback can be a structurally different shape (a default-object, a sentinel, a value derived from the error, …). The result widens to IResultOfT<A | B, never> because the recovered branch can produce either shape — control-flow at the use site narrows r.isSuccess to A on the original track and r.value to A | B overall (with B reachable only through the recovery path).

This is the canonical “catch error → produce a different shape” recovery. Compare with orElse, which requires the callback to return a new IResultOfT; catchErr simplifies that case by lifting onErr(e) straight into Ok(...) for you.

Throw policy: a synchronous throw from onErr propagates to the caller — it is NOT caught and converted to Err. The recovery handler is the only escape hatch for an Err source; making it swallow its own failures would hide bugs that should surface. This matches the policy documented on unwrapOrElse and orThrow. Wrap your onErr body in a try/catch locally if you need a recoverable fallback.

Curried form. The inner <A> is deferred so the input’s value type is re-inferred at every application site — catchErr(handler)(IResultOfT<A, E>) widens A | B per call instead of locking A at the currying boundary.

export function catchErr<B, E>(
onErr: (e: E) => B,
): <A>(r: IResultOfT<A, E>) => IResultOfT<A | B, never>;
export function catchErr<A, B, E>(
onErr: (e: E) => B,
r: IResultOfT<A, E>,
): IResultOfT<A | B, never>;

Defined in: operators/catchErr.ts

import { catchErr, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const failed = err('boom');
const recovered = catchErr((e: string) => 0, failed);
const value = match((n) => String(n), (e) => String(e), recovered);
// value === '0'
const input = ok(42);
const passthrough = catchErr((e: string) => 0, input);
// passthrough stays Ok(42); catchErr passes success through unchanged

Direct form. A is inferred from the supplied result; the recovery widens to A | B. The error track collapses to never because the recovery always succeeds.


Maps an array with a function returning a Result, and keeps only the success values.

F# equivalent: List.choose

Throw policy: pure collector with no Err channel in its return type — a synchronous throw from fn propagates to the caller; Err values are skipped and collection continues.

export function choose<A, B, E>(
fn: (a: A) => IResultOfT<B, E>,
): (items: readonly A[]) => B[];
export function choose<A, B, E>(
fn: (a: A) => IResultOfT<B, E>,
items: readonly A[],
): B[];

Defined in: operators/choose.ts

import { choose, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const items = [1, -2, 3];
const picked = choose((x: number) => (x > 0 ? ok(x * 2) : err('neg')), items);
const summary = match((n) => n * 10, (e) => 0, ok(picked.length));
// picked is [2, 6]; summary === 20
const none = choose((x: number) => err('nope'), [1, 2]);
// none is []; Err values are skipped, not collected

Returns true if the result is success and the value equals target.

Rust equivalent: result.contains(target)

export function contains<A>(target: A): <E>(r: IResultOfT<A, E>) => boolean;
export function contains<A, E>(target: A, r: IResultOfT<A, E>): boolean;

Defined in: operators/contains.ts

import { contains, unwrapOr } from '@sandlada/result/operators';
import { pipe } from '@sandlada/result/composition';
import { ok, err } from '@sandlada/result/factories';
const input = ok(42);
const hit = pipe(input, contains(42));
const fallback = unwrapOr(false, err('missing'));
// hit === true; fallback === false
const miss = pipe(ok(7), contains(42));
// miss === false

Returns true if the result is success and the predicate holds.

Rust equivalent: result.is_ok_and(predicate)

export function exists<A>(predicate: (a: A) => boolean): <E>(r: IResultOfT<A, E>) => boolean;
export function exists<A, E>(predicate: (a: A) => boolean, r: IResultOfT<A, E>): boolean;

Defined in: operators/exists.ts

import { exists, unwrapOr } from '@sandlada/result/operators';
import { pipe } from '@sandlada/result/composition';
import { ok, err } from '@sandlada/result/factories';
const input = ok(42);
const positive = pipe(input, exists((x: number) => x > 0));
const defaultFlag = unwrapOr(false, err('n/a'));
// positive === true; defaultFlag === false
const negative = pipe(ok(-1), exists((x: number) => x > 0));
// negative === false

Panics on failure — throws a TypeError with the given message. Returns the value on success.

Pass throwingFn to customise the error class — e.g. expect(msg, r, info => new MyError(info.message, info.value)). Without throwingFn, a built-in TypeError is thrown.

Rust equivalent: result.expect("msg")

export function expect<A, E>(msg: string): <A2 extends A>(r: IResultOfT<A2, E>) => A2;
export function expect<A, E>(msg: string, r: IResultOfT<A, E>): A;
export function expect<A, E>(
msg: string,
r: IResultOfT<A, E>,
throwingFn: (info: { message: string; value: E }) => Error,
): A;

Defined in: operators/expect.ts

import { expect, map } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok(42);
const mapped = map((n: number) => n + 1, input);
const value = expect('should succeed', mapped);
// value === 43
const failed = err('boom');
void expect('should fail', failed);
// throws TypeError — expect panics on failure

Panics on success — throws a TypeError with the given message. Returns the error on failure.

Pass throwingFn to customise the error class — e.g. expectErr(msg, r, info => new MyError(info.message, info.value)). Without throwingFn, a built-in TypeError is thrown.

Rust equivalent: result.expect_err("msg")

export function expectErr<A, E>(msg: string): <E2 extends E>(r: IResultOfT<A, E2>) => E2;
export function expectErr<A, E>(msg: string, r: IResultOfT<A, E>): E;
export function expectErr<A, E>(
msg: string,
r: IResultOfT<A, E>,
throwingFn: (info: { message: string; value: A }) => Error,
): E;

Defined in: operators/expectErr.ts

import { expectErr, mapErr } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = err('boom');
const wrapped = mapErr((e: string) => `[${e}]`, input);
const message = expectErr('should fail', wrapped);
// message === '[boom]'
const success = ok(1);
void expectErr('should panic', success);
// throws TypeError — expectErr panics on success

Filters a success value with a predicate. If the predicate holds, the original success passes through. If it fails, returns err(errorFn(value)). Failures pass through unchanged.

Rust equivalent: result.filter_or_else(errorFn, predicate)

Throw policy: If either the predicate or errorFn throws, the result converts to err(caughtError). The thrown value is captured verbatim and cast to the declared error type, so without a throwErrorFn the declared type is a structural claim only. Pass throwErrorFn to customise how the thrown value maps onto your error union.

export function filterOrElse<A, E>(
predicate: (a: A) => boolean,
errorFn: (a: A) => E,
throwErrorFn?: (thrown: unknown) => unknown,
): (r: IResultOfT<A, E>) => IResultOfT<A, E>;
export function filterOrElse<A, E>(
predicate: (a: A) => boolean,
errorFn: (a: A) => E,
r: IResultOfT<A, E>,
throwErrorFn?: (thrown: unknown) => E,
): IResultOfT<A, E>;

Defined in: operators/filterOrElse.ts

import { filterOrElse, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok(5);
const kept = filterOrElse(
(x: number) => x > 0,
(x: number) => `${x} is not positive`,
input,
);
const value = match((n) => String(n), (e) => String(e), kept);
// value === '5'
const bad = ok(-1);
const rejected = filterOrElse(
(x: number) => x > 0,
(x: number) => `${x} is not positive`,
bad,
);
// rejected is Err('-1 is not positive')
const upstream = err('down');
const passthrough = filterOrElse((x: number) => x > 0, (x: number) => `${x}`, upstream);
// passthrough stays Err('down'); filterOrElse short-circuits on failure

Flattens a nested result: IResultOfT<IResultOfT<A, E>, E> → IResultOfT<A, E>.

Single-step only: flatten unwraps exactly one layer. If the inner value is itself a Result (e.g. Result<Result<Result<A, E>, E>, E>), call flatten repeatedly until you reach the target depth.

Rust equivalent: result.flatten()

export function flatten<A, E>(r: IResultOfT<IResultOfT<A, E>, E>): IResultOfT<A, E>;

Defined in: operators/flatten.ts

import { flatten, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const nested = ok(ok(42));
const flat = flatten(nested);
const value = match((n) => String(n), (e) => String(e), flat);
// value === '42'
const failed = err('outer');
const outerOnly = flatten(failed);
// outerOnly stays Err('outer'); flatten short-circuits on failure

Transforms the success value. If the result is a failure, it is passed through unchanged.

Throw policy: If f throws, the result converts to err(caughtError). The thrown value is captured verbatim and cast to the declared error type, so without an errorFn the declared type is a structural claim only. Pass errorFn to customise how the thrown value maps onto your error union - e.g. map(f, e => new MyError(String(e))). An errorFn that throws itself is collapsed the same way: the mapper’s error becomes the error of the result, it never escapes the Result wrapper.

F# equivalent: Result.map f r

export function map<A, B>(
f: (a: A) => B,
errorFn?: (thrown: unknown) => unknown,
): <E>(r: IResultOfT<A, E>) => IResultOfT<B, E>;
export function map<A, B, E>(
f: (a: A) => B,
r: IResultOfT<A, E>,
errorFn?: (thrown: unknown) => E,
): IResultOfT<B, E>;

Defined in: operators/map.ts

import { map, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok(5);
const mapped = map((x: number) => x * 2, input);
const label = match((n) => `ok:${n}`, (e) => `err:${String(e)}`, mapped);
// label === 'ok:10'
const failure = err('skip');
const passthrough = map((x: number) => x * 2, failure);
// passthrough stays Err('skip'); map short-circuits on failure

Transforms the error value. If the result is a success, it is passed through unchanged.

Throw policy: a synchronous throw from f propagates to the caller — it is NOT caught and converted to Err. This matches the canonical Result throw policy (“synchronous throws propagate, async rejections are caught”). If you need f’s throw to become an Err, wrap the call site with tryCatch.

F# equivalent: Result.mapError f r

export function mapErr<E, F>(f: (e: E) => F): <A>(r: IResultOfT<A, E>) => IResultOfT<A, F>;
export function mapErr<A, E, F>(f: (e: E) => F, r: IResultOfT<A, E>): IResultOfT<A, F>;

Defined in: operators/mapErr.ts

import { mapErr, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = err('boom');
const wrapped = mapErr((e: string) => `[wrapped] ${e}`, input);
const label = match((n) => String(n), (e) => e, wrapped);
// label === '[wrapped] boom'
const success = ok(1);
const passthrough = mapErr((e: string) => e, success);
// passthrough stays Ok(1); mapErr short-circuits on success

Maps the success value, or returns defaultValue on failure. Equivalent to map(fn).unwrapOr(defaultValue) but more efficient.

export function mapOr<A, B, E>(
defaultValue: B,
fn: (a: A) => B,
): (r: IResultOfT<A, E>) => B;
export function mapOr<A, B, E>(
defaultValue: B,
fn: (a: A) => B,
r: IResultOfT<A, E>,
): B;

Defined in: operators/mapOr.ts

import { mapOr, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok(5);
const mapped = mapOr(-1, (x: number) => x * 2, input);
const label = match((n) => String(n), (e) => String(e), ok(mapped));
// label === '10'
const failed = err('boom');
const fallback = mapOr(-1, (x: number) => x * 2, failed);
// fallback === -1

Maps the success value, or computes a default from the error on failure. Equivalent to map(fn).unwrapOrElse(onErr) but more efficient.

export function mapOrElse<A, B, E>(
onErr: (e: E) => B,
fn: (a: A) => B,
): (r: IResultOfT<A, E>) => B;
export function mapOrElse<A, B, E>(
onErr: (e: E) => B,
fn: (a: A) => B,
r: IResultOfT<A, E>,
): B;

Defined in: operators/mapOrElse.ts

import { mapOrElse, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok(5);
const mapped = mapOrElse((e: string) => 0, (x: number) => x * 2, input);
const label = match((n) => String(n), (e) => String(e), ok(mapped));
// label === '10'
const failed = err('boom');
const fromErr = mapOrElse((e: string) => 0, (x: number) => x * 2, failed);
// fromErr === 0

Terminal handler — pattern-matches on both success and failure cases. Supports both positional (onOk, onErr, r?) and object ({ ok, err }, r?) handler shapes, matching the convention used by match in @sandlada/result/async-result. Prefer the object form for consistency across the library.

F# equivalent: function Ok v → onOk v | Error e → onErr e

export function match<A, E, C>(
onOk: (a: A) => C,
onErr: (e: E) => C,
): (r: IResultOfT<A, E>) => C;
export function match<A, E, C>(
onOk: (a: A) => C,
onErr: (e: E) => C,
r: IResultOfT<A, E>,
): C;
export function match<A, E, C>(
handlers: MatchHandlers<A, E, C>,
): (r: IResultOfT<A, E>) => C;
export function match<A, E, C>(
handlers: MatchHandlers<A, E, C>,
r: IResultOfT<A, E>,
): C;

Defined in: operators/match.ts

import { match, map } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const success = ok(42);
const mapped = map((n: number) => n + 1, success);
const fromObject = match(
{ ok: (v) => `success: ${v}`, err: (e) => `failure: ${String(e)}` },
mapped,
);
// fromObject === 'success: 43'
const failure = err('no');
const fromPositional = match((v) => `ok:${v}`, (e) => `err:${String(e)}`, failure);
// fromPositional === 'err:no'

Logical OR — returns other if r is failure, otherwise returns the original success.

Rust equivalent: result.or(other)

The curried form defers E (the input error type) to the application site, so the returned function preserves heterogeneous inference rather than widening the input’s error to unknown.

export function or<A, F>(other: IResultOfT<A, F>): <E>(r: IResultOfT<A, E>) => IResultOfT<A, E | F>;
export function or<A, E, F>(other: IResultOfT<A, F>, r: IResultOfT<A, E>): IResultOfT<A, E | F>;

Defined in: operators/or.ts

import { or, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = err('fail');
const fallback = or(ok(2), input);
const value = match((n) => String(n), (e) => String(e), fallback);
// value === '2'
const success = ok(1);
const kept = or(ok(2), success);
// kept stays Ok(1); or keeps the carrier when it succeeds

Error recovery — tries an alternative path on failure. On failure, calls f with the error and its result replaces this one. On success, passes through unchanged.

Throw policy: If f throws, the result converts to err(caughtError). The thrown value is captured verbatim and cast to the declared error type, so without an errorFn the declared type is a structural claim only. Pass errorFn to customise how the thrown value maps onto your error union.

export function orElse<E, B, F>(
f: (e: E) => IResultOfT<B, F>,
errorFn?: (thrown: unknown) => unknown,
): <A>(r: IResultOfT<A, E>) => IResultOfT<A | B, F>;
export function orElse<A, E, B, F>(
f: (e: E) => IResultOfT<B, F>,
r: IResultOfT<A, E>,
errorFn?: (thrown: unknown) => F,
): IResultOfT<A | B, F>;

Defined in: operators/orElse.ts

import { orElse, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = err('boom');
const fallback = orElse((e: string) => ok('default'), input);
const value = match((s) => s, (e) => String(e), fallback);
// value === 'default'
const success = ok('live');
const kept = orElse((_e: string) => ok('default'), success);
// kept stays Ok('live'); orElse passes success through unchanged

Side-effect on the error track. Calls fn with the error on failure and passes the original result through unchanged. Unlike orElse, fn’s return value (a IResultOfT) is ignored — even if fn returns a success, the original failure is preserved.

Throw policy: If fn throws, the result converts to err(caughtError). The thrown value is captured verbatim and cast to the declared error type, so without an errorFn the declared type is a structural claim only. Pass errorFn to customise how the thrown value maps onto your error type (canonical tap/tee policy - see AGENTS.md).

export function orTee<E, B, F>(
fn: (e: E) => IResultOfT<B, F>,
errorFn?: (thrown: unknown) => unknown,
): <A>(r: IResultOfT<A, E>) => IResultOfT<A, E>;
export function orTee<A, E, B, F>(
fn: (e: E) => IResultOfT<B, F>,
r: IResultOfT<A, E>,
errorFn?: (thrown: unknown) => E,
): IResultOfT<A, E>;

Defined in: operators/orTee.ts

import { orTee, match } from '@sandlada/result/operators';
import { pipe } from '@sandlada/result/composition';
import { ok, err } from '@sandlada/result/factories';
const input = err('boom');
const teed = pipe(input, orTee((_e) => ok('ignored')));
const label = match((n) => String(n), (e) => e, teed);
// label === 'boom'; orTee preserves the original failure
const ignoredErr = pipe(input, orTee((_e) => err('ignored-error')));
// ignoredErr stays Err('boom'); callback results are ignored

Unwraps a result, throwing the error on failure. orThrow throws the error directly (requires E extends Error). orThrowWith transforms the error via a callback before throwing.

Rust equivalent: result.unwrap() / result.expect("msg")

export function orThrow<T, E extends Error>(r: IResultOfT<T, E>): T;

Defined in: operators/orThrow.ts

import { orThrow, orThrowWith, map } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok(42);
const mapped = map((n: number) => n + 1, input);
const value = orThrow(mapped);
// value === 43
const failed = err(new Error('boom'));
void orThrow(failed);
// throws Error('boom')
const soft = err('fail');
void orThrowWith((e: string) => new Error(`Custom: ${e}`), soft);
// throws Error('Custom: fail')

Unwraps the success value, throwing a custom error on failure. Transforms the error via errorFn before throwing.

Data-last curried — supports both direct and partial application.

export function orThrowWith<T, E>(
errorFn: (error: E) => Error,
): <T2 extends T>(r: IResultOfT<T2, E>) => T2;
export function orThrowWith<T, E>(
errorFn: (error: E) => Error,
r: IResultOfT<T, E>,
): T;

Defined in: operators/orThrow.ts

errorFn

Transforms the error into an Error to throw.

r

The result to unwrap (omitted for curried form).

The transformed error if the result is a failure.


Partitions an array of Results into two arrays: successes and errors.

Rust equivalent: results.into_iter().partition_map() / Iter::partition_result()

export function separate<T, E>(results: readonly IResultOfT<T, E>[]): { ok: T[]; err: E[] };

Defined in: operators/separate.ts

import { separate, unwrapOr } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const rows = [ok(1), err('a'), ok(2), err('b')];
const parts = separate(rows);
const firstOk = unwrapOr(0, ok(parts.ok[0] ?? 0));
// parts.ok is [1, 2] and parts.err is ['a', 'b']; firstOk === 1
const empty = separate([]);
// empty is { ok: [], err: [] }

Swaps success and failure: Ok(v) → Err(v), Err(e) → Ok(e).

Rust equivalent: result.swap()

export function swap<A, E>(r: IResultOfT<A, E>): IResultOfT<E, A>;

Defined in: operators/swap.ts

import { swap, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok(42);
const flipped = swap(input);
const errSide = match((n) => String(n), (e) => String(e), flipped);
// errSide === '42'; Ok value moves to the error track
const failed = err('oops');
const okSide = match((n) => String(n), (e) => e, swap(failed));
// okSide === 'oops'; Err error moves to the success track

Side-effect on the success track. Calls fn with the value on success and passes the original result through unchanged.

Throw policy: If fn throws, the result converts to err(caughtError). Pass errorFn to customise how the thrown value maps onto your error type — e.g. tap(v => persist(v), thrown => new MyError(String(thrown))) — so the result’s error matches your domain shape (canonical tap/tee policy).

Without errorFn the thrown value is cast to the input result’s E. For a typed E like a tagged-union MyError, prefer supplying errorFn so the runtime payload is not silently widened.

export function tap<A>(
fn: (a: A) => void,
errorFn?: (thrown: unknown) => unknown,
): <E>(r: IResultOfT<A, E>) => IResultOfT<A, E>;
export function tap<A, E>(
fn: (a: A) => void,
r: IResultOfT<A, E>,
errorFn?: (thrown: unknown) => E,
): IResultOfT<A, E>;

Defined in: operators/tap.ts

import { tap, match } from '@sandlada/result/operators';
import { pipe } from '@sandlada/result/composition';
import { ok, err } from '@sandlada/result/factories';
const input = ok('hello');
const touched = pipe(input, tap((_v: string) => undefined));
const kept = match((s) => s, (e) => String(e), touched);
// kept === 'hello'; tap returns the original result
const failure = err('down');
const passthrough = pipe(failure, tap((_v: string) => undefined));
// passthrough stays Err('down'); tap skips the callback on failure

Side-effect on the failure track. Calls fn with the error on failure and passes the original result through unchanged.

Throw policy: If fn throws, the result converts to err(caughtError). The thrown value is captured verbatim and cast to the declared error type, so without an errorFn the declared type is a structural claim only. Pass errorFn to customise how the thrown value maps onto your error type (canonical tap/tee policy - see AGENTS.md).

export function tapErr<E>(
fn: (e: E) => void,
errorFn?: (thrown: unknown) => unknown,
): <A>(r: IResultOfT<A, E>) => IResultOfT<A, E>;
export function tapErr<A, E>(
fn: (e: E) => void,
r: IResultOfT<A, E>,
errorFn?: (thrown: unknown) => E,
): IResultOfT<A, E>;

Defined in: operators/tapErr.ts

import { tapErr, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = err('boom');
const logged = tapErr((_e: string) => undefined, input);
const label = match((n) => String(n), (e) => e, logged);
// label === 'boom'; tapErr preserves the failure
const success = ok(1);
const unchanged = tapErr((_e: string) => undefined, success);
// unchanged stays Ok(1); tapErr skips the callback on success

Applies a Result-returning function to every element in an array and collects the results. Short-circuits on the first failure (like Promise.all).

The callback fn receives each element and its zero-based index in the source array — useful for debugging or for results that depend on positional context.

Rust equivalent: iter.map(fn).collect::<Result<Vec<_>, _>>()

export function traverseArray<A, B, E>(
fn: (item: A, index: number) => IResultOfT<B, E>,
): (items: readonly A[]) => IResultOfT<B[], E>;
export function traverseArray<A, B, E>(
fn: (item: A, index: number) => IResultOfT<B, E>,
items: readonly A[],
): IResultOfT<B[], E>;

Defined in: operators/traverseArray.ts

import { traverseArray, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const items = [1, 2, 3];
const all = traverseArray((x: number) => (x > 0 ? ok(x * 2) : err('neg')), items);
const length = match((arr) => arr.length, (e) => 0, all);
// length === 3; all is Ok([2, 4, 6])
const badItems = [1, -1, 3];
const stopped = traverseArray((x: number) => (x > 0 ? ok(x * 2) : err('neg')), badItems);
// stopped is Err('neg'); traverseArray short-circuits on first failure

Throws on failure — returns the success value on success. Unlike unwrap, this function does not constrain the error type E. The raw error object is thrown directly (not wrapped in a TypeError).

Use with care — this is primarily a testing/escape-hatch utility. Prefer unwrap for code paths where the error type is Error.

export function unsafeUnwrap<A, E>(r: IResultOfT<A, E>): A;

Defined in: operators/unsafeUnwrap.ts

import { unsafeUnwrap, map } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok(42);
const mapped = map((n: number) => n + 1, input);
const value = unsafeUnwrap(mapped);
// value === 43
const failed = err('boom');
void unsafeUnwrap(failed);
// throws 'boom'

Throws on success — returns the error value on failure. Unlike unwrapErr, this function does not constrain the error type E. The raw success value is thrown directly (not wrapped in a TypeError).

Use with care — this is primarily a testing/escape-hatch utility. Prefer unwrapErr for code paths where the error type is Error.

export function unsafeUnwrapErr<A, E>(r: IResultOfT<A, E>): E;

Defined in: operators/unsafeUnwrapErr.ts

import { unsafeUnwrapErr, mapErr } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = err('boom');
const wrapped = mapErr((e: string) => `[${e}]`, input);
const message = unsafeUnwrapErr(wrapped);
// message === '[boom]'
const success = ok(42);
void unsafeUnwrapErr(success);
// throws 42

Panics on failure — throws a TypeError with the error payload. Returns the value on success.

Pass throwingFn to customise the error class — e.g. unwrap(r, info => new MyError(info.message, info.value)). Without throwingFn, a built-in TypeError is thrown with String(r.error) interpolated.

Rust equivalent: result.unwrap()

export function unwrap<T, E>(r: IResultOfT<T, E>, throwingFn?: (info: { message: string; value: E }) => Error): T;

Defined in: operators/unwrap.ts

import { unwrap, map } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok(42);
const mapped = map((n: number) => n + 1, input);
const value = unwrap(mapped);
// value === 43
const failed = err('boom');
void unwrap(failed);
// throws TypeError — unwrap panics when the carrier fails

Panics on success — throws a TypeError. Returns the error on failure.

Pass throwingFn to customise the error class — e.g. unwrapErr(r, info => new MyError(info.message, info.value)). Without throwingFn, a built-in TypeError is thrown.

Rust equivalent: result.unwrap_err()

export function unwrapErr<A, E>(r: IResultOfT<A, E>, throwingFn?: (info: { message: string; value: A }) => Error): E;

Defined in: operators/unwrapErr.ts

import { unwrapErr, mapErr } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = err('boom');
const wrapped = mapErr((e: string) => e, input);
const message = unwrapErr(wrapped);
// message === 'boom'
const success = ok(1);
void unwrapErr(success);
// throws TypeError — unwrapErr panics on success

Extracts the value on success, or returns a default on failure. Never throws.

The A generic is widened from the literal — e.g. unwrapOr(0) infers A = number, not A = 0, so the operator composes with IResultOfT<number, E> inputs. Without this widening, bare usage locks the success type to the literal value of the default.

F# equivalent: Result.defaultValue def r

export function unwrapOr<A>(defaultValue: A): <E>(r: IResultOfT<A, E>) => A;
export function unwrapOr<A, E>(defaultValue: A, r: IResultOfT<A, E>): A;

Defined in: operators/unwrapOr.ts

import { unwrapOr } from '@sandlada/result/operators';
import { pipe } from '@sandlada/result/composition';
import { ok, err } from '@sandlada/result/factories';
const success = ok(42);
const value = pipe(success, unwrapOr(0));
// value === 42
const failure = err('boom');
const fallback = pipe(failure, unwrapOr(0));
// fallback === 0

Extracts the value on success, or computes a default from the error on failure (lazy).

Throw policy: Like match, exceptions thrown by onErr propagate to the caller. Ensure onErr does not throw, or wrap it with tryCatch first if you need a recoverable fallback.

F# equivalent: Result.defaultWith f r

export function unwrapOrElse<A, E>(onErr: (e: E) => A): (r: IResultOfT<A, E>) => A;
export function unwrapOrElse<A, E>(onErr: (e: E) => A, r: IResultOfT<A, E>): A;

Defined in: operators/unwrapOrElse.ts

import { unwrapOrElse, map } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok(42);
const mapped = map((n: number) => n, input);
const value = unwrapOrElse((e: Error) => 0, mapped);
// value === 42
const failed = err(new Error('boom'));
const recovered = unwrapOrElse((e: Error) => 0, failed);
// recovered === 0

Unzips a Result containing a tuple into a tuple of Results.

Rust equivalent: Result::unzip

export function unzip<A, B, E>(
r: IResultOfT<readonly [A, B], E>,
): [IResultOfT<A, E>, IResultOfT<B, E>];

Defined in: operators/unzip.ts

import { unzip, match } from '@sandlada/result/operators';
import { ok, err } from '@sandlada/result/factories';
const input = ok([1, 'a'] as const);
const [left, right] = unzip(input);
const sum = match((n) => n, (e) => 0, left);
// sum === 1; left is Ok(1) and right is Ok('a')
const failed = err('e');
const [l, r] = unzip(failed);
// l and r are both Err('e')