UNPKG

@onrails/result

Version:

Tagged Result / ResultAsync for railway-oriented TypeScript — pure tagged unions, neverthrow-shaped compat shim, FL-friendly

79 lines (76 loc) 4.94 kB
import { R as ResultAsync } from './async-DcXR7k9K.cjs'; import { R as Result } from './types-BQ9vv0nD.cjs'; /** * Tracks whether a {@link Railway} workflow has crossed an async boundary. * Sync workflows return {@link Result}; async workflows return {@link ResultAsync}. */ type RailwayMode = "sync" | "async"; /** * Mode-aware output type for a {@link Railway} workflow: sync mode → * `Result<T, E>`, async mode → `ResultAsync<T, E>`. */ type RailwayOutput<T, E, M extends RailwayMode> = M extends "async" ? ResultAsync<T, E> : Result<T, E>; type BranchFn<C extends object> = (ctx: C) => ResultAsync<unknown, unknown>; type BranchRecord = Record<string, (ctx: never) => ResultAsync<unknown, unknown>>; type BranchOk<R> = R extends (ctx: never) => ResultAsync<infer T, infer _E> ? T : never; type BranchErr<R> = R extends (ctx: never) => ResultAsync<infer _T, infer E> ? E : never; type ParallelOutput<R extends BranchRecord> = { [K in keyof R]: BranchOk<R[K]>; }; type ParallelError<R extends BranchRecord> = BranchErr<R[keyof R]>; /** * Named-context workflow builder. Each step appends a typed field to the * accumulating context object; the workflow tracks sync/async mode so the * final output type ({@link RailwayOutput}) is correct. */ declare class Railway<C extends object, E, M extends RailwayMode> { private readonly state; private constructor(); /** Start an empty sync workflow with no fields in context. */ static empty(): Railway<Record<never, never>, never, "sync">; /** Start a sync workflow with the given context as the initial state. */ static context<C extends object>(context: C): Railway<C, never, "sync">; /** Start a sync workflow with a throwing function. */ static fromSync<K extends string, T, E>(key: K, fn: () => T, onThrow: (error: unknown) => E): Railway<Record<K, T>, E, "sync">; /** Start a sync workflow with a `Result`-returning function. */ static fromResult<K extends string, T, E>(key: K, fn: () => Result<T, E>): Railway<Record<K, T>, E, "sync">; /** Start an async workflow with a `PromiseLike`-returning function. */ static fromPromise<K extends string, T, E>(key: K, fn: () => PromiseLike<T>, onReject: (error: unknown) => E): Railway<Record<K, T>, E, "async">; /** Start an async workflow with a `ResultAsync`-returning function. */ static fromAsync<K extends string, T, E>(key: K, fn: () => ResultAsync<T, E>): Railway<Record<K, T>, E, "async">; /** * Rebuild the workflow in its current mode after transforming the carried * result. Single re-link point for the state/phantom invariant: the mode * tag is preserved verbatim, so `M` still describes the new state — TS * cannot narrow the phantom `M` from the runtime tag, hence the cast. */ private step; /** * Project the carried result into the mode-aware output type. Counterpart * of {@link Railway.step} for terminal steps: the runtime branch matches * the branch `RailwayOutput` picks for `M` (state/phantom invariant), but * TS cannot resolve the conditional on an unresolved `M`, hence the cast. */ private out; /** Lift the carried result into `ResultAsync` for async-upgrading steps. */ private toAsync; /** Pure sync derivation. */ derive<K extends string, T>(key: K, fn: (ctx: C) => T): Railway<C & Record<K, T>, E, M>; /** Throwing sync transform. */ fromSync<K extends string, T, F>(key: K, fn: (ctx: C) => T, onThrow: (error: unknown) => F): Railway<C & Record<K, T>, E | F, M>; /** Sync `Result`-returning step. */ fromResult<K extends string, T, F>(key: K, fn: (ctx: C) => Result<T, F>): Railway<C & Record<K, T>, E | F, M>; /** Promise-returning step — upgrades the workflow to async mode. */ fromPromise<K extends string, T, F>(key: K, fn: (ctx: C) => PromiseLike<T>, onReject: (error: unknown) => F): Railway<C & Record<K, T>, E | F, "async">; /** `ResultAsync`-returning step — upgrades the workflow to async mode. */ fromAsync<K extends string, T, F>(key: K, fn: (ctx: C) => ResultAsync<T, F>): Railway<C & Record<K, T>, E | F, "async">; /** Narrow a nullable context field into a required non-null field. */ require<K extends string, S extends keyof C, F>(key: K, source: S, onMissing: (ctx: C) => F): Railway<C & Record<K, NonNullable<C[S]>>, E | F, M>; /** Run independent `ResultAsync` branches concurrently and merge outputs. */ parallel<R extends Record<string, BranchFn<C>>>(branches: R): Railway<C & ParallelOutput<R>, E | ParallelError<R>, "async">; /** Project the final context into the workflow's output type. */ select<T>(fn: (ctx: C) => T): RailwayOutput<T, E, M>; /** Return the accumulated context as-is. */ done(): RailwayOutput<C, E, M>; } export { type BranchFn, type BranchRecord, type ParallelError, type ParallelOutput, Railway, type RailwayMode, type RailwayOutput };