@onrails/result
Version:
Tagged Result / ResultAsync for railway-oriented TypeScript — pure tagged unions, neverthrow-shaped compat shim, FL-friendly
492 lines (485 loc) • 18.3 kB
TypeScript
import { R as ResultAsync, I as InferErr, a as InferOk } from './async-Ci6WSL5-.js';
import { R as Result, U as UnexpectedError, E as Err, O as Ok } from './types-BQ9vv0nD.js';
export { flow } from './pipe.js';
export { $, tryGen, yieldResult } from './try-gen.js';
/**
* @onrails/result — THE lift module. Every "value → {@link ResultAsync}"
* entry point is defined exactly once, here: `okAsync`, `errAsync`,
* `fromPromise`, `fromSafePromise`, `fromResult`, `fromAsync`, `asyncAfter`,
* `tryAsync`.
*/
/**
* Lifts a value into an `Ok` async result.
*
* @example
* ```ts
* const r = okAsync(42); // ResultAsync<number, never>
* ```
*/
declare const okAsync: typeof ResultAsync.ok;
/**
* Lifts a value into an `Err` async result.
*
* @example
* ```ts
* const r = errAsync({ kind: "not_found" as const });
* ```
*/
declare const errAsync: typeof ResultAsync.err;
/**
* Wraps a `PromiseLike<T>` into a {@link ResultAsync}. Reject reasons go
* through `onReject` to become typed `Err`s; success becomes `Ok<T>`.
*
* @example
* ```ts
* const body = fromPromise(
* fetch(url).then((r) => r.text()),
* (e): NetworkError => ({ kind: "network", cause: String(e) }),
* );
* ```
*/
declare const fromPromise: typeof ResultAsync.fromPromise;
/**
* Wraps a `PromiseLike<T>` that **never rejects** into {@link ResultAsync}.
* Skips the `onReject` mapper. Use only when the promise is provably safe.
*/
declare const fromSafePromise: typeof ResultAsync.fromSafePromise;
/**
* Lifts an already-settled sync {@link Result} into a {@link ResultAsync}, so
* it can be chained alongside async steps in a railway.
*
* @example
* ```ts
* const ra = fromResult(ok(1)); // ResultAsync<number, never>
* await fromResult(err(error)).resolve(); // Err(error)
* ```
*/
declare const fromResult: typeof ResultAsync.fromResult;
/**
* Bind a sync {@link Result} into an async step without widening defects.
* Dual-form: data-first for one-shots, data-last (curried) for `pipe`/`flow`.
*
* @example
* ```ts
* asyncAfter(result, (u) => tryAsync(db.insert(u), toErr)); // data-first
* asyncAfter((u) => tryAsync(db.insert(u), toErr)); // data-last
* ```
*/
declare const asyncAfter: {
<T, U, E, F>(result: Result<T, E>, fn: (value: T) => ResultAsync<U, F>): ResultAsync<U, E | F>;
<T, U, F>(fn: (value: T) => ResultAsync<U, F>): <E>(result: Result<T, E>) => ResultAsync<U, E | F>;
};
type AnyResult = Result<unknown, unknown>;
/**
* Lifts a `(...args) => Promise<Result<T, E>>` function (typical of interop or
* boundary code) into one returning `(...args) => ResultAsync<T, E>`. Expected
* `Err`s pass through; an unexpected promise rejection (a defect) is routed via
* `onDefect`, defaulting to {@link UnexpectedError} and widening the error union.
*
* @param fn - a function returning a promise that already yields a `Result`
* @param onDefect - maps an unexpected rejection to the `Err` channel
*
* @example
* ```ts
* const loadUser = fromAsync(
* (id: string): Promise<Result<User, NotFound>> => api.getUser(id),
* );
* const ra = loadUser("u1"); // ResultAsync<User, NotFound | UnexpectedError>
* ```
*/
declare const fromAsync: <A extends readonly unknown[], R extends AnyResult>(fn: (...args: A) => Promise<R>, onDefect?: (error: unknown) => InferErr<R> | UnexpectedError) => ((...args: A) => ResultAsync<InferOk<R>, InferErr<R> | UnexpectedError>);
/**
* Convenience wrapper over {@link fromPromise} with default `Error`
* normalization. Call without `onReject` to get `ResultAsync<T, Error>`,
* or pass a custom mapper for a typed error.
*
* @example
* ```ts
* // Default: rejection → Err(Error)
* const a = tryAsync(db.users.insert(row));
*
* // Custom: typed error
* const b = tryAsync(db.users.insert(row), (e): DbError => ({
* kind: "db",
* cause: e,
* }));
* ```
*/
declare function tryAsync<T>(promise: PromiseLike<T>): ResultAsync<T, Error>;
declare function tryAsync<T, E>(promise: PromiseLike<T>, onReject: (error: unknown) => E): ResultAsync<T, E>;
/**
* @onrails/result — sync aggregation, one module, two axes:
*
* short-circuit (first `Err` wins) — {@link combine} / {@link combineTuple}
* accumulate expected failures — {@link validateAll} / {@link validateTuple}
*
* The accumulate pair collects every failure into a readonly array by
* default, or folds them with an explicit `combineErrors` when given one.
*/
/**
* Combines a homogeneous array of results into a single result holding an
* array of the `Ok` values, in input order. Short-circuits on the first `Err`
* (first failure wins). For heterogeneous tuples that preserve per-index
* types, use {@link combineTuple}.
*
* @example
* ```ts
* combine([ok(1), ok(2)]); // Ok([1, 2])
* combine([ok(1), err("e")]); // Err("e")
* ```
*/
declare const combine: <T, E>(results: readonly Result<T, E>[]) => Result<T[], E>;
type CombineTuple<R extends readonly Result<unknown, unknown>[]> = Result<{
[K in keyof R]: InferOk<R[K]>;
}, {
[K in keyof R]: InferErr<R[K]>;
}[number]>;
/**
* Heterogeneous tuple combine — like {@link combine} but preserves each
* branch's `Ok` type by position, so the result destructures type-safely.
* Short-circuits on the first `Err` in input order (neverthrow-style).
*
* @example
* ```ts
* const r = combineTuple([ok(1), ok("x")] as const);
* // Result<readonly [number, string], never>
* if (isOk(r)) {
* const [n, s] = r.value; // typed per position
* }
* ```
*/
declare const combineTuple: <const R extends readonly Result<unknown, unknown>[]>(results: R) => CombineTuple<R>;
/**
* Accumulate independent validation failures. Returns `Ok<T[]>` only when
* every input is `Ok`. Without a combiner, failures are collected into a
* readonly array; with `combineErrors` they are folded into a single `E`.
*
* Unlike {@link combine}, this does **not** short-circuit on first failure —
* use for independent checks where you want to report all problems at once.
*
* @example
* ```ts
* const checks: Result<string, string[]>[] = [ok("Ada"), err(["age required"])];
*
* validateAll(checks);
* // Result<string[], readonly string[][]>
*
* validateAll(checks, (left, right) => [...left, ...right]);
* // Result<string[], string[]>
* ```
*/
declare function validateAll<T, E>(results: readonly Result<T, E>[]): Result<T[], readonly E[]>;
declare function validateAll<T, E>(results: readonly Result<T, E>[], combineErrors: (left: E, right: E) => E): Result<T[], E>;
/**
* Tuple-preserving variant of {@link validateAll}. Heterogeneous input tuple
* → preserved `Ok` tuple shape. Without a combiner, the error union is
* collected into a readonly array; with `combineErrors`, all inputs must
* share the error type `E` and failures fold into a single `E`.
*
* @example
* ```ts
* const name: Result<string, string[]> = ok("Ada");
* const age: Result<number, string[]> = ok(36);
*
* validateTuple([name, age] as const);
* // Result<readonly [string, number], readonly string[][]>
*
* validateTuple([name, age] as const, (l, r) => [...l, ...r]);
* // Result<readonly [string, number], string[]>
* ```
*/
declare function validateTuple<const R extends readonly Result<unknown, unknown>[]>(results: R): Result<{
[K in keyof R]: InferOk<R[K]>;
}, readonly InferErr<R[number]>[]>;
declare function validateTuple<const R extends readonly Result<unknown, unknown>[], E = InferErr<R[number]>>(results: R & readonly Result<unknown, E>[], combineErrors: (left: E, right: E) => E): Result<{
[K in keyof R]: InferOk<R[K]>;
}, E>;
/**
* Lifts a value into the success track.
*
* @example
* ```ts
* const r = ok(42); // Result<number, never>
* const typed: Result<number, "parse"> = ok(1);
* ```
*/
declare const ok: <T, E = never>(value: T) => Result<T, E>;
/**
* Fantasy Land `pure` — alias of {@link ok}. One lift name shared across the
* trio (`of` / `Maybe.of` / `ResultAsync.of`) for generic and FL-style code.
*
* @example
* ```ts
* const r = of(42); // Result<number, never> — identical to ok(42)
* ```
*/
declare const of: <T, E = never>(value: T) => Result<T, E>;
/**
* Lifts a value into the error track.
*
* @example
* ```ts
* const r = err({ kind: "parse", message: "bad json" });
* // Result<never, { kind: "parse"; message: string }>
* ```
*/
declare const err: <T = never, E = unknown>(error: E) => Result<T, E>;
/**
* Type-narrowing predicate: returns `true` when the result is `Ok`.
*
* @example
* ```ts
* if (isOk(r)) {
* console.log(r.value); // narrowed to Ok branch
* }
* ```
*/
declare const isOk: <T, E>(result: Result<T, E>) => result is Ok<T, E>;
/**
* Type-narrowing predicate: returns `true` when the result is `Err`.
*
* @example
* ```ts
* if (isErr(r)) {
* metrics.inc("error", { kind: r.error.kind });
* }
* ```
*/
declare const isErr: <T, E>(result: Result<T, E>) => result is Err<T, E>;
/**
* Transform the `Ok` value, passing `Err` through unchanged. Dual-form:
* call data-first or curried (for use with {@link pipe}).
*
* @example
* ```ts
* map(ok(2), (n) => n * 3); // Ok 6 — data-first
* pipe(ok("x"), map((s) => s.length));// Ok 1 — curried
* ```
*/
declare const map: {
<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;
<T, U>(fn: (value: T) => U): <E>(result: Result<T, E>) => Result<U, E>;
};
/**
* Transform the `Err` value, passing `Ok` through unchanged. Useful for
* unifying heterogeneous failure types into one app-level union.
*
* @example
* ```ts
* type AppError = { kind: "http"; status: number } | { kind: "parse" };
* pipe(
* fetchSync(url), // Result<Body, { status: number }>
* mapErr((e): AppError => ({ kind: "http", status: e.status })),
* );
* ```
*/
declare const mapErr: {
<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F>;
<E, F>(fn: (error: E) => F): <T>(result: Result<T, E>) => Result<T, F>;
};
/**
* Transform both tracks at once — `Ok` via `onOk`, `Err` via `onErr`.
* Equivalent to `mapErr(onErr)(map(onOk)(result))` but in one pass.
*
* @example
* ```ts
* bimap(parsed, (cfg) => cfg.name, (e) => ({ kind: "input", cause: e }));
* ```
*/
declare const bimap: {
<T, U, E, F>(result: Result<T, E>, onOk: (value: T) => U, onErr: (error: E) => F): Result<U, F>;
<T, U, E, F>(onOk: (value: T) => U, onErr: (error: E) => F): (result: Result<T, E>) => Result<U, F>;
};
/**
* Canonical bind (Fantasy Land `chain`). Chains a Result-returning step,
* widening the error union to `E | F`. Short-circuits on `Err`.
*
* @example
* ```ts
* flatMap(parseInput(raw), (data) =>
* data.id != null ? ok(data) : err({ kind: "missing_id" as const }),
* );
* // Result<Data, ParseError | { kind: "missing_id" }>
* ```
*/
declare const flatMap: {
<T, U, E, F>(result: Result<T, E>, fn: (value: T) => Result<U, F>): Result<U, E | F>;
<T, U, F>(fn: (value: T) => Result<U, F>): <E>(result: Result<T, E>) => Result<U, E | F>;
};
/**
* Error-track bind — runs `fn` only when the result is `Err`, allowing
* a failed workflow to recover to `Ok` or remap the failure. Mirror of
* {@link flatMap} on the error channel.
*
* @example
* ```ts
* recover(networkResult, (e) =>
* e.kind === "rate_limit" ? ok(cachedBody) : err(e),
* );
* ```
*/
declare const recover: {
<T, E, F>(result: Result<T, E>, fn: (error: E) => Result<T, F>): Result<T, F>;
<T, E, F>(fn: (error: E) => Result<T, F>): (result: Result<T, E>) => Result<T, F>;
};
/**
* Observe the `Ok` value for side effects (logging, metrics) without
* changing the carried value. Passes `Err` through untouched.
*
* @example
* ```ts
* pipe(
* parseConfig(raw),
* tap((cfg) => log.info({ msg: "parsed", name: cfg.name })),
* flatMap(validate),
* );
* ```
*/
declare const tap: {
<T, E>(result: Result<T, E>, fn: (value: T) => void): Result<T, E>;
<T>(fn: (value: T) => void): <E>(result: Result<T, E>) => Result<T, E>;
};
/**
* Observe the `Err` value for side effects (logging, metrics) without
* changing the carried error. Passes `Ok` through untouched.
*
* @example
* ```ts
* pipe(
* loadUser(id),
* tapErr((e) => metrics.inc("user.load.fail", { kind: e.kind })),
* );
* ```
*/
declare const tapErr: {
<T, E>(result: Result<T, E>, fn: (error: E) => void): Result<T, E>;
<E>(fn: (error: E) => void): <T>(result: Result<T, E>) => Result<T, E>;
};
/**
* Terminal collapse — fold both tracks into a single value. Dual-form:
* 3-args data-first, 2-args curried for {@link pipe}. Returns whatever
* the handlers return.
*
* For files that also import `match` from `ts-pattern`, use a namespace
* import (`import * as R from "@onrails/result"` → `R.match`) to dissolve
* the collision.
*
* @example
* ```ts
* const html = match(parsed, (cfg) => render(cfg), (e) => renderError(e));
* ```
*/
declare const match: {
<T, E, U>(result: Result<T, E>, onOk: (value: T) => U, onErr: (error: E) => U): U;
<T, E, U>(onOk: (value: T) => U, onErr: (error: E) => U): (result: Result<T, E>) => U;
};
/**
* Returns the `Ok` value, or `defaultValue` when the result is `Err`.
*
* @example
* ```ts
* unwrapOr(parsedSetting, "default-value");
* ```
*/
declare const unwrapOr: {
<T, E>(result: Result<T, E>, defaultValue: T): T;
<T>(defaultValue: T): <E>(result: Result<T, E>) => T;
};
/**
* Test/assert helper — returns the `Ok` value, or **throws the original `Err`
* value** when called on an `Err`. This is the assertion tier (RFC 0001 §4):
* intended for `*.spec.ts` / `*.test.ts`, where throwing fails the test loudly.
* In business logic prefer {@link match} or {@link unwrapOr}; the lint plugins
* flag `unwrapOk` outside test files.
*
* @returns the unwrapped `Ok` value
* @throws the carried `Err` value when the result is `Err`
*
* @example
* ```ts
* // in a *.spec.ts
* expect(unwrapOk(ok(5))).toBe(5);
* expect(() => unwrapOk(err(error))).toThrow(error);
* ```
*/
declare function unwrapOk<T, E>(result: Result<T, E>): T;
/** Alias of {@link unwrapOk} for syntax cohesion with `@onrails/maybe`. */
declare const unwrap: typeof unwrapOk;
/**
* Test/assert helper — returns the `Err` value, or **throws a `TypeError`**
* when called on an `Ok`. Mirror of {@link unwrapOk} on the error track and
* part of the same assertion tier (RFC 0001 §4): intended for `*.spec.ts` /
* `*.test.ts`. Prefer {@link match} / {@link unwrapOr} in business logic; the
* lint plugins flag `unwrapErr` outside test files.
*
* @returns the unwrapped `Err` value
* @throws `TypeError` when the result is `Ok`
*
* @example
* ```ts
* // in a *.spec.ts
* expect(unwrapErr(err("x"))).toBe("x");
* expect(() => unwrapErr(ok(5))).toThrow(TypeError);
* ```
*/
declare function unwrapErr<T, E>(result: Result<T, E>): E;
/**
* Wraps a throwing sync function, returning a function that produces a
* {@link Result} instead of throwing. Thrown errors pass through `onThrow` to
* become a typed `Err`; a normal return becomes `Ok`. The neverthrow analogue
* is `Result.fromThrowable`.
*
* @param fn - the throwing function to wrap
* @param onThrow - maps a thrown value to the `Err` channel
* @returns a function with `fn`'s parameters that returns `Result<ReturnType, E>`
*
* @example
* ```ts
* type ParseError = { kind: "parse"; message: string };
* const parse = trySync(
* JSON.parse,
* (e): ParseError => ({ kind: "parse", message: String(e) }),
* );
* parse("{}"); // Ok({})
* parse("nope"); // Err({ kind: "parse", … })
* ```
*/
declare function trySync<A extends readonly unknown[], T, E>(fn: (...args: A) => T, onThrow: (error: unknown) => E): (...args: A) => Result<T, E>;
declare function trySync<F extends (...args: never) => unknown, E>(fn: F, onThrow: (error: unknown) => E): (...args: Parameters<F>) => Result<ReturnType<F>, E>;
/**
* Variadic value-first pipe — threads `value` through up to nine unary fns,
* left-to-right. Use {@link pipe} when you already have a starting value;
* use {@link flow} to define a reusable composed function with no value yet.
*
* @example
* ```ts
* pipe(
* parseConfig(raw),
* map((cfg) => cfg.name),
* flatMap((name) => (name ? ok(name) : err({ kind: "empty" as const }))),
* tap(log),
* );
* ```
*/
declare function pipe<A>(value: A): A;
declare function pipe<A, B>(value: A, ab: (a: A) => B): B;
declare function pipe<A, B, C>(value: A, ab: (a: A) => B, bc: (b: B) => C): C;
declare function pipe<A, B, C, D>(value: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D): D;
declare function pipe<A, B, C, D, E>(value: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E): E;
declare function pipe<A, B, C, D, E, F>(value: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F): F;
declare function pipe<A, B, C, D, E, F, G>(value: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F, fg: (f: F) => G): G;
declare function pipe<A, B, C, D, E, F, G, H>(value: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F, fg: (f: F) => G, gh: (g: G) => H): H;
declare function pipe<A, B, C, D, E, F, G, H, I>(value: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F, fg: (f: F) => G, gh: (g: G) => H, hi: (h: H) => I): I;
/**
* Debug printer — renders a result as `Ok(…)` / `Err(…)` for logs. Payloads
* print as JSON (values are plain data by design); non-JSON payloads fall
* back to `String(...)`.
*
* @example
* ```ts
* show(ok(1)); // 'Ok(1)'
* show(err({ kind: "e" })); // 'Err({"kind":"e"})'
* ```
*/
declare const show: <T, E>(result: Result<T, E>) => string;
export { Err, InferErr, InferOk, Ok, Result, ResultAsync, UnexpectedError, asyncAfter, bimap, combine, combineTuple, err, errAsync, flatMap, fromAsync, fromPromise, fromResult, fromSafePromise, isErr, isOk, map, mapErr, match, of, ok, okAsync, pipe, recover, show, tap, tapErr, tryAsync, trySync, unwrap, unwrapErr, unwrapOk, unwrapOr, validateAll, validateTuple };