Install
pnpm dlx shadcn@latest add https://archyn.net/r/race.jsonCopy the files below into the target paths shown in each tab.
import { err, ok, type Result } from "./core/result";import { type BehaviorRecord, type EmptyRaceError, type RaceError, type RaceOutput, type RecordContext, type RecordInput, abortChildren, normalizedConcurrency, raceFailure, raceSuccess, runChild,} from "./core/record";import { type Behavior, type RuntimeContext, type RuntimeError, resultFromSignal, thrown,} from "./core/runtime";
/** * Runs keyed behaviors and returns the first settled child. * * On success, the returned value includes the winning key and value. On failure, the returned error * includes the failing key and error. Remaining active children are aborted. * * @typeParam B - Behavior record type. * @param behaviors - Keyed behaviors to race with the same input. * @param options - Optional concurrency limit. * @returns A behavior producing a keyed success or keyed failure. * * @example * ```ts * const fastestProfile = race({ * primary: loadFromPrimary, * replica: loadFromReplica, * }); * ``` */export function race<const B extends BehaviorRecord>( behaviors: B, options?: { concurrency?: number },): Behavior<RecordInput<B>, RaceOutput<B>, RaceError<B> | EmptyRaceError, RecordContext<B>> { return async (input, context) => { if (context.signal.aborted) { return resultFromSignal(context.signal); }
return runRace(behaviors, input, context, options?.concurrency); };}
function runRace<const B extends BehaviorRecord>( behaviors: B, input: RecordInput<B>, context: RuntimeContext<RecordContext<B>>, concurrency: number | undefined,): Promise<Result<RaceOutput<B>, RaceError<B> | EmptyRaceError | RuntimeError>> { const keys = Object.keys(behaviors) as Array<keyof B>; const childControllers: AbortController[] = [];
return new Promise((resolve) => { let nextIndex = 0; let active = 0; let settled = false; const limit = normalizedConcurrency(concurrency, keys.length);
const finish = ( result: Result<RaceOutput<B>, RaceError<B> | EmptyRaceError | RuntimeError>, ) => { if (settled) { return; }
settled = true; context.signal.removeEventListener("abort", onAbort); abortChildren(childControllers, result.ok ? undefined : result.error); resolve(result); };
const onAbort = () => finish(resultFromSignal(context.signal)); context.signal.addEventListener("abort", onAbort, { once: true });
const startNext = () => { if (settled) { return; }
while (active < limit && nextIndex < keys.length) { const key = keys[nextIndex]; if (key === undefined) { break; }
const behavior = behaviors[key]; if (behavior === undefined) { break; }
nextIndex += 1; active += 1; runChild( behavior as unknown as Behavior<RecordInput<B>, unknown, unknown, RecordContext<B>>, input, context, childControllers, ) .then((result) => { active -= 1;
if (settled) { return; }
if (result.ok) { finish(ok(raceSuccess(key, result.value) as RaceOutput<B>)); return; }
finish(err(raceFailure(key, result.error) as RaceError<B>)); }) .catch((error: unknown) => { active -= 1; const failure = thrown(error); finish(err(raceFailure(key, failure.error) as RaceError<B>)); }); } };
if (keys.length === 0) { finish(err({ kind: "empty_race" })); return; }
startNext(); });}import { err, ok, type Result } from "./result";import { abortController, createLinkedController, type Behavior, type BehaviorContext, type BehaviorError, type BehaviorInput, type BehaviorOutput, type RuntimeContext, type RuntimeError, contextWithSignal, runBehavior, thrown,} from "./runtime";
/** * Behavior shape accepted by keyed record helpers. * * This advanced utility intentionally erases specific behavior types so record-level mapped types * can reconstruct input, output, error, and context information per key. */export type AnyBehavior = Behavior<never, unknown, unknown, object>;
/** * Object map of named behaviors for `all`, `allSettled`, and `race`. * * Keys are preserved in output and error records, which makes parallel results easy to inspect. */export type BehaviorRecord = Record<string, AnyBehavior>;
type UnionToIntersection<T> = (T extends unknown ? (value: T) => void : never) extends ( value: infer I,) => void ? I : never;
/** * Intersects the input types of every behavior in a record. * * @typeParam B - Behavior record to inspect. */export type RecordInput<B extends BehaviorRecord> = UnionToIntersection<BehaviorInput<B[keyof B]>>;
/** * Intersects the custom context types of every behavior in a record. * * @typeParam B - Behavior record to inspect. */export type RecordContext<B extends BehaviorRecord> = UnionToIntersection< BehaviorContext<B[keyof B]>> & object;
/** * Maps each behavior key to its successful output type. * * @typeParam B - Behavior record to inspect. */export type OutputRecord<B extends BehaviorRecord> = { [K in keyof B]: BehaviorOutput<B[K]>;};
/** * Unions the expected domain error types of every behavior in a record. * * @typeParam B - Behavior record to inspect. */export type ErrorUnion<B extends BehaviorRecord> = BehaviorError<B[keyof B]>;
/** * Maps each behavior key to the full result returned by that child. * * `allSettled` uses this type to preserve successes, domain failures, and runtime failures for * every child behavior. * * @typeParam B - Behavior record to inspect. */export type SettledRecord<B extends BehaviorRecord> = { [K in keyof B]: Result<BehaviorOutput<B[K]>, BehaviorError<B[K]> | RuntimeError>;};
/** * Keyed success value returned by `race`. * * @typeParam B - Behavior record to inspect. */export type RaceOutput<B extends BehaviorRecord> = { [K in keyof B]: { key: K; value: BehaviorOutput<B[K]>; };}[keyof B];
/** * Keyed failure value returned by `race`. * * @typeParam B - Behavior record to inspect. */export type RaceError<B extends BehaviorRecord> = { [K in keyof B]: { key: K; error: BehaviorError<B[K]> | RuntimeError; };}[keyof B];
/** * Domain error returned when `race` is called with an empty behavior record. */export type EmptyRaceError = { kind: "empty_race";};
/** * Normalizes a requested concurrency value for keyed parallel helpers. * * Non-finite or missing values run all children. Finite values are floored and clamped to the * inclusive range from one to the number of children. * * @param value - Requested concurrency. * @param count - Number of available children. * @returns Safe concurrency limit. */export function normalizedConcurrency(value: number | undefined, count: number): number { if (count === 0) { return 0; }
if (value === undefined || !Number.isFinite(value)) { return count; }
return Math.max(1, Math.min(count, Math.floor(value)));}
/** * Runs a child behavior with a linked abort controller. * * This advanced runtime helper lets parent helpers cancel child work independently while still * respecting parent cancellation. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param behavior - Child behavior to execute. * @param input - Input passed to the child. * @param context - Parent runtime context. * @param childControllers - Mutable collection of child controllers owned by the parent helper. * @returns Child result including runtime errors. */export function runChild<I, O, E, C extends object>( behavior: Behavior<I, O, E, C>, input: I, context: RuntimeContext<C>, childControllers: AbortController[],): Promise<Result<O, E | RuntimeError>> { const { controller, unlink } = createLinkedController(context.signal); childControllers.push(controller);
return runBehavior(behavior, input, contextWithSignal(context, controller.signal)).finally( unlink, );}
/** * Aborts every tracked child controller. * * Parent helpers call this when a parallel operation settles and remaining work should stop. * * @param childControllers - Controllers to abort. * @param reason - Abort reason propagated to each child. */export function abortChildren(childControllers: AbortController[], reason: unknown): void { for (const controller of childControllers) { abortController(controller, reason); }}
/** * Creates a keyed race success payload. * * @typeParam K - Winning key type. * @typeParam V - Winning value type. * @param key - Behavior key that settled successfully. * @param value - Successful child value. * @returns Keyed success payload. */export function raceSuccess<K extends PropertyKey, V>( key: K, value: V,): { key: K; value: V;} { return { key, value };}
/** * Creates a keyed race failure payload. * * @typeParam K - Failing key type. * @typeParam E - Failure value type. * @param key - Behavior key that failed. * @param error - Child domain or runtime error. * @returns Keyed failure payload. */export function raceFailure<K extends PropertyKey, E>( key: K, error: E,): { key: K; error: E;} { return { key, error };}
/** * Re-exports core result helpers used by advanced record internals. * * These exports are documented so generated API coverage stays complete for the installed core * record module. */export { err, ok, thrown };/** * Represents a value that may be returned directly or through a Promise. * * Flow helpers accept `Awaitable` callbacks so synchronous and asynchronous logic can share the * same behavior authoring APIs. * * @typeParam T - Value produced either immediately or asynchronously. */export type Awaitable<T> = T | Promise<T>;
/** * Successful `Result` variant. * * `Ok` carries the typed success value through Flow pipelines. Use `ok` to create branded values * that can be detected by `isResult`. * * @typeParam T - Success value type. */export type Ok<T> = { ok: true; value: T;};
/** * Failed `Result` variant. * * `Err` carries expected domain errors. Runtime failures such as cancellation and thrown * exceptions are modeled separately by the runtime helpers. * * @typeParam E - Expected error value type. */export type Err<E> = { ok: false; error: E;};
/** * Explicit success-or-failure outcome used throughout Flow. * * Flow behaviors return `Result` values instead of throwing for expected application failures. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. */export type Result<T, E> = Ok<T> | Err<E>;
/** * Structural result shape accepted by `from`. * * Use this when adapting unbranded result-like values into branded Flow results. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. */export type ResultLike<T, E> = Ok<T> | Err<E>;
const resultBrand = Symbol("archyn.result");
/** * Creates a branded successful result. * * Branded results are recognized by `isResult`, which allows `task` callbacks to distinguish * explicit `Result` returns from plain successful values. * * @typeParam T - Success value type. * @param value - Value to carry in the success variant. * @returns A branded `Ok` result. * * @example * ```ts * const result = ok({ id: "user_1" }); * ``` */export function ok<T>(value: T): Ok<T> { return brandResult({ ok: true, value });}
/** * Creates a branded failed result. * * Use `err` for expected domain failures that should stay typed and recoverable by Flow helpers. * * @typeParam E - Expected error value type. * @param error - Error value to carry in the failure variant. * @returns A branded `Err` result. * * @example * ```ts * const result = err({ kind: "not-found" }); * ``` */export function err<E>(error: E): Err<E> { return brandResult({ ok: false, error });}
/** * Converts a structural result-like value into a branded Flow result. * * This is useful when adapting results from another library or from plain object literals. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @param result - Structural success or failure object. * @returns A branded Flow `Result`. */export function from<T, E>(result: ResultLike<T, E>): Result<T, E> { return result.ok ? ok(result.value) : err(result.error);}
/** * Narrows a result to the successful variant. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @param result - Result to inspect. * @returns `true` when the result is `Ok`. */export function isOk<T, E>(result: Result<T, E>): result is Ok<T> { return result.ok;}
/** * Narrows a result to the failed variant. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @param result - Result to inspect. * @returns `true` when the result is `Err`. */export function isErr<T, E>(result: Result<T, E>): result is Err<E> { return !result.ok;}
/** * Exhaustively handles both result variants and returns one value. * * `match` keeps success and error handling explicit without manual branching at the call site. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @typeParam R - Handler return type. * @param result - Result to handle. * @param handlers - Callbacks for the success and failure variants. * @returns The value returned by the matching handler. * * @example * ```ts * const label = match(result, { * ok: (user) => user.name, * err: (error) => error.kind, * }); * ``` */export function match<T, E, R>( result: Result<T, E>, handlers: { ok: (value: T) => R; err: (error: E) => R; },): R { return result.ok ? handlers.ok(result.value) : handlers.err(result.error);}
/** * Returns the success value or throws the error value. * * Prefer typed recovery helpers for expected failures. `unwrap` is best reserved for tests, * boundaries that intentionally throw, or places where a failure is impossible by construction. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @param result - Result to unwrap. * @returns The success value. * @throws The `Err.error` value when the result failed. */export function unwrap<T, E>(result: Result<T, E>): T { if (result.ok) { return result.value; }
throw result.error;}
/** * Returns the success value or a fallback. * * Use this when a failure can be safely collapsed into a default value. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @param result - Result to read. * @param fallback - Value returned when the result failed. * @returns The success value or fallback. * * @example * ```ts * const count = unwrapOr(result, 0); * ``` */export function unwrapOr<T, E>(result: Result<T, E>, fallback: T): T { return result.ok ? result.value : fallback;}
/** * Namespace-style collection of core result helpers. * * This object is convenient when passing the result toolkit as a single value. The individual * named functions remain the preferred imports for tree-shakeable application code. */export const Result = { ok, err, from, isOk, isErr, match, unwrap, unwrapOr,};
/** * Detects branded Flow results created by `ok`, `err`, or `from`. * * `task` uses this guard to preserve explicit results while wrapping plain callback returns in * `ok`. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @param value - Plain value or potential result. * @returns `true` when the value is a branded Flow `Result`. */export function isResult<T, E>(value: T | Result<T, E>): value is Result<T, E> { return typeof value === "object" && value !== null && resultBrand in value;}
function brandResult<R extends Ok<unknown> | Err<unknown>>(result: R): R { Object.defineProperty(result, resultBrand, { value: true, });
return result;}import { type Awaitable, type Err, type Result, err } from "./result";
/** * Runtime error returned when execution is cancelled through an `AbortSignal`. * * Cancellation is cooperative. Flow helpers check the signal before and after async work and pass * linked signals to child behaviors where appropriate. */export type Cancelled = { kind: "cancelled"; reason?: unknown;};
/** * Runtime error returned when user code throws. * * Thrown exceptions are kept separate from expected domain errors so recovery helpers do not hide * programming or platform failures. */export type Thrown = { kind: "thrown"; error: unknown;};
/** * Runtime-level failure union added around every behavior's domain error type. * * Domain errors describe expected application failures. `RuntimeError` describes cancellation or * unexpected thrown exceptions observed by the Flow runtime. */export type RuntimeError = Cancelled | Thrown;
/** * Execution context passed to every behavior. * * The context always includes an `AbortSignal` and may include application-specific fields. * * @typeParam C - Custom context object type. */export type RuntimeContext<C extends object = {}> = C & { signal: AbortSignal;};
/** * Executable Flow workflow unit. * * A behavior receives typed input, reads runtime context, and resolves to a `Result` containing * either typed output, typed domain error, or a runtime error. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. */export type Behavior<I, O, E = unknown, C extends object = {}> = ( input: I, context: RuntimeContext<C>,) => Promise<Result<O, E | RuntimeError>>;
/** * Extracts the input type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorInput<T> = T extends Behavior<infer I, infer _O, infer _E, infer _C> ? I : never;
/** * Extracts the output type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorOutput<T> = T extends Behavior<infer _I, infer O, infer _E, infer _C> ? O : never;
/** * Extracts the expected domain error type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorError<T> = T extends Behavior<infer _I, infer _O, infer E, infer _C> ? E : never;
/** * Extracts the custom context type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorContext<T> = T extends Behavior<infer _I, infer _O, infer _E, infer C> ? C : never;
/** * Executes a behavior with runtime error protection. * * The runner checks cancellation before and after behavior execution and converts thrown * exceptions into `Thrown` results. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param behavior - Behavior to execute. * @param input - Input passed to the behavior. * @param context - Runtime context containing an `AbortSignal`. * @returns The behavior result, cancellation result, or thrown runtime result. */export async function runBehavior<I, O, E, C extends object>( behavior: Behavior<I, O, E, C>, input: I, context: RuntimeContext<C>,): Promise<Result<O, E | RuntimeError>> { if (context.signal.aborted) { return resultFromSignal(context.signal); }
try { const result = await behavior(input, context);
if (context.signal.aborted) { return resultFromSignal(context.signal); }
return result; } catch (error) { if (context.signal.aborted) { return resultFromSignal(context.signal); }
return thrown(error); }}
/** * Defines a behavior from a callback that returns a `Result`. * * Use `define` for low-level helpers that already operate in the result channel. Most application * code should use `task`, which also accepts plain successful values. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param run - Callback invoked for each behavior execution. * @returns A behavior protected by cancellation and thrown-exception handling. */export function define<I, O, E = unknown, C extends object = {}>( run: (input: I, context: RuntimeContext<C>) => Awaitable<Result<O, E>>,): Behavior<I, O, E, C> { return async (input, context) => { if (context.signal.aborted) { return resultFromSignal(context.signal); }
try { const result = await run(input, context);
if (context.signal.aborted) { return resultFromSignal(context.signal); }
return result; } catch (error) { if (context.signal.aborted) { return cancelled(context.signal.reason); }
return thrown(error); } };}
/** * Runs a behavior with an optional application context. * * If no signal is provided, Flow creates a fresh non-aborted signal. Expected failures remain in * the returned `Result`; thrown exceptions are converted to `Thrown`. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param behavior - Behavior to execute. * @param input - Input passed to the behavior. * @param context - Optional custom context and optional `AbortSignal`. * @returns A result containing behavior output, domain error, or runtime error. * * @example * ```ts * const result = await run(loadUser, "user_1"); * ``` */export async function run<I, O, E, C extends object = {}>( behavior: Behavior<I, O, E, C>, input: I, context: C & { signal?: AbortSignal } = {} as C & { signal?: AbortSignal },): Promise<Result<O, E | RuntimeError>> { const { signal = new AbortController().signal, ...rest } = context;
return runBehavior(behavior, input, { ...(rest as C), signal, });}
/** * Creates a cancellation result. * * @param reason - Optional cancellation reason from an `AbortSignal`. * @returns A failed result containing `Cancelled`. */export function cancelled(reason?: unknown): Err<Cancelled> { if (reason === undefined) { return err({ kind: "cancelled" }); }
return err({ kind: "cancelled", reason });}
/** * Narrows an error value to `Cancelled`. * * @param error - Error value to inspect. * @returns `true` when the value is a cancellation runtime error. */export function isCancelled(error: unknown): error is Cancelled { return ( typeof error === "object" && error !== null && "kind" in error && error.kind === "cancelled" );}
/** * Creates a thrown-exception runtime result. * * @param error - Exception or thrown value. * @returns A failed result containing `Thrown`. */export function thrown(error: unknown): Err<Thrown> { return err({ kind: "thrown", error });}
/** * Narrows an error value to `Thrown`. * * @param error - Error value to inspect. * @returns `true` when the value is a thrown runtime error. */export function isThrown(error: unknown): error is Thrown { return typeof error === "object" && error !== null && "kind" in error && error.kind === "thrown";}
/** * Narrows an error value to a Flow runtime error. * * Recovery helpers use this guard so they only handle expected domain errors. * * @param error - Error value to inspect. * @returns `true` for `Cancelled` or `Thrown`. */export function isRuntimeError(error: unknown): error is RuntimeError { return isCancelled(error) || isThrown(error);}
/** * Converts an aborted signal into a cancellation result. * * @param signal - Aborted signal whose reason should be preserved. * @returns A failed result containing `Cancelled`. */export function resultFromSignal(signal: AbortSignal): Err<Cancelled> { return cancelled(signal.reason);}
/** * Replaces the signal on a runtime context. * * This advanced helper is used by timeout and parallel helpers when they create child abort * controllers. * * @typeParam C - Custom context type. * @param context - Existing runtime context. * @param signal - Replacement signal. * @returns A context with the same custom fields and the replacement signal. */export function contextWithSignal<C extends object>( context: RuntimeContext<C>, signal: AbortSignal,): RuntimeContext<C> { return { ...context, signal };}
/** * Aborts a controller only when it has not already been aborted. * * @param controller - Controller to abort. * @param reason - Optional abort reason. */export function abortController(controller: AbortController, reason?: unknown): void { if (!controller.signal.aborted) { controller.abort(reason); }}
/** * Links a parent signal to a child controller. * * The returned cleanup function removes the listener when the child work has settled. * * @param parent - Parent signal to observe. * @param controller - Child controller to abort when the parent aborts. * @returns Cleanup function that unlinks the abort listener. */export function linkAbortSignal(parent: AbortSignal, controller: AbortController): () => void { if (parent.aborted) { abortController(controller, parent.reason); return () => {}; }
const onAbort = () => abortController(controller, parent.reason); parent.addEventListener("abort", onAbort, { once: true });
return () => parent.removeEventListener("abort", onAbort);}
/** * Creates a child controller linked to a parent signal. * * This advanced helper powers timeout, race, and parallel execution helpers. * * @param parent - Parent signal to link from. * @returns The child controller and an unlink cleanup function. */export function createLinkedController(parent: AbortSignal): { controller: AbortController; unlink: () => void;} { const controller = new AbortController(); const unlink = linkAbortSignal(parent, controller);
return { controller, unlink };}Update the import paths to match your project setup.
Run keyed Behavior values and settle with the first child result.
Usage
race starts child Behavior values and returns the first success or failure, tagged with the child key that settled first. It aborts the remaining active children.
Use it when:
- Use the first available data source.
- Race alternative strategies and keep the winning key.
- Stop slower work after the first useful result or failure.
import { race } from "@/lib/flow/race";import { task } from "@/lib/flow/task";
const fastestProfile = race({ edge: task(() => fetch("/api/edge-profile").then((r) => r.json())), origin: task(() => fetch("/api/profile").then((r) => r.json())),});Behavior
race starts children up to the concurrency limit and settles as soon as one child succeeds or fails.
The first failure is wrapped with its key. Empty races return empty_race. Parent cancellation aborts children and returns Cancelled.
Types
The result is keyed so callers know which behavior settled first.
Import from @/lib/flow/race.
Composition Notes
Use race for replicas, fallbacks, and first-available data sources.
Do not use race when slower successful results are still required; use allSettled.
Related APIs
Source
import { err, ok, type Result } from "./core/result";import { type BehaviorRecord, type EmptyRaceError, type RaceError, type RaceOutput, type RecordContext, type RecordInput, abortChildren, normalizedConcurrency, raceFailure, raceSuccess, runChild,} from "./core/record";import { type Behavior, type RuntimeContext, type RuntimeError, resultFromSignal, thrown,} from "./core/runtime";
/** * Runs keyed behaviors and returns the first settled child. * * On success, the returned value includes the winning key and value. On failure, the returned error * includes the failing key and error. Remaining active children are aborted. * * @typeParam B - Behavior record type. * @param behaviors - Keyed behaviors to race with the same input. * @param options - Optional concurrency limit. * @returns A behavior producing a keyed success or keyed failure. * * @example * ```ts * const fastestProfile = race({ * primary: loadFromPrimary, * replica: loadFromReplica, * }); * ``` */export function race<const B extends BehaviorRecord>( behaviors: B, options?: { concurrency?: number },): Behavior<RecordInput<B>, RaceOutput<B>, RaceError<B> | EmptyRaceError, RecordContext<B>> { return async (input, context) => { if (context.signal.aborted) { return resultFromSignal(context.signal); }
return runRace(behaviors, input, context, options?.concurrency); };}
function runRace<const B extends BehaviorRecord>( behaviors: B, input: RecordInput<B>, context: RuntimeContext<RecordContext<B>>, concurrency: number | undefined,): Promise<Result<RaceOutput<B>, RaceError<B> | EmptyRaceError | RuntimeError>> { const keys = Object.keys(behaviors) as Array<keyof B>; const childControllers: AbortController[] = [];
return new Promise((resolve) => { let nextIndex = 0; let active = 0; let settled = false; const limit = normalizedConcurrency(concurrency, keys.length);
const finish = ( result: Result<RaceOutput<B>, RaceError<B> | EmptyRaceError | RuntimeError>, ) => { if (settled) { return; }
settled = true; context.signal.removeEventListener("abort", onAbort); abortChildren(childControllers, result.ok ? undefined : result.error); resolve(result); };
const onAbort = () => finish(resultFromSignal(context.signal)); context.signal.addEventListener("abort", onAbort, { once: true });
const startNext = () => { if (settled) { return; }
while (active < limit && nextIndex < keys.length) { const key = keys[nextIndex]; if (key === undefined) { break; }
const behavior = behaviors[key]; if (behavior === undefined) { break; }
nextIndex += 1; active += 1; runChild( behavior as unknown as Behavior<RecordInput<B>, unknown, unknown, RecordContext<B>>, input, context, childControllers, ) .then((result) => { active -= 1;
if (settled) { return; }
if (result.ok) { finish(ok(raceSuccess(key, result.value) as RaceOutput<B>)); return; }
finish(err(raceFailure(key, result.error) as RaceError<B>)); }) .catch((error: unknown) => { active -= 1; const failure = thrown(error); finish(err(raceFailure(key, failure.error) as RaceError<B>)); }); } };
if (keys.length === 0) { finish(err({ kind: "empty_race" })); return; }
startNext(); });}import { err, ok, type Result } from "./result";import { abortController, createLinkedController, type Behavior, type BehaviorContext, type BehaviorError, type BehaviorInput, type BehaviorOutput, type RuntimeContext, type RuntimeError, contextWithSignal, runBehavior, thrown,} from "./runtime";
/** * Behavior shape accepted by keyed record helpers. * * This advanced utility intentionally erases specific behavior types so record-level mapped types * can reconstruct input, output, error, and context information per key. */export type AnyBehavior = Behavior<never, unknown, unknown, object>;
/** * Object map of named behaviors for `all`, `allSettled`, and `race`. * * Keys are preserved in output and error records, which makes parallel results easy to inspect. */export type BehaviorRecord = Record<string, AnyBehavior>;
type UnionToIntersection<T> = (T extends unknown ? (value: T) => void : never) extends ( value: infer I,) => void ? I : never;
/** * Intersects the input types of every behavior in a record. * * @typeParam B - Behavior record to inspect. */export type RecordInput<B extends BehaviorRecord> = UnionToIntersection<BehaviorInput<B[keyof B]>>;
/** * Intersects the custom context types of every behavior in a record. * * @typeParam B - Behavior record to inspect. */export type RecordContext<B extends BehaviorRecord> = UnionToIntersection< BehaviorContext<B[keyof B]>> & object;
/** * Maps each behavior key to its successful output type. * * @typeParam B - Behavior record to inspect. */export type OutputRecord<B extends BehaviorRecord> = { [K in keyof B]: BehaviorOutput<B[K]>;};
/** * Unions the expected domain error types of every behavior in a record. * * @typeParam B - Behavior record to inspect. */export type ErrorUnion<B extends BehaviorRecord> = BehaviorError<B[keyof B]>;
/** * Maps each behavior key to the full result returned by that child. * * `allSettled` uses this type to preserve successes, domain failures, and runtime failures for * every child behavior. * * @typeParam B - Behavior record to inspect. */export type SettledRecord<B extends BehaviorRecord> = { [K in keyof B]: Result<BehaviorOutput<B[K]>, BehaviorError<B[K]> | RuntimeError>;};
/** * Keyed success value returned by `race`. * * @typeParam B - Behavior record to inspect. */export type RaceOutput<B extends BehaviorRecord> = { [K in keyof B]: { key: K; value: BehaviorOutput<B[K]>; };}[keyof B];
/** * Keyed failure value returned by `race`. * * @typeParam B - Behavior record to inspect. */export type RaceError<B extends BehaviorRecord> = { [K in keyof B]: { key: K; error: BehaviorError<B[K]> | RuntimeError; };}[keyof B];
/** * Domain error returned when `race` is called with an empty behavior record. */export type EmptyRaceError = { kind: "empty_race";};
/** * Normalizes a requested concurrency value for keyed parallel helpers. * * Non-finite or missing values run all children. Finite values are floored and clamped to the * inclusive range from one to the number of children. * * @param value - Requested concurrency. * @param count - Number of available children. * @returns Safe concurrency limit. */export function normalizedConcurrency(value: number | undefined, count: number): number { if (count === 0) { return 0; }
if (value === undefined || !Number.isFinite(value)) { return count; }
return Math.max(1, Math.min(count, Math.floor(value)));}
/** * Runs a child behavior with a linked abort controller. * * This advanced runtime helper lets parent helpers cancel child work independently while still * respecting parent cancellation. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param behavior - Child behavior to execute. * @param input - Input passed to the child. * @param context - Parent runtime context. * @param childControllers - Mutable collection of child controllers owned by the parent helper. * @returns Child result including runtime errors. */export function runChild<I, O, E, C extends object>( behavior: Behavior<I, O, E, C>, input: I, context: RuntimeContext<C>, childControllers: AbortController[],): Promise<Result<O, E | RuntimeError>> { const { controller, unlink } = createLinkedController(context.signal); childControllers.push(controller);
return runBehavior(behavior, input, contextWithSignal(context, controller.signal)).finally( unlink, );}
/** * Aborts every tracked child controller. * * Parent helpers call this when a parallel operation settles and remaining work should stop. * * @param childControllers - Controllers to abort. * @param reason - Abort reason propagated to each child. */export function abortChildren(childControllers: AbortController[], reason: unknown): void { for (const controller of childControllers) { abortController(controller, reason); }}
/** * Creates a keyed race success payload. * * @typeParam K - Winning key type. * @typeParam V - Winning value type. * @param key - Behavior key that settled successfully. * @param value - Successful child value. * @returns Keyed success payload. */export function raceSuccess<K extends PropertyKey, V>( key: K, value: V,): { key: K; value: V;} { return { key, value };}
/** * Creates a keyed race failure payload. * * @typeParam K - Failing key type. * @typeParam E - Failure value type. * @param key - Behavior key that failed. * @param error - Child domain or runtime error. * @returns Keyed failure payload. */export function raceFailure<K extends PropertyKey, E>( key: K, error: E,): { key: K; error: E;} { return { key, error };}
/** * Re-exports core result helpers used by advanced record internals. * * These exports are documented so generated API coverage stays complete for the installed core * record module. */export { err, ok, thrown };/** * Represents a value that may be returned directly or through a Promise. * * Flow helpers accept `Awaitable` callbacks so synchronous and asynchronous logic can share the * same behavior authoring APIs. * * @typeParam T - Value produced either immediately or asynchronously. */export type Awaitable<T> = T | Promise<T>;
/** * Successful `Result` variant. * * `Ok` carries the typed success value through Flow pipelines. Use `ok` to create branded values * that can be detected by `isResult`. * * @typeParam T - Success value type. */export type Ok<T> = { ok: true; value: T;};
/** * Failed `Result` variant. * * `Err` carries expected domain errors. Runtime failures such as cancellation and thrown * exceptions are modeled separately by the runtime helpers. * * @typeParam E - Expected error value type. */export type Err<E> = { ok: false; error: E;};
/** * Explicit success-or-failure outcome used throughout Flow. * * Flow behaviors return `Result` values instead of throwing for expected application failures. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. */export type Result<T, E> = Ok<T> | Err<E>;
/** * Structural result shape accepted by `from`. * * Use this when adapting unbranded result-like values into branded Flow results. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. */export type ResultLike<T, E> = Ok<T> | Err<E>;
const resultBrand = Symbol("archyn.result");
/** * Creates a branded successful result. * * Branded results are recognized by `isResult`, which allows `task` callbacks to distinguish * explicit `Result` returns from plain successful values. * * @typeParam T - Success value type. * @param value - Value to carry in the success variant. * @returns A branded `Ok` result. * * @example * ```ts * const result = ok({ id: "user_1" }); * ``` */export function ok<T>(value: T): Ok<T> { return brandResult({ ok: true, value });}
/** * Creates a branded failed result. * * Use `err` for expected domain failures that should stay typed and recoverable by Flow helpers. * * @typeParam E - Expected error value type. * @param error - Error value to carry in the failure variant. * @returns A branded `Err` result. * * @example * ```ts * const result = err({ kind: "not-found" }); * ``` */export function err<E>(error: E): Err<E> { return brandResult({ ok: false, error });}
/** * Converts a structural result-like value into a branded Flow result. * * This is useful when adapting results from another library or from plain object literals. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @param result - Structural success or failure object. * @returns A branded Flow `Result`. */export function from<T, E>(result: ResultLike<T, E>): Result<T, E> { return result.ok ? ok(result.value) : err(result.error);}
/** * Narrows a result to the successful variant. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @param result - Result to inspect. * @returns `true` when the result is `Ok`. */export function isOk<T, E>(result: Result<T, E>): result is Ok<T> { return result.ok;}
/** * Narrows a result to the failed variant. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @param result - Result to inspect. * @returns `true` when the result is `Err`. */export function isErr<T, E>(result: Result<T, E>): result is Err<E> { return !result.ok;}
/** * Exhaustively handles both result variants and returns one value. * * `match` keeps success and error handling explicit without manual branching at the call site. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @typeParam R - Handler return type. * @param result - Result to handle. * @param handlers - Callbacks for the success and failure variants. * @returns The value returned by the matching handler. * * @example * ```ts * const label = match(result, { * ok: (user) => user.name, * err: (error) => error.kind, * }); * ``` */export function match<T, E, R>( result: Result<T, E>, handlers: { ok: (value: T) => R; err: (error: E) => R; },): R { return result.ok ? handlers.ok(result.value) : handlers.err(result.error);}
/** * Returns the success value or throws the error value. * * Prefer typed recovery helpers for expected failures. `unwrap` is best reserved for tests, * boundaries that intentionally throw, or places where a failure is impossible by construction. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @param result - Result to unwrap. * @returns The success value. * @throws The `Err.error` value when the result failed. */export function unwrap<T, E>(result: Result<T, E>): T { if (result.ok) { return result.value; }
throw result.error;}
/** * Returns the success value or a fallback. * * Use this when a failure can be safely collapsed into a default value. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @param result - Result to read. * @param fallback - Value returned when the result failed. * @returns The success value or fallback. * * @example * ```ts * const count = unwrapOr(result, 0); * ``` */export function unwrapOr<T, E>(result: Result<T, E>, fallback: T): T { return result.ok ? result.value : fallback;}
/** * Namespace-style collection of core result helpers. * * This object is convenient when passing the result toolkit as a single value. The individual * named functions remain the preferred imports for tree-shakeable application code. */export const Result = { ok, err, from, isOk, isErr, match, unwrap, unwrapOr,};
/** * Detects branded Flow results created by `ok`, `err`, or `from`. * * `task` uses this guard to preserve explicit results while wrapping plain callback returns in * `ok`. * * @typeParam T - Success value type. * @typeParam E - Expected error value type. * @param value - Plain value or potential result. * @returns `true` when the value is a branded Flow `Result`. */export function isResult<T, E>(value: T | Result<T, E>): value is Result<T, E> { return typeof value === "object" && value !== null && resultBrand in value;}
function brandResult<R extends Ok<unknown> | Err<unknown>>(result: R): R { Object.defineProperty(result, resultBrand, { value: true, });
return result;}import { type Awaitable, type Err, type Result, err } from "./result";
/** * Runtime error returned when execution is cancelled through an `AbortSignal`. * * Cancellation is cooperative. Flow helpers check the signal before and after async work and pass * linked signals to child behaviors where appropriate. */export type Cancelled = { kind: "cancelled"; reason?: unknown;};
/** * Runtime error returned when user code throws. * * Thrown exceptions are kept separate from expected domain errors so recovery helpers do not hide * programming or platform failures. */export type Thrown = { kind: "thrown"; error: unknown;};
/** * Runtime-level failure union added around every behavior's domain error type. * * Domain errors describe expected application failures. `RuntimeError` describes cancellation or * unexpected thrown exceptions observed by the Flow runtime. */export type RuntimeError = Cancelled | Thrown;
/** * Execution context passed to every behavior. * * The context always includes an `AbortSignal` and may include application-specific fields. * * @typeParam C - Custom context object type. */export type RuntimeContext<C extends object = {}> = C & { signal: AbortSignal;};
/** * Executable Flow workflow unit. * * A behavior receives typed input, reads runtime context, and resolves to a `Result` containing * either typed output, typed domain error, or a runtime error. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. */export type Behavior<I, O, E = unknown, C extends object = {}> = ( input: I, context: RuntimeContext<C>,) => Promise<Result<O, E | RuntimeError>>;
/** * Extracts the input type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorInput<T> = T extends Behavior<infer I, infer _O, infer _E, infer _C> ? I : never;
/** * Extracts the output type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorOutput<T> = T extends Behavior<infer _I, infer O, infer _E, infer _C> ? O : never;
/** * Extracts the expected domain error type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorError<T> = T extends Behavior<infer _I, infer _O, infer E, infer _C> ? E : never;
/** * Extracts the custom context type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorContext<T> = T extends Behavior<infer _I, infer _O, infer _E, infer C> ? C : never;
/** * Executes a behavior with runtime error protection. * * The runner checks cancellation before and after behavior execution and converts thrown * exceptions into `Thrown` results. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param behavior - Behavior to execute. * @param input - Input passed to the behavior. * @param context - Runtime context containing an `AbortSignal`. * @returns The behavior result, cancellation result, or thrown runtime result. */export async function runBehavior<I, O, E, C extends object>( behavior: Behavior<I, O, E, C>, input: I, context: RuntimeContext<C>,): Promise<Result<O, E | RuntimeError>> { if (context.signal.aborted) { return resultFromSignal(context.signal); }
try { const result = await behavior(input, context);
if (context.signal.aborted) { return resultFromSignal(context.signal); }
return result; } catch (error) { if (context.signal.aborted) { return resultFromSignal(context.signal); }
return thrown(error); }}
/** * Defines a behavior from a callback that returns a `Result`. * * Use `define` for low-level helpers that already operate in the result channel. Most application * code should use `task`, which also accepts plain successful values. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param run - Callback invoked for each behavior execution. * @returns A behavior protected by cancellation and thrown-exception handling. */export function define<I, O, E = unknown, C extends object = {}>( run: (input: I, context: RuntimeContext<C>) => Awaitable<Result<O, E>>,): Behavior<I, O, E, C> { return async (input, context) => { if (context.signal.aborted) { return resultFromSignal(context.signal); }
try { const result = await run(input, context);
if (context.signal.aborted) { return resultFromSignal(context.signal); }
return result; } catch (error) { if (context.signal.aborted) { return cancelled(context.signal.reason); }
return thrown(error); } };}
/** * Runs a behavior with an optional application context. * * If no signal is provided, Flow creates a fresh non-aborted signal. Expected failures remain in * the returned `Result`; thrown exceptions are converted to `Thrown`. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param behavior - Behavior to execute. * @param input - Input passed to the behavior. * @param context - Optional custom context and optional `AbortSignal`. * @returns A result containing behavior output, domain error, or runtime error. * * @example * ```ts * const result = await run(loadUser, "user_1"); * ``` */export async function run<I, O, E, C extends object = {}>( behavior: Behavior<I, O, E, C>, input: I, context: C & { signal?: AbortSignal } = {} as C & { signal?: AbortSignal },): Promise<Result<O, E | RuntimeError>> { const { signal = new AbortController().signal, ...rest } = context;
return runBehavior(behavior, input, { ...(rest as C), signal, });}
/** * Creates a cancellation result. * * @param reason - Optional cancellation reason from an `AbortSignal`. * @returns A failed result containing `Cancelled`. */export function cancelled(reason?: unknown): Err<Cancelled> { if (reason === undefined) { return err({ kind: "cancelled" }); }
return err({ kind: "cancelled", reason });}
/** * Narrows an error value to `Cancelled`. * * @param error - Error value to inspect. * @returns `true` when the value is a cancellation runtime error. */export function isCancelled(error: unknown): error is Cancelled { return ( typeof error === "object" && error !== null && "kind" in error && error.kind === "cancelled" );}
/** * Creates a thrown-exception runtime result. * * @param error - Exception or thrown value. * @returns A failed result containing `Thrown`. */export function thrown(error: unknown): Err<Thrown> { return err({ kind: "thrown", error });}
/** * Narrows an error value to `Thrown`. * * @param error - Error value to inspect. * @returns `true` when the value is a thrown runtime error. */export function isThrown(error: unknown): error is Thrown { return typeof error === "object" && error !== null && "kind" in error && error.kind === "thrown";}
/** * Narrows an error value to a Flow runtime error. * * Recovery helpers use this guard so they only handle expected domain errors. * * @param error - Error value to inspect. * @returns `true` for `Cancelled` or `Thrown`. */export function isRuntimeError(error: unknown): error is RuntimeError { return isCancelled(error) || isThrown(error);}
/** * Converts an aborted signal into a cancellation result. * * @param signal - Aborted signal whose reason should be preserved. * @returns A failed result containing `Cancelled`. */export function resultFromSignal(signal: AbortSignal): Err<Cancelled> { return cancelled(signal.reason);}
/** * Replaces the signal on a runtime context. * * This advanced helper is used by timeout and parallel helpers when they create child abort * controllers. * * @typeParam C - Custom context type. * @param context - Existing runtime context. * @param signal - Replacement signal. * @returns A context with the same custom fields and the replacement signal. */export function contextWithSignal<C extends object>( context: RuntimeContext<C>, signal: AbortSignal,): RuntimeContext<C> { return { ...context, signal };}
/** * Aborts a controller only when it has not already been aborted. * * @param controller - Controller to abort. * @param reason - Optional abort reason. */export function abortController(controller: AbortController, reason?: unknown): void { if (!controller.signal.aborted) { controller.abort(reason); }}
/** * Links a parent signal to a child controller. * * The returned cleanup function removes the listener when the child work has settled. * * @param parent - Parent signal to observe. * @param controller - Child controller to abort when the parent aborts. * @returns Cleanup function that unlinks the abort listener. */export function linkAbortSignal(parent: AbortSignal, controller: AbortController): () => void { if (parent.aborted) { abortController(controller, parent.reason); return () => {}; }
const onAbort = () => abortController(controller, parent.reason); parent.addEventListener("abort", onAbort, { once: true });
return () => parent.removeEventListener("abort", onAbort);}
/** * Creates a child controller linked to a parent signal. * * This advanced helper powers timeout, race, and parallel execution helpers. * * @param parent - Parent signal to link from. * @returns The child controller and an unlink cleanup function. */export function createLinkedController(parent: AbortSignal): { controller: AbortController; unlink: () => void;} { const controller = new AbortController(); const unlink = linkAbortSignal(parent, controller);
return { controller, unlink };}