UNPKG

@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
import { R as ResultAsync, I as InferErr, a as InferOk } from './async-DcXR7k9K.cjs'; import { R as Result, U as UnexpectedError, E as Err, O as Ok } from './types-BQ9vv0nD.cjs'; export { flow } from './pipe.cjs'; export { $, tryGen, yieldResult } from './try-gen.cjs'; /** * @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 };