@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
text/typescript
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 };