awaitly 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/di-Biw_plBn.d.cts +15 -0
- package/dist/di-DxyH2N3i.d.ts +15 -0
- package/dist/durable.cjs +4670 -0
- package/dist/durable.cjs.map +1 -0
- package/dist/durable.d.cts +9 -0
- package/dist/durable.d.ts +9 -0
- package/dist/durable.js +4610 -0
- package/dist/durable.js.map +1 -0
- package/dist/{di-BbFFfO8y.d.ts → duration-BjVn3QpB.d.cts} +1 -15
- package/dist/{di-BDlT7InM.d.cts → duration-BjVn3QpB.d.ts} +1 -15
- package/dist/engine.cjs +4501 -0
- package/dist/engine.cjs.map +1 -0
- package/dist/engine.d.cts +113 -0
- package/dist/engine.d.ts +113 -0
- package/dist/engine.js +4474 -0
- package/dist/engine.js.map +1 -0
- package/dist/{errors-DtXvrCiO.d.cts → errors-sDSeaZBO.d.cts} +1 -1
- package/dist/{errors-DtXvrCiO.d.ts → errors-sDSeaZBO.d.ts} +1 -1
- package/dist/guards-CwQZro1F.d.ts +72 -0
- package/dist/guards-DhhOJZda.d.cts +72 -0
- package/dist/hitl-BnnnLzh2.d.cts +468 -0
- package/dist/hitl-pYfkQMv_.d.ts +468 -0
- package/dist/hitl.cjs +646 -0
- package/dist/hitl.cjs.map +1 -0
- package/dist/hitl.d.cts +440 -0
- package/dist/hitl.d.ts +440 -0
- package/dist/hitl.js +605 -0
- package/dist/hitl.js.map +1 -0
- package/dist/index-Bew12SZ8.d.ts +417 -0
- package/dist/index-Bwt-iz50.d.cts +417 -0
- package/dist/{types-B8NfNRGX.d.ts → index-Cv4K_3sZ.d.ts} +2 -1162
- package/dist/{types-BZ2f4MRR.d.cts → index-ICmfQi08.d.cts} +2 -1162
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1527
- package/dist/index.d.ts +11 -1527
- package/dist/persistence.cjs +552 -0
- package/dist/persistence.cjs.map +1 -0
- package/dist/persistence.d.cts +254 -0
- package/dist/persistence.d.ts +254 -0
- package/dist/persistence.js +499 -0
- package/dist/persistence.js.map +1 -0
- package/dist/reliability.cjs +1651 -0
- package/dist/reliability.cjs.map +1 -0
- package/dist/reliability.d.cts +1442 -0
- package/dist/reliability.d.ts +1442 -0
- package/dist/reliability.js +1589 -0
- package/dist/reliability.js.map +1 -0
- package/dist/result.d.cts +1 -1
- package/dist/result.d.ts +1 -1
- package/dist/run.cjs +2283 -0
- package/dist/run.cjs.map +1 -0
- package/dist/run.d.cts +90 -0
- package/dist/run.d.ts +90 -0
- package/dist/run.js +2247 -0
- package/dist/run.js.map +1 -0
- package/dist/saga.cjs +3699 -0
- package/dist/saga.cjs.map +1 -0
- package/dist/saga.d.cts +162 -0
- package/dist/saga.d.ts +162 -0
- package/dist/saga.js +3670 -0
- package/dist/saga.js.map +1 -0
- package/dist/store-contract-BI98VYmX.d.ts +66 -0
- package/dist/store-contract-C2HyR_o6.d.cts +66 -0
- package/dist/streaming.cjs +895 -0
- package/dist/streaming.cjs.map +1 -0
- package/dist/streaming.d.cts +594 -0
- package/dist/streaming.d.ts +594 -0
- package/dist/streaming.js +832 -0
- package/dist/streaming.js.map +1 -0
- package/dist/testing.d.cts +4 -2
- package/dist/testing.d.ts +4 -2
- package/dist/types-BDTxgKKc.d.ts +845 -0
- package/dist/types-C9Y71dua.d.cts +845 -0
- package/dist/types-d8q8iQlk.d.cts +323 -0
- package/dist/types-sObbY4mX.d.ts +323 -0
- package/dist/webhook.cjs +461 -0
- package/dist/webhook.cjs.map +1 -0
- package/dist/webhook.d.cts +497 -0
- package/dist/webhook.d.ts +497 -0
- package/dist/webhook.js +422 -0
- package/dist/webhook.js.map +1 -0
- package/dist/workflow.cjs +2 -2614
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +12 -3023
- package/dist/workflow.d.ts +12 -3023
- package/dist/workflow.js +2 -2535
- package/dist/workflow.js.map +1 -1
- package/package.json +46 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/persistence-entry.ts","../src/core/index.ts","../src/workflow/guards.ts","../src/persistence.ts","../src/workflow/cache-encoding.ts","../src/workflow/serialize-resume-state.ts","../src/workflow/store-contract.ts","../src/versioning.ts"],"sourcesContent":["/**\n * awaitly/persistence\n *\n * Store contracts, serialization, snapshots, and resume-state migrations.\n * Persistence adapters should depend on this entry point rather than the\n * workflow runtime.\n */\n\nexport {\n type ResumeState,\n type SerializedResumeState,\n type StoreSaveInput,\n type StoreLoadResult,\n type PersistedWorkflowState,\n isResumeState,\n isSerializedResumeState,\n serializeResumeState,\n deserializeResumeState,\n toResumeState,\n} from \"./workflow\";\n\n// The lock is a store capability. It remains defined beside durable execution\n// internally, but is type-only here so adapters do not load that runtime.\nexport type { WorkflowLock } from \"./durable\";\n\n// =============================================================================\n// Snapshot API (JSON-serializable workflow state)\n// =============================================================================\nexport {\n // Types\n type JSONValue,\n type WorkflowSnapshot,\n type StepResult,\n type SerializedCause,\n type SnapshotWarning,\n\n // Store interface\n type SnapshotStore,\n\n // Validation\n looksLikeWorkflowSnapshot,\n isWorkflowSnapshot,\n validateSnapshot,\n assertValidSnapshot,\n mergeSnapshots,\n\n // Error classes\n SnapshotFormatError,\n SnapshotMismatchError,\n SnapshotDecodeError,\n\n // Serialization helpers (for custom implementations)\n serializeError,\n serializeThrown,\n deserializeCauseNew,\n\n // Cache adapter\n type MemoryCacheOptions,\n createMemoryCache,\n} from \"./persistence\";\n\n// =============================================================================\n// Resume-state versioning + migrations (evolve persisted state across\n// workflow versions)\n// =============================================================================\nexport {\n // Types\n type Version,\n type MigrationFn,\n type Migrations,\n type VersionedState,\n type VersionedWorkflowConfig,\n type MigrationError,\n type VersionIncompatibleError,\n\n // Guards\n isMigrationError,\n isVersionIncompatibleError,\n\n // Versioned state (create / parse / serialize / load)\n createVersionedState,\n parseVersionedState,\n stringifyVersionedState,\n createVersionedStateLoader,\n\n // Migration builders\n createKeyRenameMigration,\n createKeyRemoveMigration,\n createValueTransformMigration,\n composeMigrations,\n} from \"./versioning\";\n","/**\n * Core module (internal): Result primitives and the run() function.\n *\n * Surfaced through the root `awaitly` entry (formerly `awaitly/core`).\n * Use this module for minimal bundle size when you don't need the full workflow capabilities\n * (like retries, timeout, or state persistence) provided by `createWorkflow`.\n *\n * This module provides:\n * 1. `Result` types for error handling without try/catch\n * 2. `run()` function for executing steps with standardized error management\n * 3. Utilities for transforming and combining Results\n */\n\n// Inline duration type and parser to avoid importing the full duration module\n// This keeps the core bundle minimal (~1KB saved)\n\n/** Duration object with tagged type for type safety */\ntype DurationObject = { readonly _tag: \"Duration\"; readonly millis: number };\n\n/** Duration input: either a string (\"5s\", \"100ms\") or a Duration object */\ntype DurationInput = string | DurationObject;\n\n/** Parse a duration string like \"100ms\", \"5s\", \"2m\", \"1h\", \"1d\" */\nfunction parseDurationString(input: string): DurationObject | undefined {\n const match = input.trim().match(/^(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h|d)$/i);\n if (!match) return undefined;\n const value = parseFloat(match[1]);\n const unit = match[2].toLowerCase();\n const multipliers: Record<string, number> = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };\n return { _tag: \"Duration\", millis: value * (multipliers[unit] ?? 1) };\n}\n\n// =============================================================================\n// Core Result Types\n// =============================================================================\n\n/**\n * Represents a successful result.\n * Use `ok(value)` to create instances.\n *\n * @template T - The type of the success value\n *\n * @example\n * ```typescript\n * const success = ok(42);\n * // Type shown: Ok<number>\n * ```\n */\nexport type Ok<T> = {\n ok: true;\n value: T;\n};\n\n/**\n * Represents a failed result.\n * Use `err(error)` to create instances.\n *\n * @template E - The type of the error value\n * @template C - The type of the cause (defaults to unknown)\n * @template T - Phantom type for the success value (preserved after narrowing)\n *\n * @example\n * ```typescript\n * const failure = err({ type: \"NOT_FOUND\", message: \"User not found\" });\n * // Type shown: Err<{ type: string; message: string }>\n * ```\n */\nexport type Err<E, C = unknown> = {\n ok: false;\n error: E;\n cause?: C;\n};\n\n/**\n * Represents a successful computation or a failed one.\n * Use this type to represent the outcome of an operation that might fail,\n * instead of throwing exceptions.\n *\n * @template T - The type of the success value\n * @template E - The type of the error value (defaults to unknown)\n * @template C - The type of the cause (defaults to unknown)\n */\nexport type Result<T, E = unknown, C = unknown> = Ok<T> | Err<E, C>;\n\n/**\n * A Promise that resolves to a Result.\n * Use this for asynchronous operations that might fail.\n */\nexport type AsyncResult<T, E = unknown, C = unknown> = Promise<Result<T, E, C>>;\n\n/** Discriminant for PromiseRejectedError type - use in switch statements */\nexport const PROMISE_REJECTED = \"PROMISE_REJECTED\" as const;\n\n// =============================================================================\n// Named Error Constants (for static analysis)\n// =============================================================================\n\n/**\n * Named error constant for unexpected/unhandled errors.\n * Used by the analyzer when a step doesn't declare errors.\n */\nexport const AWAITLY_UNEXPECTED = \"AWAITLY_UNEXPECTED\" as const;\n\n/**\n * Named error constant for cancelled operations.\n */\nexport const AWAITLY_CANCELLED = \"AWAITLY_CANCELLED\" as const;\n\n/**\n * Named error constant for timed-out operations.\n */\nexport const AWAITLY_TIMEOUT = \"AWAITLY_TIMEOUT\" as const;\n\n// =============================================================================\n// Static Analysis Helpers\n// =============================================================================\n\n/**\n * Helper to create a tuple of string literal tags with preserved literal types.\n * Use this when you need to store error tags in a variable while keeping\n * TypeScript's literal type inference (avoiding widening to string[]).\n *\n * @param t - The string literal tags\n * @returns The same array with preserved literal types\n *\n * @example\n * ```typescript\n * // Without tags() - type widens to string[]\n * const errs = ['CART_NOT_FOUND', 'CART_EMPTY']; // string[]\n *\n * // With tags() - literal types preserved\n * const errs = tags('CART_NOT_FOUND', 'CART_EMPTY'); // ['CART_NOT_FOUND', 'CART_EMPTY']\n *\n * await step('getCart', () => getCart(id), {\n * errors: errs, // Analyzer can extract literal types\n * out: 'cart',\n * });\n * ```\n */\nexport const tags = <const T extends readonly string[]>(...t: T): T => t;\n\nimport { UnexpectedError } from \"../errors\";\nimport { bindSteps, type BoundSteps, type StepCallable } from \"./bound-steps\";\n\nexport { bindSteps, type BoundSteps } from \"./bound-steps\";\nexport {\n retry,\n timeout,\n fallback,\n type RetryPolicyOptions,\n type PolicyFn,\n type PolicyDelay,\n} from \"./policies\";\nexport { UnexpectedError };\n\n/**\n * Default mapper for unexpected causes (uncaught exceptions, cancellation, etc.).\n * Returns an UnexpectedError TaggedError instance.\n * Used when run() is called without catchUnexpected.\n *\n * @param cause - The thrown value\n * @returns An UnexpectedError instance\n */\nexport function defaultCatchUnexpected(cause: unknown): UnexpectedError {\n return new UnexpectedError({ cause });\n}\n\nexport type PromiseRejectedError = { type: typeof PROMISE_REJECTED; cause: unknown };\n/** Cause type for promise rejections in async batch helpers */\nexport type PromiseRejectionCause = { type: \"PROMISE_REJECTION\"; reason: unknown };\nexport type EmptyInputError = { type: \"EMPTY_INPUT\"; message: string };\nexport type MaybeAsyncResult<T, E, C = unknown> = Result<T, E, C> | Promise<Result<T, E, C>>;\n\n// =============================================================================\n// Result Constructors\n// =============================================================================\n\n/**\n * Creates a successful Result.\n * Use this when an operation completes successfully.\n *\n * @remarks When to use: Wrap a successful value in a Result for consistent return types.\n *\n * @param value - The success value to wrap\n * @returns An Ok object with `{ ok: true, value }`\n *\n * @example\n * ```typescript\n * const success = ok(42);\n * // Type: Ok<number>\n *\n * function divide(a: number, b: number): Result<number, string> {\n * if (b === 0) return err(\"Division by zero\");\n * return ok(a / b);\n * }\n * ```\n */\nexport function ok<T>(value: T): Ok<T> {\n return { ok: true as const, value };\n}\n\n/**\n * Creates a failed Result.\n * Use this when an operation fails.\n *\n * @remarks When to use: Return a typed failure without throwing so callers can handle it explicitly.\n *\n * @param error - The error value describing what went wrong (e.g., error code, object)\n * @returns An Err object with `{ ok: false, error }`\n *\n * @example\n * ```typescript\n * // Simple error\n * const r1 = err(\"NOT_FOUND\");\n * // Type: Err<\"NOT_FOUND\">\n *\n * // Error with context (include in error object)\n * const r2 = err({ type: \"PROCESSING_FAILED\", cause: originalError });\n * // Type: Err<{ type: string; cause: Error }>\n * ```\n */\nexport function err<E, C = unknown>(error: E, options?: { cause?: C }): Err<E, C> {\n const cause = options?.cause;\n return { ok: false as const, error, ...(cause !== undefined ? { cause } : {}) } as Err<E, C>;\n}\n\n// =============================================================================\n// Type Guards\n// =============================================================================\n\n/**\n * Checks if a Result is successful.\n * Use this to narrow the type of a Result to the success case.\n *\n * @remarks When to use: Prefer functional-style checks or array filtering over `result.ok`.\n *\n * @param r - The Result to check\n * @returns `true` if successful, allowing access to `r.value`\n *\n * @example\n * ```typescript\n * const r = someOperation();\n * if (isOk(r)) {\n * // Use r.value (Type is T)\n * processValue(r.value);\n * } else {\n * // Handle r.error (Type is E)\n * handleError(r.error);\n * }\n * ```\n */\nexport const isOk = <T, E, C>(r: Result<T, E, C>): r is Ok<T> => r.ok;\n\n/**\n * Checks if a Result is a failure.\n * Use this to narrow the type of a Result to the error case.\n *\n * @remarks When to use: Prefer functional-style checks or array filtering over `result.ok`.\n *\n * @param r - The Result to check\n * @returns `true` if failed, allowing access to `r.error` and `r.cause`\n *\n * @example\n * ```typescript\n * if (isErr(r)) {\n * // Handle error case early\n * return;\n * }\n * // Proceed with success case\n * ```\n */\nexport const isErr = <T, E, C>(r: Result<T, E, C>): r is Err<E, C> => !r.ok;\n\n/**\n * Checks if an error is an UnexpectedError.\n * Used internally by the framework but exported for advanced custom handling.\n * Indicates an error that wasn't typed/expected in the `run` signature.\n *\n * @remarks When to use: Distinguish unexpected failures from your typed error union.\n */\nexport const isUnexpectedError = (e: unknown): e is UnexpectedError =>\n e instanceof UnexpectedError ||\n (typeof e === \"object\" &&\n e !== null &&\n \"_tag\" in e &&\n (e as { _tag: string })._tag === \"UnexpectedError\");\n\n/**\n * Checks if an error is a PromiseRejectedError.\n * Occurs when a Promise rejects in batch operations (allAsync, anyAsync, zipAsync).\n *\n * @example\n * ```typescript\n * onError: (error): FetchError => {\n * if (isPromiseRejectedError(error)) return 'FETCH_FAILED';\n * return error; // TypeScript narrows to FetchError\n * }\n * ```\n */\nexport const isPromiseRejectedError = (e: unknown): e is PromiseRejectedError =>\n typeof e === \"object\" &&\n e !== null &&\n (e as PromiseRejectedError).type === PROMISE_REJECTED;\n\n// =============================================================================\n// Error Matching\n// =============================================================================\n\n/**\n * Type for exhaustive error handlers mapping string literal errors and UnexpectedError.\n * Each key in E gets a handler, plus UnexpectedError is required.\n */\nexport type MatchErrorHandlers<E extends string, R> = {\n [K in Exclude<E, \"UnexpectedError\">]: (error: K) => R;\n} & {\n UnexpectedError: (error: UnexpectedError) => R;\n};\n\n/**\n * Exhaustive pattern matching for error types.\n * Handles both string literal errors and UnexpectedError, ensuring all cases are covered.\n *\n * @param error - The error to match (string literal or UnexpectedError)\n * @param handlers - Object with a handler for each error case plus UnexpectedError\n * @returns The result of the matched handler\n *\n * @example\n * ```typescript\n * type FetchError = \"NOT_FOUND\" | \"FETCH_ERROR\";\n * const result: Result<User, FetchError | UnexpectedError> = await fetchUser();\n *\n * if (!result.ok) {\n * return matchError(result.error, {\n * NOT_FOUND: () => 404,\n * FETCH_ERROR: () => 500,\n * UnexpectedError: (e) => { throw e.cause; }\n * });\n * }\n * ```\n */\nexport function matchError<E extends string, R>(\n handlers: MatchErrorHandlers<E, R>\n): (error: E | UnexpectedError) => R;\nexport function matchError<E extends string, R>(\n error: E | UnexpectedError,\n handlers: MatchErrorHandlers<E, R>\n): R;\nexport function matchError<E extends string, R>(\n errorOrHandlers: E | UnexpectedError | MatchErrorHandlers<E, R>,\n handlers?: MatchErrorHandlers<E, R>\n): R | ((error: E | UnexpectedError) => R) {\n if (handlers === undefined) {\n const h = errorOrHandlers as MatchErrorHandlers<E, R>;\n return (e: E | UnexpectedError) => matchError(e, h);\n }\n const error = errorOrHandlers as E | UnexpectedError;\n // Handle UnexpectedError instances\n if (isUnexpectedError(error)) {\n return handlers.UnexpectedError(error as UnexpectedError);\n }\n // Handle string literal errors\n type StringErrors = Exclude<E, \"UnexpectedError\">;\n return handlers[error as StringErrors](error as StringErrors);\n}\n\n// =============================================================================\n// Type Utilities\n// =============================================================================\n\ntype AnyFunction = (...args: never[]) => unknown;\n\n/**\n * Helper to extract the error type from Result or AsyncResult return values.\n * Works even when a function is declared to return a union of both forms.\n * Plain (non-Result) return types contribute `never` — without the [never]\n * guard they would infer `unknown` and poison error unions built from\n * mixed deps.\n */\ntype ErrorOfReturn<R> = [Extract<Awaited<R>, { ok: false }>] extends [never]\n ? never\n : Extract<Awaited<R>, { ok: false }> extends { error: infer E }\n ? E\n : never;\n\n/**\n * Extract error type from a single function's return type\n */\nexport type ErrorOf<T extends AnyFunction> = ErrorOfReturn<ReturnType<T>>;\n\n/**\n * Extract union of error types from multiple functions (tuple form)\n */\nexport type Errors<T extends AnyFunction[]> = {\n [K in keyof T]: ErrorOf<T[K]>;\n}[number];\n\n/**\n * Extract union of error types from a deps object.\n *\n * @example\n * ```typescript\n * const deps = { getUser, createOrder, sendEmail };\n * type E = ErrorsOf<typeof deps>;\n * // = \"NOT_FOUND\" | \"ORDER_FAILED\" | \"EMAIL_ERROR\"\n * ```\n */\nexport type ErrorsOf<Deps extends Record<string, AnyFunction>> = {\n [K in keyof Deps]: ErrorOf<Deps[K]>;\n}[keyof Deps];\n\n/**\n * Extract value type from Result\n */\nexport type ExtractValue<T> = T extends { ok: true; value: infer U }\n ? U\n : never;\n\n/**\n * Extract error type from Result\n */\nexport type ExtractError<T> = T extends { ok: false; error: infer E }\n ? E\n : never;\n\n/**\n * Extract cause type from Result\n */\nexport type ExtractCause<T> = T extends { ok: false; cause?: infer C }\n ? C\n : never;\n\n/**\n * Helper to extract the cause type from Result or AsyncResult return values.\n * Works even when a function is declared to return a union of both forms.\n */\ntype CauseOfReturn<R> = Extract<Awaited<R>, { ok: false }> extends { cause?: infer C }\n ? C\n : never;\n\n/**\n * Extract cause type from a function's return type\n */\nexport type CauseOf<T extends AnyFunction> = CauseOfReturn<ReturnType<T>>;\n\n// =============================================================================\n// Step Options\n// =============================================================================\n\n/**\n * Options for configuring a step within a workflow.\n * Use these to enable tracing, caching, state persistence, and static analysis.\n */\nexport type StepOptions<\n Errs extends readonly string[] = readonly string[],\n Out extends string | undefined = undefined,\n> = {\n /**\n * Stable identity key for the step.\n * REQUIRED for:\n * 1. Caching: Used as the cache key.\n * 2. Resuming: Used to identify which steps have already completed.\n *\n * Must be unique within the workflow.\n */\n key?: string;\n\n /**\n * Short description for labels/tooltips.\n * Used by static analysis visualization tools.\n */\n description?: string;\n\n /**\n * Full markdown documentation for the step.\n * Used by static analysis visualization tools.\n */\n markdown?: string;\n\n /**\n * Retry configuration for transient failures.\n * When specified, the step will retry on errors according to this config.\n */\n retry?: RetryOptions;\n\n /**\n * Timeout configuration for the operation.\n * When specified, each attempt will be aborted after the timeout duration.\n */\n timeout?: TimeoutOptions;\n\n /**\n * Time-to-live for this step's cache entry in milliseconds.\n * Overrides any global cache TTL. Requires `key` for caching.\n */\n ttl?: number;\n\n // ==========================================================================\n // Static Analysis Options\n // ==========================================================================\n\n /**\n * Declared tagged errors this step may return.\n * Used by the static analyzer to build error flow graphs.\n *\n * Use `tags()` helper when storing in a variable:\n * @example\n * ```typescript\n * const cartErrors = tags('CART_NOT_FOUND', 'CART_EMPTY');\n * await step('getCart', () => getCart(id), { errors: cartErrors });\n *\n * // Or inline (no helper needed)\n * await step('getCart', () => getCart(id), {\n * errors: ['CART_NOT_FOUND', 'CART_EMPTY'],\n * });\n * ```\n */\n errors?: Errs;\n\n /**\n * Write the step's return value to this context key.\n * Replaces manual `ctx.set()` calls for the happy path.\n *\n * @example\n * ```typescript\n * await step('getCart', () => getCart(id), { out: 'cart' });\n * // Now ctx.cart contains the result\n * ```\n */\n out?: Out;\n\n /**\n * Override auto-detected reads from context.\n * Use when the analyzer can't trace complex data dependencies.\n *\n * @example\n * ```typescript\n * await step('charge', () => chargeCard(getCartTotal()), {\n * reads: ['cart'], // Explicitly declare dependency\n * });\n * ```\n */\n reads?: readonly string[];\n\n /**\n * Hint for dependency source tracking.\n * Use when the callback is complex and the analyzer can't detect\n * which dependency function is being called.\n *\n * @example\n * ```typescript\n * await step('getCart', () => {\n * const id = transform(ctx.input.cartId);\n * return deps.getCart(id);\n * }, {\n * dep: 'getCart', // Hint for analyzer\n * });\n * ```\n */\n dep?: string;\n\n // ==========================================================================\n // Agent Metadata — Architecture & Intent\n // ==========================================================================\n\n /**\n * Why this step exists in the business flow.\n * Unlike `description` (which says *what* the step does), `intent` explains\n * the business reason it exists in the workflow.\n *\n * @example\n * ```typescript\n * await step('validateCart', () => validate(cart), {\n * description: 'Validates cart contents and pricing',\n * intent: 'Prevent charging customers for out-of-stock items',\n * });\n * ```\n */\n intent?: string;\n\n /**\n * Business domain this step belongs to.\n * Used by the static analyzer to group steps by bounded context.\n *\n * @example\n * ```typescript\n * await step('chargeCard', () => charge(card, amount), {\n * domain: 'payments',\n * });\n * ```\n */\n domain?: string;\n\n /**\n * Team, service, or bounded-context that owns this step.\n *\n * @example\n * ```typescript\n * await step('shipOrder', () => ship(order), {\n * owner: 'fulfillment-team',\n * });\n * ```\n */\n owner?: string;\n\n /**\n * Classification tags for this step.\n *\n * Recommended vocabulary:\n * `'side-effect'`, `'external-api'`, `'idempotent'`, `'read-only'`,\n * `'cacheable'`, `'pii'`, `'pci'`, `'compensatable'`\n *\n * @example\n * ```typescript\n * await step('chargeCard', () => charge(card, amount), {\n * tags: ['side-effect', 'external-api', 'pci'],\n * });\n * ```\n */\n tags?: readonly string[];\n\n // ==========================================================================\n // Agent Metadata — Effects & Dependencies\n // ==========================================================================\n\n /**\n * Human-oriented descriptions of state mutations this step performs.\n * These are free-text labels for documentation and visualization — they are\n * not machine-parsed at runtime.\n *\n * A future `stateEffects` field will provide structured effect declarations.\n *\n * @example\n * ```typescript\n * await step('placeOrder', () => place(cart), {\n * stateChanges: ['order.status → PLACED', 'inventory.reserved += qty'],\n * });\n * ```\n */\n stateChanges?: readonly string[];\n\n /**\n * Domain events this step produces.\n * Used by the static analyzer to build event flow graphs.\n *\n * @example\n * ```typescript\n * await step('placeOrder', () => place(cart), {\n * emits: ['OrderPlaced', 'InventoryReserved'],\n * });\n * ```\n */\n emits?: readonly string[];\n\n /**\n * External systems or services this step calls.\n * Used by the static analyzer to map external dependencies.\n *\n * @example\n * ```typescript\n * await step('chargeCard', () => charge(card, amount), {\n * calls: ['stripe-api', 'fraud-detection-service'],\n * });\n * ```\n */\n calls?: readonly string[];\n\n // ==========================================================================\n // Agent Metadata — Error Classification\n // ==========================================================================\n\n /**\n * Structured metadata for each error this step may produce.\n * Keys should match entries in the `errors` array.\n *\n * Note: `retryable` classifies the error's nature (whether it CAN be retried),\n * separate from whether this step actually retries it (that's `retry.shouldRetry`).\n * Both dimensions are useful — \"this error IS retryable\" vs \"this step DOES retry it\".\n * Defaults to `undefined` (unknown), not `true`.\n *\n * @example\n * ```typescript\n * await step('chargeCard', () => charge(card, amount), {\n * errors: ['CARD_DECLINED', 'GATEWAY_TIMEOUT'],\n * errorMeta: {\n * CARD_DECLINED: {\n * retryable: false,\n * severity: 'business',\n * description: 'Card was declined by issuer',\n * },\n * GATEWAY_TIMEOUT: {\n * retryable: true,\n * severity: 'infrastructure',\n * description: 'Payment gateway did not respond in time',\n * },\n * },\n * });\n * ```\n */\n errorMeta?: Record<string, ErrorClassification>;\n\n /**\n * Compensation action to run if a later step fails.\n *\n * When set, the step's return value is captured. If the workflow later fails\n * (any step error, or the user callback throws), every step that recorded a\n * compensation runs its `compensate` callback in reverse order.\n *\n * If a compensation throws, errors are collected. When at least one fails,\n * the workflow result becomes a `SagaCompensationError` containing the\n * original error and all compensation failures.\n *\n * @example\n * ```typescript\n * const reservation = await step('reserve', () => deps.reserve(items), {\n * compensate: (r) => deps.release(r.id),\n * });\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n compensate?: (value: any) => void | Promise<void>;\n};\n\n/** Shared error classification — used in StepOptions.errorMeta, diagnostics, and wide events. */\nexport interface ErrorClassification {\n retryable?: boolean;\n severity?: 'business' | 'infrastructure' | 'validation';\n description?: string;\n}\n\n/** Runtime-visible business context from StepOptions. Does NOT include errorMeta. */\nexport interface StepMetadata {\n intent?: string;\n domain?: string;\n owner?: string;\n tags?: readonly string[];\n stateChanges?: readonly string[];\n emits?: readonly string[];\n calls?: readonly string[];\n}\n\n/** Runtime error diagnostics. classification is the matched ErrorClassification for this error. */\nexport interface StepErrorDiagnostics {\n tag: string;\n classification?: ErrorClassification;\n attempt?: number; // 1-based\n cumulativeDurationMs?: number; // execution only, excludes backoff\n origin: 'result' | 'throw' | 'timeout';\n}\n\n/** Extract canonical error tag. Priority: _tag > tag > code > Error.name > \"unknown\".\n * Tags are case-sensitive, whitespace-trimmed, otherwise raw.\n * Note: Error.name is fallback-grade (often too coarse like \"Error\", \"TypeError\"). */\nexport function extractErrorTag(error: unknown): string {\n if (error == null) return 'unknown';\n\n if (typeof error === 'string') return error.trim() || 'unknown';\n\n if (typeof error === 'object') {\n // Priority 1: _tag (TaggedError pattern)\n const tagged = error as Record<string, unknown>;\n if (typeof tagged._tag === 'string') {\n const trimmed = tagged._tag.trim();\n if (trimmed) return trimmed;\n }\n // Priority 2: tag\n if (typeof tagged.tag === 'string') {\n const trimmed = tagged.tag.trim();\n if (trimmed) return trimmed;\n }\n // Priority 3: code — string used directly, number stringified, anything else skipped\n if (typeof tagged.code === 'string') {\n const trimmed = tagged.code.trim();\n if (trimmed) return trimmed;\n } else if (typeof tagged.code === 'number') {\n return String(tagged.code);\n }\n // Priority 4: Error.name (fallback-grade)\n if (error instanceof Error && error.name) {\n const trimmed = error.name.trim();\n if (trimmed) return trimmed;\n }\n }\n\n return 'unknown';\n}\n\n/** Look up ErrorClassification from errorMeta for a given tag. */\nexport function lookupErrorClassification(\n tag: string,\n errorMeta?: Record<string, ErrorClassification>,\n): ErrorClassification | undefined {\n if (!errorMeta || !tag) return undefined;\n return errorMeta[tag];\n}\n\n/** Extract StepMetadata from StepOptions (returns undefined when empty). */\nexport function extractStepMetadata(options: StepOptions): StepMetadata | undefined {\n const { intent, domain, owner, tags, stateChanges, emits, calls } = options;\n if (!intent && !domain && !owner && !tags?.length && !stateChanges?.length && !emits?.length && !calls?.length) {\n return undefined;\n }\n const metadata: StepMetadata = {};\n if (intent) metadata.intent = intent;\n if (domain) metadata.domain = domain;\n if (owner) metadata.owner = owner;\n if (tags?.length) metadata.tags = tags;\n if (stateChanges?.length) metadata.stateChanges = stateChanges;\n if (emits?.length) metadata.emits = emits;\n if (calls?.length) metadata.calls = calls;\n return metadata;\n}\n\n/** Build StepErrorDiagnostics from error + errorMeta. */\nfunction buildStepErrorPayload(\n error: unknown,\n errorMeta: StepOptions['errorMeta'],\n origin: StepErrorDiagnostics['origin'],\n attempt?: number,\n cumulativeDurationMs?: number,\n): StepErrorDiagnostics {\n const tag = extractErrorTag(error);\n const classification = lookupErrorClassification(tag, errorMeta);\n const diagnostics: StepErrorDiagnostics = { tag, origin };\n if (classification !== undefined) diagnostics.classification = classification;\n if (attempt !== undefined) diagnostics.attempt = attempt;\n if (cumulativeDurationMs !== undefined) diagnostics.cumulativeDurationMs = cumulativeDurationMs;\n return diagnostics;\n}\n\n// =============================================================================\n// Retry and Timeout Types\n// =============================================================================\n\n/**\n * Backoff strategy for retry operations.\n */\nexport type BackoffStrategy = \"fixed\" | \"linear\" | \"exponential\";\n\n/**\n * Configuration for step retry behavior.\n */\nexport type RetryOptions<E = unknown> = {\n /**\n * Total number of attempts (1 = no retry, 3 = initial + 2 retries).\n * Must be >= 1.\n */\n attempts: number;\n\n /**\n * Backoff strategy between retries.\n * - 'fixed': Same delay each time (initialDelay)\n * - 'linear': Delay increases linearly (initialDelay * attempt)\n * - 'exponential': Delay doubles each time (initialDelay * 2^(attempt-1))\n * @default 'exponential'\n */\n backoff?: BackoffStrategy;\n\n /**\n * Initial delay in milliseconds before first retry.\n * @default 100\n */\n initialDelay?: number;\n\n /**\n * Maximum delay cap in milliseconds.\n * Prevents exponential backoff from growing too large.\n * @default 30000 (30 seconds)\n */\n maxDelay?: number;\n\n /**\n * Whether to add random jitter (0-25% of delay).\n * Helps prevent thundering herd when multiple workflows retry simultaneously.\n * @default true\n */\n jitter?: boolean;\n\n /**\n * Predicate to determine if a retry should occur.\n * Receives the error and current attempt number (1-indexed).\n * Return true to retry, false to fail immediately.\n * @default Always retry on any error\n */\n shouldRetry?: (error: E, attempt: number) => boolean;\n\n /**\n * Callback invoked before each retry attempt.\n * Useful for logging, metrics, or side effects.\n */\n onRetry?: (error: E, attempt: number, delayMs: number) => void;\n};\n\n/**\n * Timeout behavior when the timeout is reached.\n *\n * - 'error' (default): Return an error result with StepTimeoutError\n * - 'option': Return Ok(undefined) instead of an error (useful for optional operations)\n * - 'disconnect': Let the operation complete in background, return timeout error immediately\n * - function: Custom handler to generate the timeout error\n */\nexport type TimeoutBehavior =\n | \"error\"\n | \"option\"\n | \"disconnect\"\n | ((stepInfo: { name?: string; key?: string; ms: number }) => unknown);\n\n/**\n * Configuration for step timeout behavior.\n */\nexport type TimeoutOptions = {\n /**\n * Timeout duration in milliseconds per attempt.\n * When combined with retry, each attempt gets its own timeout.\n */\n ms: number;\n\n /**\n * Custom error to use when timeout occurs.\n * @default StepTimeoutError with step details\n */\n error?: unknown;\n\n /**\n * Whether to pass an AbortSignal to the operation.\n * When true, the operation function receives (signal: AbortSignal) as argument.\n * Useful for fetch() and other APIs that support cancellation.\n * @default false\n */\n signal?: boolean;\n\n /**\n * Behavior when timeout is reached.\n *\n * - 'error' (default): Return StepTimeoutError (or custom error if provided)\n * - 'option': Return Ok(undefined) instead of error (operation treated as optional)\n * - 'disconnect': Let operation complete in background, return error immediately\n * - function: Custom handler `(stepInfo) => customError`\n *\n * @default 'error'\n *\n * @example\n * ```typescript\n * // Default: Return timeout error\n * step.withTimeout(() => slowOp(), { ms: 5000 });\n *\n * // Optional: Return undefined if times out\n * step.withTimeout(() => optionalOp(), { ms: 5000, onTimeout: 'option' });\n *\n * // Disconnect: Don't wait for slow operation\n * step.withTimeout(() => fireAndForget(), { ms: 5000, onTimeout: 'disconnect' });\n *\n * // Custom error\n * step.withTimeout(() => apiCall(), {\n * ms: 5000,\n * onTimeout: ({ name, ms }) => ({ type: 'API_TIMEOUT', name, ms })\n * });\n * ```\n */\n onTimeout?: TimeoutBehavior;\n};\n\n/**\n * Standard timeout error type.\n */\nexport type StepTimeoutError = {\n type: \"STEP_TIMEOUT\";\n stepName?: string;\n stepKey?: string;\n timeoutMs: number;\n attempt?: number;\n};\n\n/**\n * Symbol used to mark any error (including custom errors) as a timeout error.\n * This allows detection of timeout errors even when users provide custom error payloads.\n */\nexport const STEP_TIMEOUT_MARKER: unique symbol = Symbol.for(\"step_timeout_marker\");\n\n/**\n * Metadata attached to timeout-marked errors.\n */\nexport type StepTimeoutMarkerMeta = {\n timeoutMs: number;\n stepName?: string;\n stepKey?: string;\n attempt?: number;\n};\n\n/**\n * Type guard to check if an error is a StepTimeoutError.\n * This checks both the standard type field AND the timeout marker symbol,\n * so custom errors provided via timeout.error are also detected.\n */\nexport function isStepTimeoutError(e: unknown): e is StepTimeoutError {\n if (typeof e !== \"object\" || e === null) {\n return false;\n }\n // Check for standard type field\n if ((e as StepTimeoutError).type === \"STEP_TIMEOUT\") {\n return true;\n }\n // Check for timeout marker (custom errors)\n return STEP_TIMEOUT_MARKER in e;\n}\n\n/**\n * Get timeout metadata from a timeout error (works with both standard and custom errors).\n * Returns undefined if the error is not a timeout error.\n */\nexport function getStepTimeoutMeta(e: unknown): StepTimeoutMarkerMeta | undefined {\n if (typeof e !== \"object\" || e === null) {\n return undefined;\n }\n // Check for standard type field first\n if ((e as StepTimeoutError).type === \"STEP_TIMEOUT\") {\n const err = e as StepTimeoutError;\n return {\n timeoutMs: err.timeoutMs,\n stepName: err.stepName,\n stepKey: err.stepKey,\n attempt: err.attempt,\n };\n }\n // Check for timeout marker (custom errors)\n if (STEP_TIMEOUT_MARKER in e) {\n return (e as Record<symbol, StepTimeoutMarkerMeta>)[STEP_TIMEOUT_MARKER];\n }\n return undefined;\n}\n\n// =============================================================================\n// RunStep Interface\n// =============================================================================\n\n/**\n * The `step` object passed to the function in `run(async ({ step }) => { ... })`.\n * acts as the bridge between your business logic and the workflow engine.\n *\n * It provides methods to:\n * 1. Execute operations that return `Result` types.\n * 2. safely wrap operations that might throw exceptions (using `step.try`).\n * 3. Assign names and keys to operations for tracing and caching.\n *\n * @template E - The union of all known error types expected in this workflow.\n */\nexport interface RunStep<E = unknown> {\n /**\n * Execute a Result-returning operation with explicit step ID.\n *\n * The ID is used for:\n * - Static analysis visualization\n * - Error flow tracking\n * - Step identification in diagrams\n * - Caching and resume (ID is used as the cache key)\n *\n * @param id - Unique step identifier (string literal for static analysis)\n * @param operation - A function that returns a Result or AsyncResult\n * @param options - Step options\n * @returns The success value (unwrapped)\n * @throws {EarlyExit} If the result is an error (stops execution safely)\n *\n * @example\n * ```typescript\n * const cart = await step('getCart', () => getCart(ctx.input.cartId), {\n * errors: ['CART_NOT_FOUND', 'CART_EMPTY'],\n * out: 'cart',\n * });\n * ```\n */\n <T, StepE extends E, StepC = unknown>(\n id: string,\n operation: () => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>,\n options?: StepOptions\n ): Promise<T>;\n\n /**\n * Execute a standard throwing operation safely.\n * Catches exceptions and maps them to a typed error.\n *\n * Use this when integrating with libraries that throw exceptions.\n * Supports retry, timeout, and compensate options inline.\n *\n * @param id - Unique identifier for this step (required for analysis and caching)\n * @param operation - A function that returns a value or Promise (may throw)\n * @param options - Configuration including error mapping, retry, timeout, compensate\n * @returns The success value\n * @throws {EarlyExit} If the operation throws (stops execution safely)\n *\n * @example\n * ```typescript\n * // Basic\n * const data = await step.try(\"db-query\", () => db.query(), {\n * onError: (e) => ({ type: \"DB_ERROR\", cause: e }),\n * });\n *\n * // With retry + timeout\n * const data = await step.try(\"api-call\", () => callApi(), {\n * onError: (e) => \"API_ERROR\" as const,\n * retry: { attempts: 3, initialDelay: 100 },\n * timeout: { ms: 5000 },\n * });\n * ```\n */\n try: <T, const Err extends E>(\n id: string,\n operation: () => T | Promise<T>,\n options:\n | {\n error: Err;\n key?: string;\n ttl?: number;\n retry?: RetryOptions<Err>;\n timeout?: TimeoutOptions;\n compensate?: (value: T) => void | Promise<void>;\n }\n | {\n onError: (cause: unknown) => Err;\n key?: string;\n ttl?: number;\n retry?: RetryOptions<Err>;\n timeout?: TimeoutOptions;\n compensate?: (value: T) => void | Promise<void>;\n }\n ) => Promise<T>;\n\n /**\n * Execute a Result-returning function and map its error to a typed error.\n *\n * Use this when calling functions that return Result<T, E> and you want to\n * map their typed errors to your workflow's error type. Unlike step.try(),\n * the error passed to onError is typed (not unknown).\n *\n * @param id - Unique identifier for this step (required for analysis and caching)\n * @param operation - A function that returns a Result or AsyncResult\n * @param options - Configuration including error mapping\n * @returns The success value (unwrapped)\n * @throws {EarlyExit} If the result is an error (stops execution safely)\n *\n * @example\n * ```typescript\n * const response = await step.fromResult(\n * \"call-provider\",\n * () => callProvider(input),\n * {\n * onError: (providerError) => ({\n * type: \"PROVIDER_FAILED\",\n * provider: providerError.provider,\n * cause: providerError\n * })\n * }\n * );\n * ```\n */\n fromResult: <T, ResultE, const Err extends E>(\n id: string,\n operation: () => Result<T, ResultE, unknown> | AsyncResult<T, ResultE, unknown>,\n options:\n | { error: Err; key?: string; ttl?: number }\n | { onError: (resultError: ResultE) => Err; key?: string; ttl?: number }\n ) => Promise<T>;\n\n /**\n * Execute an operation that may return null/undefined and convert to a typed error.\n *\n * Shorthand for wrapping `fromNullable()` in a step — avoids boilerplate when\n * looking up optional values (database finds, map lookups, etc.).\n *\n * @param id - Unique step identifier\n * @param operation - A function that returns `T | null | undefined` (or a Promise thereof)\n * @param onNull - Returns the typed error when operation returns null/undefined\n *\n * @example\n * ```typescript\n * const user = await step.fromNullable(\n * 'getUser',\n * () => db.users.findById(id),\n * () => ({ type: 'NOT_FOUND' as const, id })\n * );\n * ```\n */\n fromNullable: <T, const Err extends E>(\n id: string,\n operation: () => T | null | undefined | Promise<T | null | undefined>,\n onNull: () => Err,\n options?: { key?: string; ttl?: number }\n ) => Promise<T>;\n\n /**\n * Execute parallel operations with scope events for visualization.\n *\n * This wraps the operations with scope_start and scope_end events, enabling\n * visualization of parallel execution branches.\n *\n * @overload Object form - step.all(name, { key: () => ... })\n * @overload Array form - step.all(name, () => allAsync([...]))\n *\n * @example Object form\n * ```typescript\n * const { user, posts } = await step.all('Fetch user data', {\n * user: () => fetchUser(id),\n * posts: () => fetchPosts(id),\n * });\n * ```\n *\n * @example Canonical form (strict mode)\n * ```typescript\n * const { user, posts } = await step.all('Fetch user data', {\n * user: { fn: () => fetchUser(id), errors: ['NOT_FOUND'] },\n * posts: { fn: () => fetchPosts(id), errors: ['FETCH_ERROR'] },\n * });\n * ```\n *\n * @example Array form\n * ```typescript\n * const [user, posts] = await step.all('Fetch all data', () =>\n * allAsync([fetchUser(id), fetchPosts(id)])\n * );\n * ```\n */\n all: {\n // Object form: step.all(name, { key: () => ... })\n <\n TOperations extends Record<\n string,\n () => MaybeAsyncResult<unknown, E, unknown>\n >\n >(\n name: string,\n operations: TOperations\n ): Promise<{\n [K in keyof TOperations]: TOperations[K] extends () => MaybeAsyncResult<\n infer V,\n E,\n unknown\n >\n ? V\n : never;\n }>;\n\n // Object form canonical: step.all(name, { key: { fn, errors } })\n <\n TOperations extends Record<\n string,\n ParallelOperationDescriptor<unknown, readonly string[]>\n >\n >(\n name: string,\n operations: TOperations\n ): Promise<{\n [K in keyof TOperations]: TOperations[K] extends ParallelOperationDescriptor<\n infer V,\n readonly string[]\n >\n ? V\n : never;\n }>;\n\n // Array form: step.all(name, () => allAsync([...]))\n <T, StepE extends E, StepC = unknown>(\n name: string,\n operation: () => Result<T[], StepE, StepC> | AsyncResult<T[], StepE, StepC>\n ): Promise<T[]>;\n };\n\n /**\n * Execute a race operation (anyAsync) with scope events for visualization.\n *\n * This wraps the operation with scope_start and scope_end events, enabling\n * visualization of racing execution branches.\n *\n * @param name - Name for this race block (used in visualization)\n * @param operation - A function that returns a Result from anyAsync\n * @returns The success value (first to succeed)\n *\n * @example\n * ```typescript\n * const data = await step.race('Fastest API', () =>\n * anyAsync([fetchFromPrimary(id), fetchFromFallback(id)])\n * );\n * ```\n */\n race: <T, StepE extends E, StepC = unknown>(\n name: string,\n operation: () => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>\n ) => Promise<T>;\n\n /**\n * Execute a primary operation with a fallback if the primary fails.\n *\n * If the primary operation returns an error, the fallback is executed instead.\n * When `on` is specified, the fallback only runs for that specific error;\n * other errors propagate without invoking the fallback.\n *\n * Returns `Promise<T>` — errors escape via earlyExit into the workflow's error channel.\n * The error union `E1 | E2` is the generic constraint propagated to the workflow.\n *\n * @overload Fallback on ANY error from primary\n * @overload Fallback on SPECIFIC error literal only (via `on`)\n *\n * @param id - Unique step identifier (single step ID for events)\n * @param operation - Primary operation that returns AsyncResult\n * @param options - Fallback configuration\n * @returns The success value from primary or fallback\n *\n * @example\n * ```typescript\n * const user = await step.withFallback(\n * 'getUser',\n * () => fetchFromPrimary(id),\n * { fallback: () => fetchFromCache(id) }\n * );\n *\n * // With specific error filter\n * const data = await step.withFallback(\n * 'getData',\n * () => fetchFromApi(id),\n * { on: 'NOT_FOUND', fallback: () => getDefault(id) }\n * );\n * ```\n */\n withFallback: {\n // Overload 1: fallback on ANY error from primary\n <T, E1 extends E, E2 extends E>(\n id: string,\n operation: () => AsyncResult<T, E1>,\n options: { fallback: () => AsyncResult<T, E2>; key?: string }\n ): Promise<T>;\n\n // Overload 2: fallback on SPECIFIC error literal only\n <T, E1 extends E & string, E2 extends E>(\n id: string,\n operation: () => AsyncResult<T, E1>,\n options: { on: E1; fallback: () => AsyncResult<T, E2>; key?: string }\n ): Promise<T>;\n };\n\n /**\n * Execute an operation with automatic resource lifecycle management.\n *\n * Acquires a resource, uses it, and guarantees release regardless of outcome.\n * Release always runs after use completes (even on error or throw).\n * Release errors are logged via console.warn but never override the use result.\n *\n * No caching support — caching resource-using steps is dangerous.\n *\n * @param id - Unique step identifier\n * @param options - Resource lifecycle configuration\n * @returns The success value from the use function\n *\n * @example\n * ```typescript\n * const data = await step.withResource('useDb', {\n * acquire: () => connectToDb(),\n * use: (db) => db.query('SELECT * FROM users'),\n * release: (db) => db.close(),\n * });\n * ```\n */\n withResource: <T, R, AcquireE extends E, UseE extends E>(\n id: string,\n options: {\n acquire: () => AsyncResult<R, AcquireE>;\n use: (resource: R) => AsyncResult<T, UseE>;\n release: (resource: R) => void | Promise<void>;\n }\n ) => Promise<T>;\n\n /**\n * Execute an operation with retry and optional timeout.\n *\n * Use this for operations that may fail transiently (network issues, rate limits)\n * and benefit from automatic retry with backoff.\n *\n * @param id - Unique identifier for this step (required for analysis and caching)\n * @param operation - A function that returns a Result or AsyncResult\n * @param options - Retry configuration and optional timeout\n * @returns The success value (unwrapped)\n * @throws {EarlyExit} If all retries are exhausted (stops execution safely)\n *\n * @example\n * ```typescript\n * const data = await step.retry(\n * \"fetch-external\",\n * () => fetchFromExternalApi(id),\n * {\n * attempts: 3,\n * backoff: 'exponential',\n * initialDelay: 200,\n * shouldRetry: (error) => error === 'RATE_LIMITED' || error === 'TRANSIENT',\n * onRetry: (error, attempt, delay) => {\n * console.log(`Retry ${attempt} after ${delay}ms`);\n * },\n * }\n * );\n * ```\n */\n retry: <T, StepE extends E, StepC = unknown>(\n id: string,\n operation: () => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>,\n options: RetryOptions<StepE> & { key?: string; timeout?: TimeoutOptions }\n ) => Promise<T>;\n\n /**\n * Execute an operation with a timeout.\n *\n * Use this for operations that may hang indefinitely (external APIs, connections)\n * and need to be aborted after a certain duration.\n *\n * When `signal: true` is set, an AbortSignal is passed to your operation,\n * which you can use with APIs like fetch() for proper cancellation.\n *\n * @param id - Unique identifier for this step (required for analysis and caching)\n * @param operation - A function that returns a Result (may receive AbortSignal)\n * @param options - Timeout configuration\n * @returns The success value (unwrapped)\n * @throws {EarlyExit} If the operation times out (stops execution safely)\n *\n * @example\n * ```typescript\n * // Without AbortSignal\n * const data = await step.withTimeout(\n * \"fetch-data\",\n * () => fetchData(id),\n * { ms: 5000 }\n * );\n *\n * // With AbortSignal for fetch()\n * const data = await step.withTimeout(\n * \"fetch-url\",\n * (signal) => fetch(url, { signal }).then(r => ok(r.json())),\n * { ms: 5000, signal: true }\n * );\n * ```\n */\n withTimeout: <T, StepE extends E, StepC = unknown>(\n id: string,\n operation:\n | (() => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>)\n | ((signal: AbortSignal) => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>),\n options: TimeoutOptions & { key?: string }\n ) => Promise<T>;\n\n /**\n * Pause execution for a specified duration.\n *\n * Use this for intentional delays between operations (rate limiting,\n * polling intervals, debouncing). Respects workflow cancellation.\n *\n * @param id - Unique identifier for this step (required for analysis and caching)\n * @param duration - Duration as string (\"5s\", \"100ms\") or Duration object\n * @param options - Optional key for per-iteration identity, ttl, description\n * @returns Promise that resolves after the duration\n * @throws {AbortError} If the workflow is cancelled during sleep\n *\n * @example\n * ```typescript\n * // String duration\n * await step.sleep(\"rate-limit-delay\", \"5s\");\n *\n * // Duration object\n * await step.sleep(\"my-sleep\", seconds(5));\n * ```\n */\n sleep(\n id: string,\n duration: DurationInput,\n options?: { key?: string; ttl?: number; description?: string; signal?: AbortSignal }\n ): Promise<void>;\n\n // ===========================================================================\n // Streaming Methods\n // ===========================================================================\n\n /**\n * Get a writable stream for this workflow.\n *\n * Use this to write values that can be consumed by readers\n * (e.g., HTTP response streaming, AI token streaming).\n *\n * @param options - Stream options (namespace, highWaterMark)\n * @returns StreamWriter for writing values\n *\n * @example\n * ```typescript\n * const writer = step.getWritable<string>({ namespace: 'ai-response' });\n *\n * await step(() => generateAI({\n * prompt: 'Hello',\n * onToken: async (token) => { await writer.write(token); }\n * }), { key: 'generate' });\n *\n * await writer.close();\n * ```\n */\n getWritable: <T>(options?: StreamWritableOptions) => StreamWriterInterface<T>;\n\n /**\n * Get a readable stream for this workflow.\n *\n * Use this to consume values from a stream, with support for\n * resuming from a specific position.\n *\n * @param options - Read options (namespace, startIndex)\n * @returns StreamReader for reading values\n *\n * @example\n * ```typescript\n * const reader = step.getReadable<string>({ namespace: 'ai-response' });\n *\n * let result = await reader.read();\n * while (result.ok) {\n * response.write(result.value);\n * result = await reader.read();\n * }\n * ```\n */\n getReadable: <T>(options?: StreamReadableOptions) => StreamReaderInterface<T>;\n\n /**\n * Process stream items with checkpointing.\n *\n * Combines streaming with batch processing - each item is processed\n * and checkpointed, enabling resume from the last successful item.\n *\n * @param source - StreamReader or AsyncIterable to process\n * @param processor - Function to process each item\n * @param options - Processing options\n * @returns Results from all processed items\n *\n * @example\n * ```typescript\n * const reader = step.getReadable<Message>({ namespace: 'messages' });\n *\n * const result = await step.streamForEach(\n * reader,\n * async (message, index) => {\n * const processed = await processMessage(message);\n * return ok(processed);\n * },\n * { name: 'process-messages', checkpointInterval: 10 }\n * );\n *\n * console.log(`Processed ${result.value.processedCount} messages`);\n * ```\n */\n streamForEach: <T, R, StepE extends E>(\n source: StreamReaderInterface<T> | AsyncIterable<T>,\n processor: (item: T, index: number) => AsyncResult<R, StepE>,\n options?: StreamForEachStepOptions\n ) => Promise<StreamForEachResultType<R>>;\n\n // ===========================================================================\n // Static Analysis Methods\n // ===========================================================================\n\n /**\n * Mark a conditional for static analysis with a stable ID and condition label.\n * Runtime: returns the boolean result of condition().\n * Analysis: emits a DecisionNode with stable id and conditionLabel, and attaches\n * the then/else subgraphs from the if/else branches.\n *\n * @param id - Stable identifier for this decision point (string literal for static analysis)\n * @param conditionLabel - Human-readable label describing the condition\n * @param condition - Function that returns the boolean condition\n * @returns The result of the condition function\n *\n * @example\n * ```typescript\n * if (step.if('payment', 'cart.total > 0', () => ctx.ref('cart').total > 0)) {\n * await step('chargeCard', () => deps.chargeCard(ctx.ref('cart').total), {\n * errors: ['CARD_DECLINED'],\n * });\n * } else {\n * await step('skipPayment', async () => ({ skipped: true }), {\n * errors: [],\n * });\n * }\n * ```\n */\n if: <T extends boolean>(\n id: string,\n conditionLabel: string,\n condition: () => T\n ) => T;\n\n /**\n * Alias for `step.if()`. Mark a conditional for static analysis with a stable ID.\n * Use this to label conditionals in strict mode when they contain step calls.\n *\n * @param id - Stable identifier for this decision point\n * @param conditionLabel - Human-readable label describing the condition\n * @param condition - Function that returns the boolean condition\n * @returns The result of the condition function\n *\n * @example\n * ```typescript\n * if (step.label('email-type', 'user.isPremium', () => user.isPremium)) {\n * await step('premium', () => sendPriorityEmail(user), { errors: ['EMAIL_FAILED'] });\n * } else {\n * await step('free', () => sendRegularEmail(user), { errors: ['EMAIL_FAILED'] });\n * }\n * ```\n */\n label: <T extends boolean>(\n id: string,\n conditionLabel: string,\n condition: () => T\n ) => T;\n\n /**\n * Execute a branch with explicit metadata for static analysis.\n * Use when you want richer analyzer metadata (conditionLabel, per-arm errors).\n * For most cases, use natural if/else with step.label() instead.\n *\n * @param id - Stable identifier for this branch point\n * @param options - Branch configuration with condition, then/else arms, and errors\n * @returns The result from the executed arm\n *\n * @example\n * ```typescript\n * const charge = await step.branch('payment', {\n * conditionLabel: 'cart.total > 0',\n * condition: () => ctx.ref('cart').total > 0,\n * out: 'charge',\n * then: () => chargeCard(ctx.ref('cart').total),\n * thenErrors: ['CARD_DECLINED'],\n * else: () => ok({ skipped: true }),\n * elseErrors: [],\n * });\n * ```\n */\n branch: <\n T,\n const ThenErrs extends readonly string[] = readonly [],\n const ElseErrs extends readonly string[] = readonly [],\n const Out extends string | undefined = undefined,\n >(\n id: string,\n options: BranchOptions<T, ThenErrs, ElseErrs, Out>\n ) => Promise<T>;\n\n /**\n * Create an arm definition for use with step.branch().\n * Runtime: returns the arm definition unchanged.\n * Analyzer: extracts arm metadata for visualization.\n *\n * @param fn - The arm function\n * @param errors - Declared errors for this arm\n * @returns The arm definition\n *\n * @example\n * ```typescript\n * const thenArm = step.arm(() => chargeCard(total), ['CARD_DECLINED']);\n * const elseArm = step.arm(() => ok({ skipped: true }), []);\n * ```\n */\n arm: <T, const Errs extends readonly string[] = readonly []>(\n fn: () => T | Promise<T>,\n errors?: Errs\n ) => ArmDefinition<T, Errs>;\n\n /**\n * Execute a forEach loop with static analysis support.\n * Supports both simple (run) and complex (item) forms.\n *\n * @param id - Stable identifier for this loop\n * @param items - Iterable to loop over\n * @param options - Loop configuration\n * @returns Array of results from each iteration\n *\n * @example Simple form:\n * ```typescript\n * await step.forEach('process-items', items, {\n * maxIterations: 100,\n * stepIdPattern: 'process-{i}',\n * errors: ['PROCESS_ERROR'],\n * run: (item) => processItem(item),\n * });\n * ```\n *\n * @example Complex form with multiple steps:\n * ```typescript\n * await step.forEach('process-items', items, {\n * maxIterations: 100,\n * item: step.item((item, i, innerStep) => {\n * await innerStep('validate', () => validate(item), { errors: ['INVALID'] });\n * await innerStep('process', () => process(item), { errors: ['FAILED'] });\n * }),\n * });\n * ```\n */\n forEach: {\n // Simple form with run callback\n <T, R, const Errs extends readonly string[] = readonly []>(\n id: string,\n items: Iterable<T> | AsyncIterable<T>,\n options: ForEachRunOptions<T, R, Errs>\n ): Promise<R[]>;\n\n // Complex form with item callback\n <T, R>(\n id: string,\n items: Iterable<T> | AsyncIterable<T>,\n options: ForEachItemOptions<T, R>\n ): Promise<R[]>;\n };\n\n /**\n * Create an item handler for use with step.forEach().\n * Runtime: returns the handler unchanged.\n * Analyzer: extracts the inner step structure.\n *\n * @param handler - Function to process each item\n * @returns The item handler\n *\n * @example\n * ```typescript\n * step.item((item, index, innerStep) => {\n * await innerStep('validate', () => validate(item));\n * await innerStep('process', () => process(item));\n * });\n * ```\n */\n item: <T, R>(\n handler: (item: T, index: number, step: RunStep<E>) => R | Promise<R>\n ) => ForEachItemHandler<T, R>;\n\n /**\n * Wrap a dependency function for static analysis tracking.\n * Returns the function unchanged but marks it for the analyzer.\n *\n * @param name - Name of the dependency (for analyzer tracking)\n * @param fn - The dependency function to wrap\n * @returns The same function, unchanged\n *\n * @example\n * ```typescript\n * await step('getCart', step.dep('getCart', () => deps.getCart(ctx.input.cartId)), {\n * errors: ['CART_NOT_FOUND'],\n * out: 'cart',\n * });\n * ```\n */\n dep: <T extends (...args: unknown[]) => unknown>(name: string, fn: T) => T;\n\n // ===========================================================================\n // Effect-Style Ergonomics\n // ===========================================================================\n\n /**\n /**\n * Run a sub-workflow (or any AsyncResult-returning operation) as a step.\n * Use for workflow composition; the getter's error type (SubE) flows into the parent's error union.\n *\n * @param id - Unique step identifier\n * @param getter - Function that returns AsyncResult (e.g. () => subWorkflow.run(fn))\n * @param options - Step options (key, ttl, etc.)\n * @returns The success value (unwrapped)\n * @throws {EarlyExit} If the result is an error\n *\n * @example\n * ```typescript\n * const authResult = await step.workflow(\"authorize\", () => authorizeWorkflow.run(fn));\n * ```\n */\n workflow: <T, SubE extends E, StepC = unknown>(\n id: string,\n getter: () => AsyncResult<T, SubE, StepC>,\n options?: StepOptions\n ) => Promise<T>;\n\n /**\n * Map over an array with parallel execution and error tracking.\n *\n * Similar to Effect.forEach - executes mapper function for each item\n * in parallel and collects results.\n *\n * @param id - Unique step identifier\n * @param items - Array of items to process\n * @param mapper - Function to process each item (returns AsyncResult)\n * @param options - Optional concurrency limit and cache key\n * @returns Array of results in original order\n *\n * @example\n * ```typescript\n * const users = await step.map('fetchUsers', userIds, (id) =>\n * fetchUser(id)\n * );\n * // Automatic parallel execution with error union\n * ```\n */\n map: <T, U, StepE extends E, StepC = unknown>(\n id: string,\n items: T[],\n mapper: (item: T, index: number) => AsyncResult<U, StepE, StepC>,\n options?: { concurrency?: number; key?: string }\n ) => Promise<U[]>;\n\n}\n\n// =============================================================================\n// Parallel Types\n// =============================================================================\n\n/**\n * Operation descriptor for canonical parallel form.\n * Use this for analyzable parallel operations with explicit error declarations.\n */\nexport type ParallelOperationDescriptor<\n T,\n Errs extends readonly string[] = readonly [],\n> = {\n /** The operation function */\n fn: () => MaybeAsyncResult<T, unknown, unknown>;\n /** Declared errors for this operation (for static analysis) */\n errors?: Errs;\n};\n\n// =============================================================================\n// Branch and ForEach Types\n// =============================================================================\n\n/**\n * Options for step.branch().\n */\nexport type BranchOptions<\n T,\n ThenErrs extends readonly string[] = readonly [],\n ElseErrs extends readonly string[] = readonly [],\n Out extends string | undefined = undefined,\n> = {\n /** Human-readable label describing the condition */\n conditionLabel: string;\n /** Function that evaluates the condition */\n condition: () => boolean;\n /** Output key for data flow (writes result to ctx[out]) */\n out?: Out;\n /** Function to execute when condition is true */\n then: () => T | Promise<T>;\n /** Declared errors for the then arm */\n thenErrors?: ThenErrs;\n /** Function to execute when condition is false */\n else?: () => T | Promise<T>;\n /** Declared errors for the else arm */\n elseErrors?: ElseErrs;\n};\n\n/**\n * Arm definition for step.branch().\n */\nexport type ArmDefinition<T, Errs extends readonly string[] = readonly []> = {\n fn: () => T | Promise<T>;\n errors?: Errs;\n};\n\n/**\n * Options for step.forEach() with simple run form.\n */\nexport type ForEachRunOptions<T, R, Errs extends readonly string[] = readonly []> = {\n /** Maximum iterations (for bounded analysis) */\n maxIterations?: number;\n /** Step ID pattern for iterations (e.g., 'process-{i}') */\n stepIdPattern?: string;\n /** Declared errors for the loop body */\n errors?: Errs;\n /** Output key for results (requires collect option in strict mode) */\n out?: string;\n /** How to collect results when out is specified */\n collect?: \"array\" | \"last\";\n /** Simple callback for each item */\n run: (item: T, index: number) => R | Promise<R>;\n};\n\n/**\n * Options for step.forEach() with complex item form.\n */\nexport type ForEachItemOptions<T, R> = {\n /** Maximum iterations (for bounded analysis) */\n maxIterations?: number;\n /** Step ID pattern for iterations (e.g., 'process-{i}') */\n stepIdPattern?: string;\n /** Output key for results (requires collect option in strict mode) */\n out?: string;\n /** How to collect results when out is specified */\n collect?: \"array\" | \"last\";\n /** Complex item handler with inner step access */\n item: ForEachItemHandler<T, R>;\n};\n\n/**\n * Item handler for step.forEach() with inner step access.\n */\nexport type ForEachItemHandler<T, R> = {\n __forEachItemHandler: true;\n handler: (item: T, index: number, step: RunStep<unknown>) => R | Promise<R>;\n};\n\n// =============================================================================\n// Streaming Types (minimal interfaces for RunStep)\n// =============================================================================\n\n/**\n * Options for getWritable.\n */\nexport interface StreamWritableOptions {\n /** Named streams (default: 'default') */\n namespace?: string;\n /** Backpressure threshold (default: 16) */\n highWaterMark?: number;\n}\n\n/**\n * Options for getReadable.\n */\nexport interface StreamReadableOptions {\n /** Named streams (default: 'default') */\n namespace?: string;\n /** Resume from position (0-indexed) */\n startIndex?: number;\n /** Poll interval in ms when waiting for new items (default: 10) */\n pollInterval?: number;\n /** Stop polling after this many ms with no new items (default: 30000) */\n pollTimeout?: number;\n}\n\n/**\n * Options for streamForEach.\n */\nexport interface StreamForEachStepOptions {\n /** Checkpoint after every N items (default: 1) */\n checkpointInterval?: number;\n /** Maximum concurrent processors (default: 1 = sequential) */\n concurrency?: number;\n}\n\n/**\n * Result from streamForEach operation.\n */\nexport interface StreamForEachResultType<R> {\n /** Results from each processed item */\n results: R[];\n /** Total items processed */\n processedCount: number;\n /** Position of last processed item */\n lastPosition: number;\n}\n\n/**\n * Writable stream interface used in RunStep.\n * @see StreamWriter in awaitly/streaming for full interface\n */\nexport interface StreamWriterInterface<T> {\n write(value: T): AsyncResult<void, StreamWriteErrorType>;\n close(): AsyncResult<void, StreamCloseErrorType>;\n abort(reason: unknown): void;\n readonly writable: boolean;\n readonly position: number;\n readonly namespace: string;\n}\n\n/**\n * Readable stream interface used in RunStep.\n * @see StreamReader in awaitly/streaming for full interface\n */\nexport interface StreamReaderInterface<T> {\n read(): AsyncResult<T, StreamReadErrorType | StreamEndedMarkerType>;\n close(): void;\n readonly readable: boolean;\n readonly position: number;\n readonly namespace: string;\n}\n\n/**\n * Stream write error type.\n */\nexport interface StreamWriteErrorType {\n type: \"STREAM_WRITE_ERROR\";\n reason: \"closed\" | \"aborted\" | \"store_error\";\n message: string;\n cause?: unknown;\n}\n\n/**\n * Stream read error type.\n */\nexport interface StreamReadErrorType {\n type: \"STREAM_READ_ERROR\";\n reason: \"closed\" | \"store_error\";\n message: string;\n cause?: unknown;\n}\n\n/**\n * Stream close error type.\n */\nexport interface StreamCloseErrorType {\n type: \"STREAM_CLOSE_ERROR\";\n reason: \"already_closed\" | \"store_error\";\n message: string;\n cause?: unknown;\n}\n\n/**\n * Stream ended marker type.\n */\nexport interface StreamEndedMarkerType {\n type: \"STREAM_ENDED\";\n finalPosition: number;\n}\n\n// =============================================================================\n// Event Types (for run() optional event support)\n// =============================================================================\n\n/**\n * Unified event stream for workflow execution.\n *\n * Note: step_complete.result uses Result<unknown, unknown, unknown> because events\n * aggregate results from heterogeneous steps. At runtime, the actual Result object\n * preserves its original types, but the event type cannot statically represent them.\n * Use runtime checks or the meta field to interpret cause values.\n */\n/**\n * Scope types for parallel and race operations.\n */\nexport type ScopeType = \"parallel\" | \"race\" | \"allSettled\";\n\nexport type WorkflowEvent<E, C = unknown> =\n | { type: \"workflow_start\"; workflowId: string; workflowName?: string; ts: number; context?: C }\n | { type: \"workflow_success\"; workflowId: string; workflowName?: string; ts: number; durationMs: number; context?: C }\n | { type: \"workflow_error\"; workflowId: string; workflowName?: string; ts: number; durationMs: number; error: E; context?: C }\n | { type: \"step_start\"; workflowId: string; workflowName?: string; stepId: string; stepKey?: string; name?: string; description?: string; ts: number; metadata?: StepMetadata; context?: C }\n | { type: \"step_success\"; workflowId: string; workflowName?: string; stepId: string; stepKey?: string; name?: string; description?: string; ts: number; durationMs: number; metadata?: StepMetadata; context?: C }\n | { type: \"step_error\"; workflowId: string; workflowName?: string; stepId: string; stepKey?: string; name?: string; description?: string; ts: number; durationMs: number; error: E; metadata?: StepMetadata; diagnostics?: StepErrorDiagnostics; context?: C }\n | { type: \"step_aborted\"; workflowId: string; workflowName?: string; stepId: string; stepKey?: string; name?: string; description?: string; ts: number; durationMs: number; metadata?: StepMetadata; context?: C }\n | { type: \"step_complete\"; workflowId: string; workflowName?: string; stepKey: string; name?: string; description?: string; ts: number; durationMs: number; result: Result<unknown, unknown, unknown>; meta?: StepFailureMeta; metadata?: StepMetadata; context?: C }\n | { type: \"step_cache_hit\"; workflowId: string; workflowName?: string; stepKey: string; name?: string; ts: number; metadata?: StepMetadata; context?: C }\n | { type: \"step_cache_miss\"; workflowId: string; workflowName?: string; stepKey: string; name?: string; ts: number; metadata?: StepMetadata; context?: C }\n | { type: \"step_skipped\"; workflowId: string; workflowName?: string; stepKey?: string; name?: string; reason?: string; decisionId?: string; ts: number; metadata?: StepMetadata; context?: C }\n | { type: \"decision\"; workflowId: string; workflowName?: string; decisionId: string; label?: string; branch: string; value: unknown; phase?: \"start\" | \"end\"; durationMs?: number; ts: number; context?: C }\n | { type: \"scope_start\"; workflowId: string; workflowName?: string; scopeId: string; scopeType: ScopeType; name?: string; ts: number; context?: C }\n | { type: \"scope_end\"; workflowId: string; workflowName?: string; scopeId: string; ts: number; durationMs: number; winnerId?: string; context?: C }\n // Retry events\n | {\n type: \"step_retry\";\n workflowId: string;\n workflowName?: string;\n stepId: string;\n stepKey?: string;\n name?: string;\n ts: number;\n attempt: number;\n maxAttempts: number;\n delayMs: number;\n error: E;\n metadata?: StepMetadata;\n diagnostics?: StepErrorDiagnostics;\n context?: C;\n }\n | {\n type: \"step_retries_exhausted\";\n workflowId: string;\n workflowName?: string;\n stepId: string;\n stepKey?: string;\n name?: string;\n ts: number;\n durationMs: number;\n attempts: number;\n lastError: E;\n metadata?: StepMetadata;\n diagnostics?: StepErrorDiagnostics;\n context?: C;\n }\n // Timeout event\n | {\n type: \"step_timeout\";\n workflowId: string;\n workflowName?: string;\n stepId: string;\n stepKey?: string;\n name?: string;\n ts: number;\n timeoutMs: number;\n attempt?: number;\n metadata?: StepMetadata;\n diagnostics?: StepErrorDiagnostics;\n context?: C;\n }\n // Hook events\n | {\n type: \"hook_should_run\";\n workflowId: string;\n workflowName?: string;\n ts: number;\n durationMs: number;\n result: boolean;\n skipped: boolean;\n context?: C;\n }\n | {\n type: \"hook_should_run_error\";\n workflowId: string;\n workflowName?: string;\n ts: number;\n durationMs: number;\n error: E;\n context?: C;\n }\n | {\n type: \"hook_before_start\";\n workflowId: string;\n workflowName?: string;\n ts: number;\n durationMs: number;\n result: boolean;\n skipped: boolean;\n context?: C;\n }\n | {\n type: \"hook_before_start_error\";\n workflowId: string;\n workflowName?: string;\n ts: number;\n durationMs: number;\n error: E;\n context?: C;\n }\n | {\n type: \"hook_after_step\";\n workflowId: string;\n workflowName?: string;\n stepKey: string;\n ts: number;\n durationMs: number;\n context?: C;\n }\n | {\n type: \"hook_after_step_error\";\n workflowId: string;\n workflowName?: string;\n stepKey: string;\n ts: number;\n durationMs: number;\n error: E;\n context?: C;\n }\n // Stream events\n | {\n type: \"stream_created\";\n workflowId: string;\n workflowName?: string;\n namespace: string;\n ts: number;\n context?: C;\n }\n | {\n type: \"stream_write\";\n workflowId: string;\n workflowName?: string;\n namespace: string;\n position: number;\n ts: number;\n context?: C;\n }\n | {\n type: \"stream_read\";\n workflowId: string;\n workflowName?: string;\n namespace: string;\n position: number;\n ts: number;\n context?: C;\n }\n | {\n type: \"stream_close\";\n workflowId: string;\n workflowName?: string;\n namespace: string;\n finalPosition: number;\n ts: number;\n context?: C;\n }\n | {\n type: \"stream_error\";\n workflowId: string;\n workflowName?: string;\n namespace: string;\n error: unknown;\n position: number;\n ts: number;\n context?: C;\n }\n | {\n type: \"stream_backpressure\";\n workflowId: string;\n workflowName?: string;\n namespace: string;\n bufferedCount: number;\n state: \"paused\" | \"flowing\";\n ts: number;\n context?: C;\n }\n // Workflow cancellation event\n | {\n type: \"workflow_cancelled\";\n workflowId: string;\n workflowName?: string;\n ts: number;\n durationMs: number;\n /** Reason from AbortSignal.reason (if provided) */\n reason?: string;\n /** Last successfully completed keyed step before cancellation (for resume purposes) */\n lastStepKey?: string;\n context?: C;\n };\n\n// =============================================================================\n// Run Options\n// =============================================================================\n\n/**\n * A declared workflow graph for strict runtime validation.\n *\n * Pass either a list of step/decision ids or a WorkflowDiagramDSL-shaped\n * object (`{ states: [{ id }] }`, as produced by awaitly-analyze).\n * When provided, any runtime step or decision id not present in the graph\n * fails the workflow immediately — so the static diagram is guaranteed to\n * match what actually runs. Ids containing `{...}` placeholders\n * (e.g. \"item-{i}\") match any value in that position.\n */\nexport type DeclaredGraph =\n | readonly string[]\n | {\n readonly states: ReadonlyArray<{\n readonly id: string;\n /** Authored id when the unique diagram id needed a collision suffix. */\n readonly semanticId?: string;\n }>;\n };\n\nexport type RunOptionsWithCatch<E, C = void> = {\n /**\n * Handler for expected errors.\n * Called when a step fails with a known error type.\n */\n onError?: (error: E, stepName?: string, ctx?: C) => void;\n /**\n * Listener for workflow events (start, success, error, step events).\n * Use this for logging, telemetry, or debugging.\n *\n * Context is automatically included in `event.context` when provided via the `context` option.\n * The separate `ctx` parameter is provided for convenience.\n */\n onEvent?: (event: WorkflowEvent<E | UnexpectedError, C>, ctx: C) => void;\n /**\n * Catch-all mapper for unexpected exceptions.\n * Converts unknown exceptions (and cancellation) into your typed error union E.\n */\n catchUnexpected: (cause: unknown) => E;\n /**\n * Unique ID for this workflow execution.\n * Defaults to a random UUID.\n * Useful for correlating logs across distributed systems.\n */\n workflowId?: string;\n /**\n * Human-readable workflow name included on emitted events.\n * Useful for observability and visualization.\n */\n workflowName?: string;\n /**\n * Arbitrary context object passed to onEvent and onError.\n * Useful for passing request IDs, user IDs, or loggers.\n */\n context?: C;\n /**\n * Declared workflow graph for strict runtime validation.\n * Undeclared step/decision ids fail the workflow immediately.\n */\n graph?: DeclaredGraph;\n /**\n * @internal External signal for workflow-level cancellation.\n * Used by createWorkflow() to pass the workflow signal to steps.\n */\n _workflowSignal?: AbortSignal;\n};\n\nexport type RunOptionsWithoutCatch<E, C = void> = {\n /**\n * Handler for expected errors AND unexpected errors.\n * Unexpected errors will be wrapped in `UnexpectedError`.\n */\n onError?: (error: E | UnexpectedError, stepName?: string, ctx?: C) => void;\n /**\n * Listener for workflow events (start, success, error, step events).\n *\n * Note: Context is available both on `event.context` and as the separate `ctx` parameter.\n * The `ctx` parameter is provided for convenience and backward compatibility.\n */\n onEvent?: (event: WorkflowEvent<E | UnexpectedError, C>, ctx: C) => void;\n catchUnexpected?: undefined;\n workflowId?: string;\n /**\n * Human-readable workflow name included on emitted events.\n * Useful for observability and visualization.\n */\n workflowName?: string;\n context?: C;\n /**\n * Declared workflow graph for strict runtime validation.\n * Undeclared step/decision ids fail the workflow immediately.\n */\n graph?: DeclaredGraph;\n /**\n * @internal External signal for workflow-level cancellation.\n * Used by createWorkflow() to pass the workflow signal to steps.\n */\n _workflowSignal?: AbortSignal;\n};\n\nexport type RunOptions<E, C = void> = RunOptionsWithCatch<E, C> | RunOptionsWithoutCatch<E, C>;\n\n// =============================================================================\n// Early Exit Mechanism (exported for caching layer)\n// =============================================================================\n\n/**\n * Symbol used to identify early exit throws.\n * Exported for the caching layer in workflow.ts.\n * @internal\n */\nexport const EARLY_EXIT_SYMBOL: unique symbol = Symbol(\"early-exit\");\n\n/**\n * Metadata about how a step failed.\n * @internal\n */\nexport type StepFailureMeta =\n | { origin: \"result\"; resultCause?: unknown }\n | { origin: \"throw\"; thrown: unknown }\n | { origin: \"fallback\"; fallbackUsed: true; fallbackReason: string };\n\n/**\n * Early exit object thrown to short-circuit workflow execution.\n * @internal\n */\nexport type EarlyExit<E> = {\n [EARLY_EXIT_SYMBOL]: true;\n error: E;\n meta: StepFailureMeta;\n};\n\n/**\n * Create an early exit throw object.\n * Used by the caching layer to synthesize early exits for cached errors.\n * @internal\n */\nexport function createEarlyExit<E>(error: E, meta: StepFailureMeta): EarlyExit<E> {\n return {\n [EARLY_EXIT_SYMBOL]: true,\n error,\n meta,\n };\n}\n\n/**\n * Type guard for early exit objects.\n * @internal\n */\nexport function isEarlyExit<E>(e: unknown): e is EarlyExit<E> {\n return (\n typeof e === \"object\" &&\n e !== null &&\n (e as Record<PropertyKey, unknown>)[EARLY_EXIT_SYMBOL] === true\n );\n}\n\n/**\n * Symbol to mark exceptions thrown by catchUnexpected mappers.\n * These should propagate without being re-processed.\n * @internal\n */\nconst MAPPER_EXCEPTION_SYMBOL: unique symbol = Symbol(\"mapper-exception\");\n\ntype MapperException = {\n [MAPPER_EXCEPTION_SYMBOL]: true;\n thrown: unknown;\n};\n\nfunction createMapperException(thrown: unknown): MapperException {\n return { [MAPPER_EXCEPTION_SYMBOL]: true, thrown };\n}\n\nfunction isMapperException(e: unknown): e is MapperException {\n return (\n typeof e === \"object\" &&\n e !== null &&\n (e as Record<PropertyKey, unknown>)[MAPPER_EXCEPTION_SYMBOL] === true\n );\n}\n\n// =============================================================================\n// Retry and Timeout Utilities\n// =============================================================================\n\n/**\n * Calculate the delay for a retry attempt based on the backoff strategy.\n * @internal\n */\nfunction calculateRetryDelay(\n attempt: number,\n options: {\n backoff: BackoffStrategy;\n initialDelay: number;\n maxDelay: number;\n jitter: boolean;\n }\n): number {\n const { backoff, initialDelay, maxDelay, jitter } = options;\n\n let delay: number;\n\n switch (backoff) {\n case \"fixed\":\n delay = initialDelay;\n break;\n case \"linear\":\n delay = initialDelay * attempt;\n break;\n case \"exponential\":\n delay = initialDelay * Math.pow(2, attempt - 1);\n break;\n }\n\n // Apply max cap\n delay = Math.min(delay, maxDelay);\n\n // Apply jitter (0-25% of delay)\n if (jitter) {\n const jitterAmount = delay * 0.25 * Math.random();\n delay = delay + jitterAmount;\n }\n\n return Math.floor(delay);\n}\n\n/**\n * Sleep for a specified number of milliseconds.\n * @internal\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nconst DEFAULT_RETRY_ASYNC_CONFIG = {\n backoff: \"exponential\" as BackoffStrategy,\n initialDelay: 100,\n maxDelay: 30000,\n jitter: true,\n shouldRetry: (_error: unknown, _attempt: number) => true,\n onRetry: (_error: unknown, _attempt: number, _delayMs: number) => {},\n} as const;\n\n/**\n * Run an async function with retry. Reuses the same backoff and shouldRetry semantics as step.retry.\n * Use this when you want retries without the workflow/step machinery (e.g. in fetch).\n *\n * @param fn - Function that returns a Promise<Result<T, E>>\n * @param options - Retry configuration (attempts, backoff, shouldRetry, etc.)\n * @returns Promise that resolves to the last Result (ok or err). Rejects only if fn throws and shouldRetry returns false.\n */\nexport async function retryAsync<T, E>(\n fn: () => Promise<Result<T, E>>,\n options: RetryOptions\n): Promise<Result<T, E>> {\n const attempts = Math.max(1, options.attempts);\n const effective = {\n backoff: options.backoff ?? DEFAULT_RETRY_ASYNC_CONFIG.backoff,\n initialDelay: options.initialDelay ?? DEFAULT_RETRY_ASYNC_CONFIG.initialDelay,\n maxDelay: options.maxDelay ?? DEFAULT_RETRY_ASYNC_CONFIG.maxDelay,\n jitter: options.jitter ?? DEFAULT_RETRY_ASYNC_CONFIG.jitter,\n shouldRetry: options.shouldRetry ?? DEFAULT_RETRY_ASYNC_CONFIG.shouldRetry,\n onRetry: options.onRetry ?? DEFAULT_RETRY_ASYNC_CONFIG.onRetry,\n };\n\n let lastResult: Result<T, E> | undefined;\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n const result = await fn();\n if (result.ok) return result;\n lastResult = result;\n if (attempt < attempts && effective.shouldRetry(result.error, attempt)) {\n const delay = calculateRetryDelay(attempt, effective);\n effective.onRetry(result.error, attempt, delay);\n await sleep(delay);\n continue;\n }\n return result;\n } catch (thrown) {\n if (attempt < attempts && effective.shouldRetry(thrown, attempt)) {\n const delay = calculateRetryDelay(attempt, effective);\n effective.onRetry(thrown, attempt, delay);\n await sleep(delay);\n continue;\n }\n throw thrown;\n }\n }\n return lastResult!;\n}\n\n/**\n * Symbol used internally to identify timeout rejection.\n */\nconst TIMEOUT_SYMBOL: unique symbol = Symbol(\"timeout\");\nconst TIMEOUT_OPTION_SYMBOL: unique symbol = Symbol(\"timeout-option\");\n\n/**\n * Check if an error is a timeout option marker (should return undefined instead of error).\n * @internal\n */\nfunction isTimeoutOptionMarker(\n value: unknown\n): value is { [TIMEOUT_OPTION_SYMBOL]: true; ms: number } {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as Record<symbol, unknown>)[TIMEOUT_OPTION_SYMBOL] === true\n );\n}\n\n/**\n * Execute an operation with a timeout using Promise.race.\n * @internal\n */\nasync function executeWithTimeout<T>(\n operation: (() => Promise<T>) | ((signal: AbortSignal) => Promise<T>),\n options: TimeoutOptions,\n stepInfo: { name?: string; key?: string; attempt?: number },\n /** External signal (e.g., workflow cancellation) to combine with timeout signal */\n externalSignal?: AbortSignal\n): Promise<T> {\n const controller = new AbortController();\n const behavior = options.onTimeout ?? \"error\";\n\n // Create the timeout error based on behavior\n const createTimeoutError = (): unknown => {\n // For function behavior, call the handler to generate the error\n if (typeof behavior === \"function\") {\n return behavior({\n name: stepInfo.name,\n key: stepInfo.key,\n ms: options.ms,\n });\n }\n\n // For other behaviors, use custom error or default StepTimeoutError\n return (\n (options.error as StepTimeoutError) ?? {\n type: \"STEP_TIMEOUT\",\n stepName: stepInfo.name,\n stepKey: stepInfo.key,\n timeoutMs: options.ms,\n attempt: stepInfo.attempt,\n }\n );\n };\n\n // Track the timeout ID for cleanup\n let timeoutId: ReturnType<typeof setTimeout>;\n\n // If external signal is already aborted, abort immediately\n if (externalSignal?.aborted) {\n controller.abort(externalSignal.reason);\n }\n\n // Forward external signal abort to internal controller\n let externalAbortHandler: (() => void) | undefined;\n if (externalSignal && !externalSignal.aborted) {\n externalAbortHandler = () => controller.abort(externalSignal.reason);\n externalSignal.addEventListener(\"abort\", externalAbortHandler, { once: true });\n }\n\n // Create a timeout promise that rejects after the specified duration\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n // For 'disconnect', don't abort - let operation continue in background\n if (behavior !== \"disconnect\") {\n controller.abort();\n }\n\n // For 'option', throw special marker to return undefined\n if (behavior === \"option\") {\n reject({ [TIMEOUT_OPTION_SYMBOL]: true, ms: options.ms });\n return;\n }\n\n // For all other behaviors, throw the timeout error\n reject({ [TIMEOUT_SYMBOL]: true, error: createTimeoutError() });\n }, options.ms);\n });\n\n // Execute the operation\n let operationPromise: Promise<T>;\n if (options.signal) {\n // Operation expects an AbortSignal\n // Pass the internal controller's signal which is linked to both timeout and external signal\n operationPromise = Promise.resolve(\n (operation as (signal: AbortSignal) => Promise<T>)(controller.signal)\n );\n } else {\n // Standard operation\n operationPromise = Promise.resolve((operation as () => Promise<T>)());\n }\n\n try {\n // Race between operation and timeout\n const result = await Promise.race([operationPromise, timeoutPromise]);\n return result;\n } catch (error) {\n // Check if this was an 'option' timeout - return undefined as success\n if (\n typeof error === \"object\" &&\n error !== null &&\n (error as Record<symbol, unknown>)[TIMEOUT_OPTION_SYMBOL] === true\n ) {\n // Throw special marker that step handler will convert to ok(undefined)\n throw { [TIMEOUT_OPTION_SYMBOL]: true, ms: options.ms };\n }\n\n // Check if this was our timeout\n if (\n typeof error === \"object\" &&\n error !== null &&\n (error as Record<symbol, unknown>)[TIMEOUT_SYMBOL] === true\n ) {\n // For 'disconnect' behavior, the operation continues in the background\n // Attach a catch handler to prevent unhandled rejection if it fails later\n if (behavior === \"disconnect\") {\n operationPromise.catch(() => {\n // Intentionally swallowed - operation was disconnected\n });\n }\n\n const errorToThrow = (error as { error: unknown }).error;\n\n // Mark the error with STEP_TIMEOUT_MARKER if it's a custom error (not already a StepTimeoutError)\n // This allows isStepTimeoutError() and getStepTimeoutMeta() to work with custom errors\n // Note: Always update metadata to reflect the current attempt (same error may be reused across retries)\n if (\n typeof errorToThrow === \"object\" &&\n errorToThrow !== null &&\n (errorToThrow as StepTimeoutError).type !== \"STEP_TIMEOUT\"\n ) {\n const meta: StepTimeoutMarkerMeta = {\n timeoutMs: options.ms,\n stepName: stepInfo.name,\n stepKey: stepInfo.key,\n attempt: stepInfo.attempt,\n };\n\n if (STEP_TIMEOUT_MARKER in errorToThrow) {\n // Update existing marker with current attempt's metadata\n (errorToThrow as Record<symbol, StepTimeoutMarkerMeta>)[STEP_TIMEOUT_MARKER] = meta;\n } else {\n // Define new marker (writable so it can be updated on retry)\n Object.defineProperty(errorToThrow, STEP_TIMEOUT_MARKER, {\n value: meta,\n enumerable: false,\n writable: true,\n configurable: false,\n });\n }\n }\n\n throw errorToThrow;\n }\n // Re-throw other errors\n throw error;\n } finally {\n // Always clear the timeout to prevent leaks\n clearTimeout(timeoutId!);\n // Clean up external signal listener\n if (externalAbortHandler && externalSignal) {\n externalSignal.removeEventListener(\"abort\", externalAbortHandler);\n }\n }\n}\n\n/**\n * Default retry configuration values.\n * @internal\n */\nconst DEFAULT_RETRY_CONFIG = {\n backoff: \"exponential\" as BackoffStrategy,\n initialDelay: 100,\n maxDelay: 30000,\n jitter: true,\n shouldRetry: () => true,\n onRetry: () => {},\n} as const;\n\n// =============================================================================\n// run() Function\n// =============================================================================\n\n/**\n * Execute a workflow with step-based error handling.\n *\n * ## When to Use run()\n *\n * Use `run()` when:\n * - Dependencies are dynamic (passed at runtime, not known at compile time)\n * - You don't need step caching or resume state\n * - Error types are known upfront and can be specified manually\n * - Building lightweight, one-off workflows\n *\n * For automatic error type inference from static dependencies, use `createWorkflow()`.\n *\n * ## Error union\n *\n * `run()` returns:\n * - **`catchUnexpected`**: Maps uncaught exceptions to your type E → `Result<T, E>`\n * - **No catchUnexpected**: Step errors pass through + `UnexpectedError` for exceptions → `Result<T, E | UnexpectedError>`\n *\n * When `E` is not specified, it defaults to `never`, giving `Result<T, UnexpectedError>`.\n *\n * @see createWorkflow - For static dependencies with auto error inference\n */\n\n/**\n * run() with catchUnexpected: closed union Result<T, E>.\n */\nfunction runFn<T, E, C = void>(\n fn: (context: { step: RunStep<E> }) => Promise<T> | T,\n options: RunOptionsWithCatch<E, C>\n): AsyncResult<T, E, unknown>;\n\n/**\n * run() without catchUnexpected.\n * Always adds UnexpectedError to the error union so callers know\n * uncaught exceptions are possible. Step errors pass through as-is.\n * When E is never (default), step is RunStep<unknown> so any operation is allowed.\n */\nfunction runFn<T, E = never, C = void>(\n fn: (context: {\n step: [E] extends [never] ? RunStep<unknown> : RunStep<E>;\n }) => Promise<T> | T,\n options?: {\n onError?: (error: E | UnexpectedError, stepName?: string, ctx?: C) => void;\n onEvent?: (event: WorkflowEvent<E | UnexpectedError, C>, ctx: C) => void;\n workflowId?: string;\n workflowName?: string;\n context?: C;\n graph?: DeclaredGraph;\n /** @internal External signal for workflow-level cancellation. */\n _workflowSignal?: AbortSignal;\n }\n): AsyncResult<T, E | UnexpectedError, unknown>;\n\n/**\n * run() with dependencies: auto-bound steps and automatic error inference.\n *\n * Pass your functions as the first argument; the callback receives a steps\n * object mirroring them. Calling `s.getUser(id)` behaves exactly like\n * `step('getUser', () => getUser(id))` — unwraps ok, early-exits on err —\n * and the result's error union is inferred from the deps. No type\n * parameters, no string IDs, no thunks.\n *\n * Plain (non-Result) functions are valid deps: their values pass through\n * and their throws become UnexpectedError, so existing code works unchanged\n * and can adopt typed errors incrementally.\n *\n * @example\n * ```typescript\n * const result = await run({ getOrder, getUser, charge }, async (s) => {\n * const order = await s.getOrder(orderId);\n * const user = await s.getUser(order.userId);\n * return s.charge(order.total);\n * });\n * // result.error: OrderNotFound | UserNotFound | ChargeDeclined | UnexpectedError\n * ```\n */\nfunction runFn<const Deps extends Record<string, AnyFunction>, T, C = void>(\n deps: Deps,\n fn: (\n steps: BoundSteps<Deps>,\n context: {\n step: [ErrorsOf<Deps>] extends [never]\n ? RunStep<unknown>\n : RunStep<ErrorsOf<Deps>>;\n }\n ) => Promise<T> | T,\n options?: {\n onError?: (\n error: ErrorsOf<Deps> | UnexpectedError,\n stepName?: string,\n ctx?: C\n ) => void;\n onEvent?: (\n event: WorkflowEvent<ErrorsOf<Deps> | UnexpectedError, C>,\n ctx: C\n ) => void;\n workflowId?: string;\n workflowName?: string;\n context?: C;\n graph?: DeclaredGraph;\n /** @internal External signal for workflow-level cancellation. */\n _workflowSignal?: AbortSignal;\n }\n): AsyncResult<T, ErrorsOf<Deps> | UnexpectedError, unknown>;\n\n// Implementation\nasync function runFn<T, E, C = void>(\n fnOrDeps:\n | ((context: { step: RunStep<E> }) => Promise<T> | T)\n | Record<string, AnyFunction>,\n optionsOrFn?:\n | RunOptions<E, C>\n | ((\n steps: BoundSteps<Record<string, AnyFunction>>,\n context: { step: RunStep<E> }\n ) => Promise<T> | T),\n maybeOptions?: RunOptions<E, C>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): AsyncResult<T, any> {\n // Deps-first form: run(deps, fn, options?) — bind deps as steps and\n // re-enter through the classic form.\n if (typeof fnOrDeps !== \"function\") {\n const deps = fnOrDeps;\n const boundFn = optionsOrFn as (\n steps: BoundSteps<Record<string, AnyFunction>>,\n context: { step: RunStep<E> }\n ) => Promise<T> | T;\n if (typeof boundFn !== \"function\") {\n throw new TypeError(\n \"[awaitly] run(deps, fn) requires a callback as the second argument. \" +\n \"Example: run({ getUser }, async (s) => s.getUser(id))\"\n );\n }\n // Re-enter the implementation with the classic (fn, options) shape;\n // cast past the public overloads (same approach as runInternal).\n const classicRun = runFn as unknown as (\n fn: (context: { step: RunStep<E> }) => Promise<T> | T,\n options?: RunOptions<E, C>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ) => AsyncResult<T, any>;\n return classicRun(\n ({ step }) => boundFn(bindSteps(deps, step as StepCallable), { step }),\n maybeOptions\n );\n }\n\n const fn = fnOrDeps;\n const options = optionsOrFn as RunOptions<E, C> | undefined;\n const {\n onError,\n onEvent,\n catchUnexpected,\n workflowId: providedWorkflowId,\n workflowName,\n context,\n graph,\n _workflowSignal,\n } = options && typeof options === \"object\"\n ? (options as RunOptions<E, C>)\n : ({} as RunOptions<E, C>);\n\n const workflowId = providedWorkflowId ?? crypto.randomUUID();\n\n // Strict graph validation: when a declared graph is provided, every runtime\n // step/decision id must be one of its state ids. Ids with {placeholder}\n // segments (e.g. step.forEach's \"item-{i}\") match any value in that slot.\n const declaredIds = graph\n ? new Set(\n Array.isArray(graph)\n ? (graph as readonly string[])\n : (graph as {\n states: ReadonlyArray<{ id: string; semanticId?: string }>;\n }).states.map((state) => state.semanticId ?? state.id)\n )\n : undefined;\n const declaredPatterns = declaredIds\n ? [...declaredIds]\n .filter((id) => id.includes(\"{\"))\n .map(\n (id) =>\n new RegExp(\n `^${id.replaceAll(/[.*+?^$()[\\]\\\\|]/g, String.raw`\\$&`).replaceAll(/\\{[^}]*\\}/g, \".+\")}$`\n )\n )\n : undefined;\n const assertDeclared = (id: string, kind: \"step\" | \"decision\"): void => {\n if (!declaredIds || declaredIds.has(id)) return;\n if (declaredPatterns?.some((re) => re.test(id))) return;\n throw new Error(\n `[awaitly] ${kind} id \"${id}\" is not in the declared workflow graph. ` +\n `Declared ids: ${[...declaredIds].join(\", \")}. ` +\n `Either add it to the graph or remove the graph option.`\n );\n };\n const effectiveCatchUnexpected = catchUnexpected ?? defaultCatchUnexpected;\n\n // Track active scopes as a stack for proper nesting\n // When a step succeeds, only the innermost race scope gets the winner\n const activeScopeStack: Array<{ scopeId: string; type: ScopeType; winnerId?: string }> = [];\n\n // Counter for generating unique step IDs\n let stepIdCounter = 0;\n\n // Generate a unique step ID\n // Uses stepKey when provided (for cache stability), otherwise generates a unique ID.\n // Note: name is NOT used for stepId because multiple concurrent steps may share a name,\n // which would cause them to collide in activeSteps tracking and race winner detection.\n const generateStepId = (stepKey?: string): string => {\n return stepKey ?? `step_${++stepIdCounter}`;\n };\n\n const emitEvent = (event: WorkflowEvent<E | UnexpectedError, C>) => {\n // Add context to event only if:\n // 1. Event doesn't already have context (preserves replayed events or per-step overrides)\n // 2. Workflow actually has a context (don't add context: undefined property)\n const eventWithContext =\n event.context !== undefined || context === undefined\n ? event\n : ({ ...event, context: context as C } as WorkflowEvent<E | UnexpectedError, C>);\n\n const eventWithName =\n workflowName !== undefined && eventWithContext.workflowName === undefined\n ? ({ ...eventWithContext, workflowName } as WorkflowEvent<E | UnexpectedError, C>)\n : eventWithContext;\n \n // Track first successful step in the innermost race scope for winnerId\n if (eventWithName.type === \"step_success\") {\n // Use the stepId from the event (already generated at step start)\n const stepId = eventWithName.stepId;\n\n // Find innermost race scope (search from end of stack)\n for (let i = activeScopeStack.length - 1; i >= 0; i--) {\n const scope = activeScopeStack[i];\n if (scope.type === \"race\" && !scope.winnerId) {\n scope.winnerId = stepId;\n break; // Only update innermost race scope\n }\n }\n }\n onEvent?.(eventWithName, context as C);\n };\n\n // Use the exported early exit function with proper type parameter\n const earlyExit = createEarlyExit<E>;\n\n // Local type guard that narrows to EarlyExit<E> specifically\n const isEarlyExitE = (e: unknown): e is EarlyExit<E> => isEarlyExit(e);\n\n // Step errors always pass through — they are typed Result errors.\n // Only truly uncaught exceptions get mapped via effectiveCatchUnexpected.\n const wrapForStep = (\n error: unknown,\n _meta?: StepFailureMeta\n ): E => {\n return error as E;\n };\n\n // Helper to check if a value is a Result (has ok property) vs a function\n const isResultLike = (value: unknown): value is Result<unknown, unknown, unknown> | Promise<Result<unknown, unknown, unknown>> => {\n if (typeof value === 'function') return false;\n if (value && typeof value === 'object' && 'ok' in value) return true;\n // Check for Promise<Result> - it will have a then method\n if (value && typeof value === 'object' && 'then' in value && typeof (value as Promise<unknown>).then === 'function') return true;\n return false;\n };\n\n try {\n // Step function: requires step('id', fn, opts) or step('id', result, opts)\n const stepFn = <T, StepE, StepC = unknown>(\n id: string,\n operationOrResult: (() => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>) | Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>,\n stepOptions?: StepOptions\n ): Promise<T> => {\n return (async () => {\n // Validate required string ID\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(\n '[awaitly] step() requires an explicit string ID as the first argument. ' +\n 'Example: step(\"fetchUser\", () => fetchUser(id))'\n );\n }\n assertDeclared(id, \"step\");\n\n const parsedOptions: StepOptions = stepOptions ?? {};\n const stepMetadata = extractStepMetadata(parsedOptions);\n\n // Name is always derived from ID\n const stepName = id;\n const stepKey = parsedOptions.key ?? id; // For general events (step_start, step_success, etc.)\n const explicitKey = parsedOptions.key ?? id; // For step_complete and caching (ID is used when no key)\n const { description: stepDescription, retry: retryConfig, timeout: timeoutConfig } = parsedOptions;\n const stepId = generateStepId(stepKey);\n const hasEventListeners = onEvent;\n const overallStartTime = hasEventListeners ? performance.now() : 0;\n\n // Determine if this is a direct Result or a function\n const isDirectResult = isResultLike(operationOrResult);\n const operation = isDirectResult\n ? () => operationOrResult as Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>\n : operationOrResult as () => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>;\n\n // Build effective retry config with defaults\n // Ensure at least 1 attempt (0 would skip the loop entirely and crash)\n const maxAttempts = Math.max(1, retryConfig?.attempts ?? 1);\n const effectiveRetry = {\n attempts: maxAttempts,\n backoff: retryConfig?.backoff ?? DEFAULT_RETRY_CONFIG.backoff,\n initialDelay: retryConfig?.initialDelay ?? DEFAULT_RETRY_CONFIG.initialDelay,\n maxDelay: retryConfig?.maxDelay ?? DEFAULT_RETRY_CONFIG.maxDelay,\n jitter: retryConfig?.jitter ?? DEFAULT_RETRY_CONFIG.jitter,\n shouldRetry: retryConfig?.shouldRetry ?? DEFAULT_RETRY_CONFIG.shouldRetry,\n onRetry: retryConfig?.onRetry ?? DEFAULT_RETRY_CONFIG.onRetry,\n };\n\n // Emit step_start only once (before first attempt)\n if (onEvent) {\n emitEvent({\n type: \"step_start\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n description: stepDescription,\n ts: Date.now(),\n ...(stepMetadata && { metadata: stepMetadata }),\n });\n }\n\n let lastResult: Result<T, StepE, StepC> | undefined;\n\n for (let attempt = 1; attempt <= effectiveRetry.attempts; attempt++) {\n const attemptStartTime = hasEventListeners ? performance.now() : 0;\n\n try {\n // Execute operation with optional timeout\n let result: Result<T, StepE, StepC>;\n\n if (timeoutConfig) {\n // Wrap with timeout, passing workflow signal for { signal: true } steps\n result = await executeWithTimeout(\n operation as () => Promise<Result<T, StepE, StepC>>,\n timeoutConfig,\n { name: stepName, key: stepKey, attempt },\n _workflowSignal\n );\n } else {\n result = await operation();\n }\n\n // Success case\n if (result.ok) {\n const durationMs = performance.now() - overallStartTime;\n emitEvent({\n type: \"step_success\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n description: stepDescription,\n ts: Date.now(),\n durationMs,\n ...(stepMetadata && { metadata: stepMetadata }),\n });\n if (explicitKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey: explicitKey,\n name: stepName,\n description: stepDescription,\n ts: Date.now(),\n durationMs,\n result,\n ...(stepMetadata && { metadata: stepMetadata }),\n });\n }\n return result.value;\n }\n\n // Result error case - check if we should retry\n lastResult = result;\n\n if (attempt < effectiveRetry.attempts && effectiveRetry.shouldRetry(result.error, attempt)) {\n const delay = calculateRetryDelay(attempt, effectiveRetry);\n\n // Emit retry event\n emitEvent({\n type: \"step_retry\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n attempt: attempt + 1,\n maxAttempts: effectiveRetry.attempts,\n delayMs: delay,\n error: result.error as unknown as E,\n ...(stepMetadata && { metadata: stepMetadata }),\n diagnostics: buildStepErrorPayload(result.error, parsedOptions.errorMeta, 'result', attempt, performance.now() - overallStartTime),\n });\n\n effectiveRetry.onRetry(result.error, attempt, delay);\n await sleep(delay);\n continue;\n }\n\n // No more retries or shouldRetry returned false - emit exhausted event if we retried\n if (effectiveRetry.attempts > 1) {\n emitEvent({\n type: \"step_retries_exhausted\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs: performance.now() - overallStartTime,\n attempts: attempt,\n lastError: result.error as unknown as E,\n ...(stepMetadata && { metadata: stepMetadata }),\n diagnostics: buildStepErrorPayload(result.error, parsedOptions.errorMeta, 'result', attempt, performance.now() - overallStartTime),\n });\n }\n\n // Fall through to final error handling below\n break;\n\n } catch (thrown) {\n const durationMs = performance.now() - attemptStartTime;\n\n // Handle timeout with 'option' behavior - return undefined as success\n if (isTimeoutOptionMarker(thrown)) {\n const timeoutMs = thrown.ms;\n emitEvent({\n type: \"step_timeout\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n timeoutMs,\n attempt,\n ...(stepMetadata && { metadata: stepMetadata }),\n diagnostics: buildStepErrorPayload(thrown, parsedOptions.errorMeta, 'timeout', attempt),\n });\n emitEvent({\n type: \"step_success\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n description: stepDescription,\n ts: Date.now(),\n durationMs: performance.now() - overallStartTime,\n ...(stepMetadata && { metadata: stepMetadata }),\n });\n if (explicitKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey: explicitKey,\n name: stepName,\n description: stepDescription,\n ts: Date.now(),\n durationMs: performance.now() - overallStartTime,\n result: ok(undefined),\n ...(stepMetadata && { metadata: stepMetadata }),\n });\n }\n // Return undefined as success value (timeout was treated as optional)\n return undefined as T;\n }\n\n // Handle early exit - propagate immediately\n if (isEarlyExitE(thrown)) {\n emitEvent({\n type: \"step_aborted\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n description: stepDescription,\n ts: Date.now(),\n durationMs,\n ...(stepMetadata && { metadata: stepMetadata }),\n });\n throw thrown;\n }\n\n // Handle timeout error\n if (isStepTimeoutError(thrown)) {\n // Get timeout metadata from the error (works for both standard and custom errors)\n const timeoutMeta = getStepTimeoutMeta(thrown);\n const timeoutMs = timeoutConfig?.ms ?? timeoutMeta?.timeoutMs ?? 0;\n emitEvent({\n type: \"step_timeout\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n timeoutMs,\n attempt,\n ...(stepMetadata && { metadata: stepMetadata }),\n diagnostics: buildStepErrorPayload(thrown, parsedOptions.errorMeta, 'timeout', attempt),\n });\n\n // Check if we should retry after timeout\n if (attempt < effectiveRetry.attempts && effectiveRetry.shouldRetry(thrown, attempt)) {\n const delay = calculateRetryDelay(attempt, effectiveRetry);\n\n emitEvent({\n type: \"step_retry\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n attempt: attempt + 1,\n maxAttempts: effectiveRetry.attempts,\n delayMs: delay,\n error: thrown as unknown as E,\n ...(stepMetadata && { metadata: stepMetadata }),\n diagnostics: buildStepErrorPayload(thrown, parsedOptions.errorMeta, 'timeout', attempt, performance.now() - overallStartTime),\n });\n\n effectiveRetry.onRetry(thrown, attempt, delay);\n await sleep(delay);\n continue;\n }\n\n // No more retries - emit exhausted if we retried\n if (effectiveRetry.attempts > 1) {\n emitEvent({\n type: \"step_retries_exhausted\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs: performance.now() - overallStartTime,\n attempts: attempt,\n lastError: thrown as unknown as E,\n ...(stepMetadata && { metadata: stepMetadata }),\n diagnostics: buildStepErrorPayload(thrown, parsedOptions.errorMeta, 'timeout', attempt, performance.now() - overallStartTime),\n });\n }\n\n // Treat STEP_TIMEOUT as a typed error - exit directly without UnexpectedError wrapper\n // This provides better DX: users get STEP_TIMEOUT directly in result.error\n const totalDurationMs = performance.now() - overallStartTime;\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n description: stepDescription,\n ts: Date.now(),\n durationMs: totalDurationMs,\n error: thrown as unknown as E,\n ...(stepMetadata && { metadata: stepMetadata }),\n diagnostics: buildStepErrorPayload(thrown, parsedOptions.errorMeta, 'timeout', attempt, totalDurationMs),\n });\n if (explicitKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey: explicitKey,\n name: stepName,\n description: stepDescription,\n ts: Date.now(),\n durationMs: totalDurationMs,\n result: err(thrown as unknown as E, { cause: thrown }),\n meta: { origin: \"throw\", thrown },\n ...(stepMetadata && { metadata: stepMetadata }),\n });\n }\n onError?.(thrown as unknown as E, stepName, context);\n throw earlyExit(thrown as unknown as E, { origin: \"throw\", thrown });\n }\n\n // Handle other thrown errors (continue to error handling below)\n\n // Check if we should retry thrown errors\n if (attempt < effectiveRetry.attempts && effectiveRetry.shouldRetry(thrown, attempt)) {\n const delay = calculateRetryDelay(attempt, effectiveRetry);\n\n emitEvent({\n type: \"step_retry\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n attempt: attempt + 1,\n maxAttempts: effectiveRetry.attempts,\n delayMs: delay,\n error: thrown as unknown as E,\n ...(stepMetadata && { metadata: stepMetadata }),\n diagnostics: buildStepErrorPayload(thrown, parsedOptions.errorMeta, 'throw', attempt, performance.now() - overallStartTime),\n });\n\n effectiveRetry.onRetry(thrown, attempt, delay);\n await sleep(delay);\n continue;\n }\n\n // No more retries for thrown errors - emit exhausted if we retried\n if (effectiveRetry.attempts > 1 && !isStepTimeoutError(thrown)) {\n emitEvent({\n type: \"step_retries_exhausted\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs: performance.now() - overallStartTime,\n attempts: attempt,\n lastError: thrown as unknown as E,\n ...(stepMetadata && { metadata: stepMetadata }),\n diagnostics: buildStepErrorPayload(thrown, parsedOptions.errorMeta, 'throw', attempt, performance.now() - overallStartTime),\n });\n }\n\n // Handle the error using effectiveCatchUnexpected\n const totalDurationMs = performance.now() - overallStartTime;\n\n let mappedError: E | UnexpectedError;\n try {\n mappedError = effectiveCatchUnexpected(thrown) as E | UnexpectedError;\n } catch (mapperError) {\n throw createMapperException(mapperError);\n }\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n description: stepDescription,\n ts: Date.now(),\n durationMs: totalDurationMs,\n error: mappedError,\n ...(stepMetadata && { metadata: stepMetadata }),\n diagnostics: buildStepErrorPayload(thrown, parsedOptions.errorMeta, 'throw', attempt, totalDurationMs),\n });\n if (explicitKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey: explicitKey,\n name: stepName,\n description: stepDescription,\n ts: Date.now(),\n durationMs: totalDurationMs,\n result: err(mappedError, { cause: thrown }),\n meta: { origin: \"throw\", thrown },\n ...(stepMetadata && { metadata: stepMetadata }),\n });\n }\n onError?.(mappedError as E, stepName, context);\n throw earlyExit(mappedError as E, { origin: \"throw\", thrown });\n }\n }\n\n // All retries exhausted with Result error - handle final error\n // At this point lastResult must be an error result (we only reach here on error)\n const errorResult = lastResult as Err<StepE, StepC>;\n const totalDurationMs = performance.now() - overallStartTime;\n const wrappedError = wrapForStep(errorResult.error, {\n origin: \"result\",\n resultCause: errorResult.cause,\n });\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n description: stepDescription,\n ts: Date.now(),\n durationMs: totalDurationMs,\n error: wrappedError,\n ...(stepMetadata && { metadata: stepMetadata }),\n diagnostics: buildStepErrorPayload(errorResult.error, parsedOptions.errorMeta, 'result', effectiveRetry.attempts, totalDurationMs),\n });\n if (explicitKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey: explicitKey,\n name: stepName,\n description: stepDescription,\n ts: Date.now(),\n durationMs: totalDurationMs,\n result: errorResult,\n meta: { origin: \"result\", resultCause: errorResult.cause },\n ...(stepMetadata && { metadata: stepMetadata }),\n });\n }\n onError?.(wrappedError as unknown as E, stepName, context);\n throw earlyExit(wrappedError as unknown as E, {\n origin: \"result\",\n resultCause: errorResult.cause,\n });\n })();\n };\n\n stepFn.try = <T, Err>(\n id: string,\n operation: () => T | Promise<T>,\n opts:\n | {\n error: Err;\n key?: string;\n ttl?: number;\n retry?: RetryOptions<Err>;\n timeout?: TimeoutOptions;\n compensate?: (value: T) => void | Promise<void>;\n }\n | {\n onError: (cause: unknown) => Err;\n key?: string;\n ttl?: number;\n retry?: RetryOptions<Err>;\n timeout?: TimeoutOptions;\n compensate?: (value: T) => void | Promise<void>;\n }\n ): Promise<T> => {\n // Validate required string ID\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(\n '[awaitly] step.try() requires an explicit string ID as the first argument. ' +\n 'Example: step.try(\"parse\", () => JSON.parse(str), { error: \"PARSE_ERROR\" })'\n );\n }\n assertDeclared(id, \"step\");\n\n const mapToError = \"error\" in opts ? () => opts.error : opts.onError;\n\n // If retry or timeout is requested, delegate to step.retry (which handles both).\n if (opts.retry || opts.timeout) {\n return stepFn.retry(\n id,\n async () => {\n try {\n return ok(await operation());\n } catch (cause) {\n return err(mapToError(cause), { cause });\n }\n },\n {\n attempts: opts.retry?.attempts ?? 1,\n ...(opts.retry ?? {}),\n key: opts.key,\n timeout: opts.timeout,\n }\n );\n }\n\n const stepKey = opts.key ?? id; // Use id as key if not provided\n const stepName = id; // Name is always the id\n const stepId = id;\n const hasEventListeners = onEvent;\n\n return (async () => {\n const startTime = hasEventListeners ? performance.now() : 0;\n\n if (onEvent) {\n emitEvent({\n type: \"step_start\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n });\n }\n\n try {\n const value = await operation();\n const durationMs = performance.now() - startTime;\n emitEvent({\n type: \"step_success\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n });\n // Emit step_complete for keyed steps (for state persistence)\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: ok(value),\n });\n }\n return value;\n } catch (error) {\n const mapped = mapToError(error);\n const durationMs = performance.now() - startTime;\n const wrappedError = wrapForStep(mapped, { origin: \"throw\", thrown: error });\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n error: wrappedError,\n });\n // Emit step_complete for keyed steps (for state persistence)\n // Note: For step.try errors, we encode the mapped error, not the original thrown\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: err(mapped, { cause: error }),\n meta: { origin: \"throw\", thrown: error },\n });\n }\n onError?.(wrappedError as unknown as E, stepName, context);\n throw earlyExit(wrappedError as unknown as E, { origin: \"throw\", thrown: error });\n }\n })();\n };\n\n // step.fromResult: Execute a Result-returning function and map its typed error\n stepFn.fromResult = <T, ResultE, Err>(\n id: string,\n operation: () => Result<T, ResultE, unknown> | AsyncResult<T, ResultE, unknown>,\n opts:\n | { error: Err; key?: string }\n | { onError: (resultError: ResultE) => Err; key?: string }\n ): Promise<T> => {\n // Validate required string ID\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(\n '[awaitly] step.fromResult() requires an explicit string ID as the first argument. ' +\n 'Example: step.fromResult(\"callProvider\", () => callProvider(input), { onError: (e) => ({ type: \"FAILED\" }) })'\n );\n }\n assertDeclared(id, \"step\");\n\n const stepKey = opts.key ?? id; // Use id as key if not provided\n const stepName = id; // Name is always the id\n const stepId = id;\n const mapToError = \"error\" in opts ? () => opts.error : opts.onError;\n const hasEventListeners = onEvent;\n\n return (async () => {\n const startTime = hasEventListeners ? performance.now() : 0;\n\n if (onEvent) {\n emitEvent({\n type: \"step_start\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n });\n }\n\n const result = await operation();\n\n if (result.ok) {\n const durationMs = performance.now() - startTime;\n emitEvent({\n type: \"step_success\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n });\n // Emit step_complete for keyed steps (for state persistence)\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: ok(result.value),\n });\n }\n return result.value;\n } else {\n const mapped = mapToError(result.error);\n const durationMs = performance.now() - startTime;\n // For fromResult, the cause is the original result.error (what got mapped)\n // This is analogous to step.try using thrown exception as cause\n const wrappedError = wrapForStep(mapped, {\n origin: \"result\",\n resultCause: result.error,\n });\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n error: wrappedError,\n });\n // Emit step_complete for keyed steps (for state persistence)\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: err(mapped, { cause: result.error }),\n meta: { origin: \"result\", resultCause: result.error },\n });\n }\n onError?.(wrappedError as unknown as E, stepName, context);\n throw earlyExit(wrappedError as unknown as E, {\n origin: \"result\",\n resultCause: result.error,\n });\n }\n })();\n };\n\n // step.fromNullable: Execute an operation returning T | null/undefined and convert to typed error\n stepFn.fromNullable = <T, Err>(\n id: string,\n operation: () => T | null | undefined | Promise<T | null | undefined>,\n onNull: () => Err,\n options?: { key?: string; ttl?: number }\n ): Promise<T> => {\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(\n '[awaitly] step.fromNullable() requires an explicit string ID as the first argument. ' +\n 'Example: step.fromNullable(\"getUser\", () => db.find(id), () => ({ type: \"NOT_FOUND\" }))'\n );\n }\n return stepFn(\n id,\n async () => {\n const value = await operation();\n return value != null ? ok(value) : err(onNull());\n },\n options\n );\n };\n\n // step.retry: Execute an operation with retry and optional timeout\n stepFn.retry = <T, StepE, StepC = unknown>(\n id: string,\n operation: () => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>,\n options: RetryOptions<StepE> & { key?: string; timeout?: TimeoutOptions }\n ): Promise<T> => {\n // Validate required string ID\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(\n '[awaitly] step.retry() requires an explicit string ID as the first argument. ' +\n 'Example: step.retry(\"fetchData\", () => fetchData(), { attempts: 3 })'\n );\n }\n\n // Delegate to stepFn with retry options merged into StepOptions\n // Use key for caching if provided, otherwise use id\n return stepFn(id, operation, {\n key: options.key ?? id,\n retry: {\n attempts: options.attempts,\n backoff: options.backoff,\n initialDelay: options.initialDelay,\n maxDelay: options.maxDelay,\n jitter: options.jitter,\n shouldRetry: options.shouldRetry as RetryOptions[\"shouldRetry\"],\n onRetry: options.onRetry as RetryOptions[\"onRetry\"],\n },\n timeout: options.timeout,\n });\n };\n\n // step.withTimeout: Execute an operation with a timeout\n stepFn.withTimeout = <T, StepE, StepC = unknown>(\n id: string,\n operation:\n | (() => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>)\n | ((signal: AbortSignal) => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>),\n options: TimeoutOptions & { key?: string }\n ): Promise<T> => {\n // Validate required string ID\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(\n '[awaitly] step.withTimeout() requires an explicit string ID as the first argument. ' +\n 'Example: step.withTimeout(\"slowOp\", () => slowOp(), { ms: 5000 })'\n );\n }\n\n // Delegate to stepFn with timeout options\n // The signal handling happens in executeWithTimeout when timeout.signal is true\n // Use key for caching if provided, otherwise use id\n return stepFn(\n id,\n operation as () => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>,\n {\n key: options.key ?? id,\n timeout: options,\n }\n );\n };\n\n // step.sleep: Pause execution for a specified duration\n stepFn.sleep = (\n id: string,\n duration: DurationInput,\n options?: { key?: string; ttl?: number; description?: string; signal?: AbortSignal }\n ): Promise<void> => {\n // Validate required string ID\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(\n '[awaitly] step.sleep() requires an explicit string ID as the first argument. ' +\n 'Example: step.sleep(\"delay\", \"5s\")'\n );\n }\n\n // Parse duration - inline to avoid importing duration module\n const d = typeof duration === \"string\" ? parseDurationString(duration) : duration;\n if (!d) {\n throw new Error(`step.sleep: invalid duration '${duration}'`);\n }\n const ms = d.millis;\n const userSignal = options?.signal;\n\n // Delegate to stepFn with a cancellation-aware sleep operation\n // Use key for caching if provided, otherwise use id\n return stepFn(\n id,\n async (): AsyncResult<void, never> => {\n // Check if already aborted (workflow or user signal)\n if (_workflowSignal?.aborted || userSignal?.aborted) {\n const e = new Error(\"Sleep aborted\");\n e.name = \"AbortError\";\n throw e;\n }\n\n return new Promise<Result<void, never>>((resolve, reject) => {\n // Using object to avoid prefer-const warning while allowing\n // onAbort to reference the timeout before it's assigned\n const state = { timeoutId: undefined as ReturnType<typeof setTimeout> | undefined };\n\n const onAbort = () => {\n if (state.timeoutId) clearTimeout(state.timeoutId);\n const e = new Error(\"Sleep aborted\");\n e.name = \"AbortError\";\n reject(e);\n };\n\n _workflowSignal?.addEventListener(\"abort\", onAbort, { once: true });\n userSignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n state.timeoutId = setTimeout(() => {\n _workflowSignal?.removeEventListener(\"abort\", onAbort);\n userSignal?.removeEventListener(\"abort\", onAbort);\n resolve(ok(undefined));\n }, ms);\n });\n },\n {\n key: options?.key ?? id,\n description: options?.description,\n }\n );\n };\n\n // step.all: Execute parallel operations with scope events\n // 1. Object form: step.all(name, { key: fn | { fn, errors } })\n // 2. Array form: step.all(name, () => allAsync([...]))\n stepFn.all = ((...args: unknown[]): Promise<unknown> => {\n if (typeof args[0] !== \"string\") {\n throw new TypeError(\n \"step.all(name, ...): first argument must be a string (step name). Example: step.all('Fetch data', { user: () => fetchUser(), posts: () => fetchPosts() })\"\n );\n }\n const name = args[0] as string;\n const second = args[1];\n if (typeof second === \"function\") {\n return executeParallelArray(name, second as () => MaybeAsyncResult<unknown[], unknown, unknown>);\n }\n if (second && typeof second === \"object\" && !Array.isArray(second)) {\n const rawOperations = second as Record<string, (() => MaybeAsyncResult<unknown, unknown, unknown>) | ParallelOperationDescriptor<unknown, readonly string[]>>;\n const normalizedOperations = normalizeParallelOperations(rawOperations);\n return executeParallelNamed(normalizedOperations, { name });\n }\n throw new TypeError(\n \"step.all(name, ...): second argument must be a function (array form) or an object of operations (object form).\"\n );\n }) as RunStep<E>[\"all\"];\n\n function normalizeParallelOperations(\n rawOperations: Record<string, (() => MaybeAsyncResult<unknown, unknown, unknown>) | ParallelOperationDescriptor<unknown, readonly string[]>>\n ): Record<string, () => MaybeAsyncResult<unknown, unknown, unknown>> {\n const out: Record<string, () => MaybeAsyncResult<unknown, unknown, unknown>> = {};\n for (const [key, value] of Object.entries(rawOperations)) {\n if (typeof value === \"function\") {\n out[key] = value;\n } else if (value && typeof value === \"object\" && \"fn\" in value) {\n out[key] = value.fn;\n } else {\n throw new TypeError(`step.all: operation \"${key}\" must be a function or { fn, errors? } object`);\n }\n }\n return out;\n }\n\n // Array form implementation\n function executeParallelArray<T>(\n name: string,\n operation: () => MaybeAsyncResult<T[], unknown, unknown>\n ): Promise<T[]> {\n const scopeId = `scope_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n\n return (async () => {\n const startTime = performance.now();\n let scopeEnded = false;\n\n // Push this scope onto the stack for proper nesting tracking\n activeScopeStack.push({ scopeId, type: \"parallel\" });\n\n // Helper to emit scope_end exactly once\n const emitScopeEnd = () => {\n if (scopeEnded) return;\n scopeEnded = true;\n // Pop this scope from the stack\n const idx = activeScopeStack.findIndex(s => s.scopeId === scopeId);\n if (idx !== -1) activeScopeStack.splice(idx, 1);\n emitEvent({\n type: \"scope_end\",\n workflowId,\n scopeId,\n ts: Date.now(),\n durationMs: performance.now() - startTime,\n });\n };\n\n // Emit scope_start event\n emitEvent({\n type: \"scope_start\",\n workflowId,\n scopeId,\n scopeType: \"parallel\",\n name,\n ts: Date.now(),\n });\n\n try {\n const result = await operation();\n\n // Emit scope_end before processing result\n emitScopeEnd();\n\n if (!result.ok) {\n onError?.(result.error as unknown as E, name, context);\n throw earlyExit(result.error as unknown as E, {\n origin: \"result\",\n resultCause: result.cause,\n });\n }\n\n return result.value;\n } catch (error) {\n // Always emit scope_end in finally-like fashion\n emitScopeEnd();\n throw error;\n }\n })();\n }\n\n // Named object form implementation - execute each operation in parallel\n function executeParallelNamed<T extends Record<string, unknown>>(\n operations: Record<string, () => MaybeAsyncResult<unknown, unknown, unknown>>,\n options: { name?: string }\n ): Promise<T> {\n const keys = Object.keys(operations);\n const name = options.name ?? `Parallel(${keys.join(\", \")})`;\n const scopeId = `scope_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n\n return (async () => {\n const startTime = performance.now();\n let scopeEnded = false;\n\n // Push this scope onto the stack for proper nesting tracking\n activeScopeStack.push({ scopeId, type: \"parallel\" });\n\n // Helper to emit scope_end exactly once\n const emitScopeEnd = () => {\n if (scopeEnded) return;\n scopeEnded = true;\n const idx = activeScopeStack.findIndex(s => s.scopeId === scopeId);\n if (idx !== -1) activeScopeStack.splice(idx, 1);\n emitEvent({\n type: \"scope_end\",\n workflowId,\n scopeId,\n ts: Date.now(),\n durationMs: performance.now() - startTime,\n });\n };\n\n // Emit scope_start event with operation names in metadata\n emitEvent({\n type: \"scope_start\",\n workflowId,\n scopeId,\n scopeType: \"parallel\",\n name,\n ts: Date.now(),\n });\n\n try {\n // Execute all operations in parallel, fail-fast on first error\n const results = await new Promise<{ key: string; result: Result<unknown, unknown, unknown> }[]>((resolve) => {\n if (keys.length === 0) {\n resolve([]);\n return;\n }\n\n let settled = false;\n let pendingCount = keys.length;\n const resultArray: { key: string; result: Result<unknown, unknown, unknown> }[] = new Array(keys.length);\n\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n const index = i;\n\n Promise.resolve(operations[key]())\n .catch((reason) => err(\n { type: \"PROMISE_REJECTED\" as const, cause: reason },\n { cause: { type: \"PROMISE_REJECTION\" as const, reason } }\n ))\n .then((result) => {\n if (settled) return;\n\n // Fail-fast: if any operation fails, resolve immediately with just the failed entry\n if (!result.ok) {\n settled = true;\n resolve([{ key, result }]);\n return;\n }\n\n resultArray[index] = { key, result };\n pendingCount--;\n\n if (pendingCount === 0) {\n resolve(resultArray);\n }\n });\n }\n });\n\n // Emit scope_end before processing results\n emitScopeEnd();\n\n // Check for errors and build result object\n const output: Record<string, unknown> = {};\n for (const { key, result } of results) {\n if (!result.ok) {\n onError?.(result.error as unknown as E, key, context);\n throw earlyExit(result.error as unknown as E, {\n origin: \"result\",\n resultCause: result.cause,\n });\n }\n output[key] = result.value;\n }\n\n return output as T;\n } catch (error) {\n // Always emit scope_end in finally-like fashion\n emitScopeEnd();\n throw error;\n }\n })();\n }\n\n // step.race: Execute a race operation with scope events\n stepFn.race = <T, StepE, StepC>(\n name: string,\n operation: () => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>\n ): Promise<T> => {\n const scopeId = `scope_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n\n return (async () => {\n const startTime = performance.now();\n let scopeEnded = false;\n\n // Push this race scope onto the stack to track the first successful step as winner\n const scopeEntry = { scopeId, type: \"race\" as const, winnerId: undefined as string | undefined };\n activeScopeStack.push(scopeEntry);\n\n // Helper to emit scope_end exactly once, including winnerId\n const emitScopeEnd = () => {\n if (scopeEnded) return;\n scopeEnded = true;\n // Pop this scope from the stack\n const idx = activeScopeStack.findIndex(s => s.scopeId === scopeId);\n if (idx !== -1) activeScopeStack.splice(idx, 1);\n emitEvent({\n type: \"scope_end\",\n workflowId,\n scopeId,\n ts: Date.now(),\n durationMs: performance.now() - startTime,\n winnerId: scopeEntry.winnerId,\n });\n };\n\n // Emit scope_start event\n emitEvent({\n type: \"scope_start\",\n workflowId,\n scopeId,\n scopeType: \"race\",\n name,\n ts: Date.now(),\n });\n\n try {\n const result = await operation();\n\n // Emit scope_end before processing result\n emitScopeEnd();\n\n if (!result.ok) {\n onError?.(result.error as unknown as E, name, context);\n throw earlyExit(result.error as unknown as E, {\n origin: \"result\",\n resultCause: result.cause,\n });\n }\n\n return result.value;\n } catch (error) {\n // Always emit scope_end in finally-like fashion\n emitScopeEnd();\n throw error;\n }\n })();\n };\n\n // step.if: Mark a conditional for static analysis\n // Runtime: executes the condition and emits a decision event so\n // visualizers see which branch fired without manual instrumentation\n // Analyzer: extracts the id and conditionLabel for DecisionNode\n stepFn.if = <T extends boolean>(\n id: string,\n conditionLabel: string,\n condition: () => T\n ): T => {\n assertDeclared(id, \"decision\");\n const value = condition();\n emitEvent({\n type: \"decision\",\n workflowId,\n decisionId: id,\n label: conditionLabel,\n branch: value ? \"then\" : \"else\",\n value,\n ts: Date.now(),\n });\n return value;\n };\n\n // step.label: Alias for step.if - mark a conditional for static analysis\n // Use step.label for strict mode when conditionals contain step calls\n stepFn.label = stepFn.if;\n\n // step.branch: Execute a branch with explicit metadata for static analysis\n // Runtime: evaluates condition and executes appropriate arm\n // Analyzer: extracts branch metadata (conditionLabel, per-arm errors, out)\n stepFn.branch = async <\n T,\n const ThenErrs extends readonly string[] = readonly [],\n const ElseErrs extends readonly string[] = readonly [],\n const Out extends string | undefined = undefined,\n >(\n id: string,\n options: BranchOptions<T, ThenErrs, ElseErrs, Out>\n ): Promise<T> => {\n const { condition, then: thenFn, else: elseFn } = options;\n assertDeclared(id, \"decision\");\n const conditionResult = condition();\n const branch = conditionResult ? \"then\" : \"else\";\n const startTime = performance.now();\n // step.branch owns arm execution, so the decision is a real scope:\n // phase \"start\" before the arm runs, phase \"end\" after it settles.\n // Visualizers nest the arm's steps inside the taken branch.\n emitEvent({\n type: \"decision\",\n workflowId,\n decisionId: id,\n label: options.conditionLabel,\n branch,\n value: conditionResult,\n phase: \"start\",\n ts: Date.now(),\n });\n const emitEnd = () => {\n emitEvent({\n type: \"decision\",\n workflowId,\n decisionId: id,\n label: options.conditionLabel,\n branch,\n value: conditionResult,\n phase: \"end\",\n durationMs: performance.now() - startTime,\n ts: Date.now(),\n });\n };\n try {\n if (conditionResult) {\n return await thenFn();\n } else if (elseFn) {\n return await elseFn();\n }\n return undefined as T;\n } finally {\n emitEnd();\n }\n };\n\n // step.arm: Create an arm definition for use with step.branch\n // Runtime: returns the arm definition unchanged\n // Analyzer: extracts arm metadata\n stepFn.arm = <T, const Errs extends readonly string[] = readonly []>(\n fn: () => T | Promise<T>,\n errors?: Errs\n ): ArmDefinition<T, Errs> => {\n return { fn, errors };\n };\n\n // step.forEach: Execute a forEach loop with static analysis support\n // Supports both simple (run) and complex (item) forms\n stepFn.forEach = async <T, R>(\n _id: string,\n items: Iterable<T> | AsyncIterable<T>,\n options: ForEachRunOptions<T, R, readonly string[]> | ForEachItemOptions<T, R>\n ): Promise<R[]> => {\n const results: R[] = [];\n const maxIterations = options.maxIterations;\n let index = 0;\n\n // Check if this is the run form or item form\n const isRunForm = 'run' in options;\n\n // Convert items to async iterable for uniform handling\n const asyncItems = Symbol.asyncIterator in (items as object)\n ? (items as AsyncIterable<T>)\n : (async function* () { yield* items as Iterable<T>; })();\n\n for await (const item of asyncItems) {\n if (maxIterations !== undefined && index >= maxIterations) {\n break;\n }\n\n let result: R;\n if (isRunForm) {\n const runOptions = options as ForEachRunOptions<T, R, readonly string[]>;\n result = await runOptions.run(item, index);\n } else {\n const itemOptions = options as ForEachItemOptions<T, R>;\n result = await itemOptions.item.handler(item, index, stepFn as unknown as RunStep<unknown>);\n }\n\n results.push(result);\n index++;\n }\n\n return results;\n };\n\n // step.item: Create an item handler for use with step.forEach\n // Runtime: returns the handler wrapped in a marker object\n // Analyzer: extracts the inner step structure\n stepFn.item = <T, R>(\n handler: (item: T, index: number, step: RunStep<unknown>) => R | Promise<R>\n ): ForEachItemHandler<T, R> => {\n return {\n __forEachItemHandler: true as const,\n handler,\n };\n };\n\n // step.dep: Wrap a dependency function for static analysis tracking\n // Runtime: returns the function unchanged\n // Analyzer: records the dependency name\n stepFn.dep = <T extends (...args: unknown[]) => unknown>(\n _name: string,\n fn: T\n ): T => {\n return fn;\n };\n\n // ===========================================================================\n // Effect-Style Ergonomics\n // ===========================================================================\n\n // step.workflow: Run sub-workflow (or any AsyncResult getter) as a step; same engine as step(id, getter, opts)\n stepFn.workflow = <T, SubE, StepC = unknown>(\n id: string,\n getter: () => AsyncResult<T, SubE, StepC>,\n options?: StepOptions\n ): Promise<T> => {\n return stepFn(id, getter as () => AsyncResult<T, E, StepC>, options);\n };\n\n // step.map: Map over array with parallel execution\n stepFn.map = async <T, U, StepE, StepC = unknown>(\n id: string,\n items: T[],\n mapper: (item: T, index: number) => AsyncResult<U, StepE, StepC>,\n options?: { concurrency?: number; key?: string }\n ): Promise<U[]> => {\n const concurrency = options?.concurrency ?? items.length;\n\n // Use allAsync for parallel execution with fail-fast\n return stepFn(\n id,\n () => {\n if (concurrency >= items.length) {\n // Full parallelism - execute all at once\n return allAsync(items.map((item, index) => mapper(item, index)));\n } else {\n // Limited concurrency - batch execution\n return (async () => {\n const results: U[] = [];\n for (let i = 0; i < items.length; i += concurrency) {\n const batch = items.slice(i, i + concurrency);\n const batchResult = await allAsync(\n batch.map((item, batchIndex) => mapper(item, i + batchIndex))\n );\n // allAsync returns Result<U[], E, C>, so we need to check if it's ok\n if (!batchResult.ok) {\n return batchResult; // Propagate the error\n }\n results.push(...batchResult.value);\n }\n return ok(results);\n })();\n }\n },\n { key: options?.key }\n );\n };\n\n // step.withFallback: Execute primary with fallback on error\n stepFn.withFallback = <T, E1, E2>(\n id: string,\n operation: () => AsyncResult<T, E1>,\n options: { on?: E1 & string; fallback: () => AsyncResult<T, E2>; key?: string }\n ): Promise<T> => {\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(\n '[awaitly] step.withFallback() requires an explicit string ID as the first argument. ' +\n 'Example: step.withFallback(\"getUser\", () => fetchUser(id), { fallback: () => fetchFromCache(id) })'\n );\n }\n assertDeclared(id, \"step\");\n\n const stepKey = options.key ?? id;\n const stepName = id;\n const stepId = generateStepId(stepKey);\n const hasEventListeners = onEvent;\n\n return (async () => {\n const startTime = hasEventListeners ? performance.now() : 0;\n\n if (onEvent) {\n emitEvent({\n type: \"step_start\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n });\n }\n\n // Try the primary operation\n let primaryResult: Result<T, E1>;\n try {\n primaryResult = await operation();\n } catch (thrown) {\n // If it's an earlyExit from a nested step, propagate\n if (isEarlyExitE(thrown)) {\n emitEvent({\n type: \"step_aborted\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs: performance.now() - startTime,\n });\n throw thrown;\n }\n\n // Primary threw — map to UnexpectedError\n let mappedError: E | UnexpectedError;\n try {\n mappedError = effectiveCatchUnexpected(thrown) as E | UnexpectedError;\n } catch (mapperError) {\n throw createMapperException(mapperError);\n }\n\n // If `on` is specified, only run fallback if it matches the mapped error\n if (options.on !== undefined && options.on !== mappedError) {\n const durationMs = performance.now() - startTime;\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n error: mappedError,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: err(mappedError, { cause: thrown }),\n meta: { origin: \"throw\", thrown },\n });\n }\n onError?.(mappedError as E, stepName, context);\n throw earlyExit(mappedError as E, { origin: \"throw\", thrown });\n }\n\n // Run fallback for thrown error\n let fallbackResultFromThrow: Result<T, E2>;\n try {\n fallbackResultFromThrow = await options.fallback();\n } catch (fallbackThrown) {\n if (isEarlyExitE(fallbackThrown)) {\n emitEvent({\n type: \"step_aborted\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs: performance.now() - startTime,\n });\n throw fallbackThrown;\n }\n let fallbackMappedError: E | UnexpectedError;\n try {\n fallbackMappedError = effectiveCatchUnexpected(fallbackThrown) as E | UnexpectedError;\n } catch (mapperError) {\n throw createMapperException(mapperError);\n }\n const durationMs = performance.now() - startTime;\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n error: fallbackMappedError,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: err(fallbackMappedError, { cause: fallbackThrown }),\n meta: { origin: \"throw\", thrown: fallbackThrown },\n });\n }\n onError?.(fallbackMappedError as E, stepName, context);\n throw earlyExit(fallbackMappedError as E, { origin: \"throw\", thrown: fallbackThrown });\n }\n\n if (fallbackResultFromThrow.ok) {\n const durationMs = performance.now() - startTime;\n emitEvent({\n type: \"step_success\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: fallbackResultFromThrow,\n meta: { origin: \"fallback\" as const, fallbackUsed: true as const, fallbackReason: String(mappedError) },\n });\n }\n return fallbackResultFromThrow.value;\n } else {\n // Fallback also failed\n const durationMs = performance.now() - startTime;\n const wrappedError = wrapForStep(fallbackResultFromThrow.error, {\n origin: \"result\",\n resultCause: fallbackResultFromThrow.cause,\n });\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n error: wrappedError,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: fallbackResultFromThrow,\n meta: { origin: \"result\", resultCause: fallbackResultFromThrow.cause },\n });\n }\n onError?.(wrappedError as unknown as E, stepName, context);\n throw earlyExit(wrappedError as unknown as E, {\n origin: \"result\",\n resultCause: fallbackResultFromThrow.cause,\n });\n }\n }\n\n // Primary returned a result (didn't throw)\n if (primaryResult.ok) {\n const durationMs = performance.now() - startTime;\n emitEvent({\n type: \"step_success\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: primaryResult,\n });\n }\n return primaryResult.value;\n }\n\n // Primary returned an error\n const primaryError = primaryResult.error;\n\n // If `on` is specified and doesn't match, earlyExit with primary error (no fallback)\n if (options.on !== undefined && options.on !== primaryError) {\n const durationMs = performance.now() - startTime;\n const wrappedError = wrapForStep(primaryError, {\n origin: \"result\",\n resultCause: primaryResult.cause,\n });\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n error: wrappedError,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: primaryResult,\n meta: { origin: \"result\", resultCause: primaryResult.cause },\n });\n }\n onError?.(wrappedError as unknown as E, stepName, context);\n throw earlyExit(wrappedError as unknown as E, {\n origin: \"result\",\n resultCause: primaryResult.cause,\n });\n }\n\n // Run fallback\n let fallbackResult: Result<T, E2>;\n try {\n fallbackResult = await options.fallback();\n } catch (thrown) {\n if (isEarlyExitE(thrown)) {\n emitEvent({\n type: \"step_aborted\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs: performance.now() - startTime,\n });\n throw thrown;\n }\n // Fallback threw — map via effectiveCatchUnexpected\n let mappedError: E | UnexpectedError;\n try {\n mappedError = effectiveCatchUnexpected(thrown) as E | UnexpectedError;\n } catch (mapperError) {\n throw createMapperException(mapperError);\n }\n const durationMs = performance.now() - startTime;\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n error: mappedError,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: err(mappedError, { cause: thrown }),\n meta: { origin: \"throw\", thrown },\n });\n }\n onError?.(mappedError as E, stepName, context);\n throw earlyExit(mappedError as E, { origin: \"throw\", thrown });\n }\n\n if (fallbackResult.ok) {\n const durationMs = performance.now() - startTime;\n emitEvent({\n type: \"step_success\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: fallbackResult,\n meta: { origin: \"fallback\" as const, fallbackUsed: true as const, fallbackReason: String(primaryError) },\n });\n }\n return fallbackResult.value;\n }\n\n // Fallback also returned an error\n const durationMs = performance.now() - startTime;\n const wrappedError = wrapForStep(fallbackResult.error, {\n origin: \"result\",\n resultCause: fallbackResult.cause,\n });\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n error: wrappedError,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: fallbackResult,\n meta: { origin: \"result\", resultCause: fallbackResult.cause },\n });\n }\n onError?.(wrappedError as unknown as E, stepName, context);\n throw earlyExit(wrappedError as unknown as E, {\n origin: \"result\",\n resultCause: fallbackResult.cause,\n });\n })();\n };\n\n // step.withResource: Acquire/use/release lifecycle with guaranteed release\n stepFn.withResource = <T, R, AcquireE, UseE>(\n id: string,\n options: {\n acquire: () => AsyncResult<R, AcquireE>;\n use: (resource: R) => AsyncResult<T, UseE>;\n release: (resource: R) => void | Promise<void>;\n }\n ): Promise<T> => {\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(\n '[awaitly] step.withResource() requires an explicit string ID as the first argument. ' +\n 'Example: step.withResource(\"useDb\", { acquire: () => connect(), use: (db) => query(db), release: (db) => db.close() })'\n );\n }\n assertDeclared(id, \"step\");\n\n const stepKey = id;\n const stepName = id;\n const stepId = generateStepId(stepKey);\n const hasEventListeners = onEvent;\n\n return (async () => {\n const startTime = hasEventListeners ? performance.now() : 0;\n\n if (onEvent) {\n emitEvent({\n type: \"step_start\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n });\n }\n\n // Acquire\n let acquireResult: Result<R, AcquireE>;\n try {\n acquireResult = await options.acquire();\n } catch (thrown) {\n if (isEarlyExitE(thrown)) {\n emitEvent({\n type: \"step_aborted\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs: performance.now() - startTime,\n });\n throw thrown;\n }\n let mappedError: E | UnexpectedError;\n try {\n mappedError = effectiveCatchUnexpected(thrown) as E | UnexpectedError;\n } catch (mapperError) {\n throw createMapperException(mapperError);\n }\n const durationMs = performance.now() - startTime;\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n error: mappedError,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: err(mappedError, { cause: thrown }),\n meta: { origin: \"throw\", thrown },\n });\n }\n onError?.(mappedError as E, stepName, context);\n throw earlyExit(mappedError as E, { origin: \"throw\", thrown });\n }\n\n if (!acquireResult.ok) {\n // Acquire failed — no release needed\n const durationMs = performance.now() - startTime;\n const wrappedError = wrapForStep(acquireResult.error, {\n origin: \"result\",\n resultCause: acquireResult.cause,\n });\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n error: wrappedError,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: acquireResult,\n meta: { origin: \"result\", resultCause: acquireResult.cause },\n });\n }\n onError?.(wrappedError as unknown as E, stepName, context);\n throw earlyExit(wrappedError as unknown as E, {\n origin: \"result\",\n resultCause: acquireResult.cause,\n });\n }\n\n const resource = acquireResult.value;\n let useResult: Result<T, UseE> | undefined;\n let useThrown: unknown;\n let useThrewNonResult = false;\n\n // Use\n try {\n useResult = await options.use(resource);\n } catch (thrown) {\n if (isEarlyExitE(thrown)) {\n // Release before propagating\n try {\n await options.release(resource);\n } catch (releaseErr) {\n console.warn(\n `[awaitly] step.withResource(\"${id}\"): release threw after earlyExit:`,\n releaseErr\n );\n }\n throw thrown;\n }\n useThrown = thrown;\n useThrewNonResult = true;\n }\n\n // Release — ALWAYS runs after use (unless acquire failed)\n try {\n await options.release(resource);\n } catch (releaseErr) {\n console.warn(\n `[awaitly] step.withResource(\"${id}\"): release threw:`,\n releaseErr\n );\n }\n\n // Emit events AFTER release completes\n if (useThrewNonResult) {\n let mappedError: E | UnexpectedError;\n try {\n mappedError = effectiveCatchUnexpected(useThrown) as E | UnexpectedError;\n } catch (mapperError) {\n throw createMapperException(mapperError);\n }\n const durationMs = performance.now() - startTime;\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n error: mappedError,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result: err(mappedError, { cause: useThrown }),\n meta: { origin: \"throw\", thrown: useThrown },\n });\n }\n onError?.(mappedError as E, stepName, context);\n throw earlyExit(mappedError as E, { origin: \"throw\", thrown: useThrown });\n }\n\n // useResult is defined if useThrewNonResult is false\n const result = useResult!;\n if (result.ok) {\n const durationMs = performance.now() - startTime;\n emitEvent({\n type: \"step_success\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result,\n });\n }\n return result.value;\n }\n\n // Use returned an error\n const durationMs = performance.now() - startTime;\n const wrappedError = wrapForStep(result.error, {\n origin: \"result\",\n resultCause: result.cause,\n });\n emitEvent({\n type: \"step_error\",\n workflowId,\n stepId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n error: wrappedError,\n });\n if (stepKey) {\n emitEvent({\n type: \"step_complete\",\n workflowId,\n stepKey,\n name: stepName,\n ts: Date.now(),\n durationMs,\n result,\n meta: { origin: \"result\", resultCause: result.cause },\n });\n }\n onError?.(wrappedError as unknown as E, stepName, context);\n throw earlyExit(wrappedError as unknown as E, {\n origin: \"result\",\n resultCause: result.cause,\n });\n })();\n };\n\n const step = stepFn as unknown as RunStep<E | UnexpectedError>;\n const value = await fn({ step });\n\n // Dev-only warning: Detect common mistake of returning ok() or err() from executor\n if (\n process.env.NODE_ENV !== \"production\" &&\n value !== null &&\n typeof value === \"object\" &&\n \"ok\" in value &&\n typeof (value as { ok: unknown }).ok === \"boolean\"\n ) {\n const maybeResult = value as { ok: boolean; value?: unknown; error?: unknown };\n if (\n (maybeResult.ok === true && \"value\" in maybeResult) ||\n (maybeResult.ok === false && \"error\" in maybeResult)\n ) {\n console.warn(\n `awaitly: Workflow executor returned a Result-like object. ` +\n `Return raw values, not ok() or err().\\n\\n` +\n ` Incorrect: return ok({ data });\\n` +\n ` Correct: return { data };\\n\\n` +\n `See: https://jagreehal.github.io/awaitly/guides/troubleshooting/#returning-ok-from-workflow-executor-double-wrapping`\n );\n }\n }\n\n return ok(value);\n } catch (error) {\n // If a catchUnexpected mapper threw, propagate without re-processing\n if (isMapperException(error)) {\n throw error.thrown;\n }\n\n if (isEarlyExitE(error)) {\n // Extract original cause from early exit metadata\n const originalCause = error.meta.origin === \"throw\"\n ? error.meta.thrown\n : error.meta.origin === \"result\"\n ? error.meta.resultCause\n : undefined;\n\n return err(error.error, { cause: originalCause });\n }\n\n const mapped = effectiveCatchUnexpected(error);\n onError?.(mapped as E, \"unexpected\", context);\n return err(mapped, { cause: error });\n }\n}\n\n/**\n * Non-overloaded re-typing of `run()`. Same function at runtime — just\n * exposed with one signature so other awaitly modules (e.g. `awaitly/flow`)\n * can call the engine without dancing through TS overload resolution.\n *\n * End-users should call `run()` instead; the overloads give better inference\n * at call sites.\n *\n * @internal\n */\nexport const runInternal: <T, E, U = UnexpectedError, C = void>(\n fn: (context: { step: RunStep<E> }) => Promise<T> | T,\n options?: {\n catchUnexpected?: (cause: unknown) => U;\n onEvent?: (event: WorkflowEvent<E | U, C>, ctx: C) => void;\n onError?: (error: E | U, stepName?: string, ctx?: C) => void;\n workflowId?: string;\n workflowName?: string;\n context?: C;\n }\n) => Promise<Result<T, E | U>> = runFn as never;\n\n/**\n * Convenience for run() with catchUnexpected: closed union Result<T, E>.\n * You must provide catchUnexpected to map uncaught exceptions to E.\n */\nconst runStrict = <T, E, C = void>(\n fn: (context: { step: RunStep<E> }) => Promise<T> | T,\n options: {\n onError?: (error: E, stepName?: string, ctx?: C) => void;\n /**\n * Listener for workflow events (start, success, error, step events).\n *\n * Note: Context is available both on `event.context` and as the separate `ctx` parameter.\n * The `ctx` parameter is provided for convenience and backward compatibility.\n */\n onEvent?: (event: WorkflowEvent<E | UnexpectedError, C>, ctx: C) => void;\n catchUnexpected: (cause: unknown) => E;\n workflowId?: string;\n context?: C;\n /** @internal External signal for workflow-level cancellation. */\n _workflowSignal?: AbortSignal;\n }\n): AsyncResult<T, E, unknown> => {\n return runFn<T, E, C>(fn, options);\n};\n\n/**\n * The public run(): the engine with `.strict` attached.\n *\n * Assembled with a PURE-annotated Object.assign instead of a top-level\n * `run.strict = ...` mutation — a top-level property assignment is a side\n * effect that pins run (and the whole step engine) into every consumer\n * bundle even when only Result primitives are imported.\n */\nexport const run = /* @__PURE__ */ Object.assign(runFn, { strict: runStrict });\n\n// =============================================================================\n// Unwrap Utilities\n// =============================================================================\n\n/**\n * Error thrown when `unwrap()` is called on an error Result.\n *\n * This error is thrown to prevent silent failures when using `unwrap()`.\n * Prefer using `unwrapOr`, `unwrapOrElse`, or pattern matching with `match` or `isOk`/`isErr`.\n */\nexport class UnwrapError<E = unknown, C = unknown> extends Error {\n constructor(\n public readonly error: E,\n public readonly cause?: C\n ) {\n super(`Unwrap called on an error result: ${String(error)}`);\n this.name = \"UnwrapError\";\n }\n}\n\n/**\n * Unwraps a Result, throwing an error if it's a failure.\n *\n * @remarks When to use: Only at boundaries or tests where a failure should be fatal.\n *\n * ## When to Use\n *\n * Use `unwrap()` when:\n * - You're certain the Result is successful (e.g., after checking with `isOk`)\n * - You're in a context where errors should crash (e.g., tests, initialization)\n * - You need the value immediately and can't handle errors gracefully\n *\n * ## Why Avoid This\n *\n * **Prefer alternatives** in production code:\n * - `unwrapOr(defaultValue)` - Provide a fallback value\n * - `unwrapOrElse(fn)` - Compute fallback from error\n * - `match()` - Handle both cases explicitly\n * - `isOk()` / `isErr()` - Type-safe pattern matching\n *\n * Throwing errors makes error handling harder and can crash your application.\n *\n * @param r - The Result to unwrap\n * @returns The success value if the Result is successful\n * @throws {UnwrapError} If the Result is an error (includes the error and cause)\n *\n * @example\n * ```typescript\n * // Safe usage after checking\n * const result = someOperation();\n * if (isOk(result)) {\n * const value = unwrap(result); // Safe - we know it's ok\n * }\n *\n * // Unsafe usage (not recommended)\n * const value = unwrap(someOperation()); // May throw!\n * ```\n */\nexport const unwrap = <T, E, C>(r: Result<T, E, C>): T => {\n if (r.ok) return r.value;\n throw new UnwrapError<E, C>(r.error, r.cause);\n};\n\n/**\n * Unwraps a Result, returning a default value if it's a failure.\n *\n * @remarks When to use: Provide a safe fallback without branching.\n *\n * ## When to Use\n *\n * Use `unwrapOr()` when:\n * - You have a sensible default value for errors\n * - You want to continue execution even on failure\n * - The default value is cheap to compute (use `unwrapOrElse` if expensive)\n *\n * ## Why Use This\n *\n * - **Safe**: Never throws, always returns a value\n * - **Simple**: One-liner for common error handling\n * - **Type-safe**: TypeScript knows you'll always get a `T`\n *\n * @param r - The Result to unwrap\n * @param defaultValue - The value to return if the Result is an error\n * @returns The success value if successful, otherwise the default value\n *\n * @example\n * ```typescript\n * // Provide default for missing data\n * const user = unwrapOr(fetchUser(id), { id: 'anonymous', name: 'Guest' });\n *\n * // Provide default for numeric operations\n * const count = unwrapOr(parseCount(input), 0);\n *\n * // Provide default for optional features\n * const config = unwrapOr(loadConfig(), getDefaultConfig());\n * ```\n */\nexport const unwrapOr = <T, E, C>(r: Result<T, E, C>, defaultValue: T): T =>\n r.ok ? r.value : defaultValue;\n\n/**\n * Unwraps a Result, computing a default value from the error if it's a failure.\n *\n * @remarks When to use: Compute a fallback from the error (logging, metrics, or derived defaults).\n *\n * ## When to Use\n *\n * Use `unwrapOrElse()` when:\n * - The default value is expensive to compute (lazy evaluation)\n * - You need to log or handle the error before providing a default\n * - The default depends on the error type or cause\n * - You want to transform the error into a success value\n *\n * ## Why Use This Instead of `unwrapOr`\n *\n * - **Lazy**: Default is only computed if needed (better performance)\n * - **Error-aware**: You can inspect the error before providing default\n * - **Flexible**: Default can depend on error type or cause\n *\n * @param r - The Result to unwrap\n * @param fn - Function that receives the error and optional cause, returns the default value\n * @returns The success value if successful, otherwise the result of calling `fn(error, cause)`\n *\n * @example\n * ```typescript\n * // Compute default based on error type\n * const port = unwrapOrElse(parsePort(env.PORT), (error) => {\n * if (error === 'INVALID_FORMAT') return 3000;\n * if (error === 'OUT_OF_RANGE') return 8080;\n * return 4000; // default\n * });\n *\n * // Log error before providing default\n * const data = unwrapOrElse(fetchData(), (error, cause) => {\n * console.error('Failed to fetch:', error, cause);\n * return getCachedData();\n * });\n *\n * // Transform error into success value\n * const result = unwrapOrElse(operation(), (error) => {\n * return { success: false, reason: String(error) };\n * });\n * ```\n */\nexport const unwrapOrElse = <T, E, C>(\n r: Result<T, E, C>,\n fn: (error: E, cause?: C) => T\n): T => (r.ok ? r.value : fn(r.error, r.cause));\n\n/**\n * Alias for `unwrap`. Returns the success value or throws.\n *\n * The Result is already computed; use when you want the value or throw (e.g. at boundaries or in tests).\n *\n * @param r - The Result to unwrap\n * @returns The success value if the Result is successful\n * @throws {UnwrapError} If the Result is an error (includes the error and cause)\n */\nexport const runOrThrow = <T, E, C>(r: Result<T, E, C>): T => unwrap(r);\n\n/**\n * Awaits a Promise of a Result, then returns the success value or rejects.\n *\n * The returned promise **resolves with T** on success and **rejects with UnwrapError** on failure.\n * UnwrapError extends Error and carries the original `error` and `cause` from the Err.\n *\n * @param ar - A Promise or thenable that resolves to a Result\n * @returns A Promise that resolves with the success value or rejects with UnwrapError\n */\nexport const runOrThrowAsync = <T, E, C>(\n ar: PromiseLike<Result<T, E, C>>\n): Promise<T> => Promise.resolve(ar).then(unwrap);\n\n/**\n * Convenience alias for `unwrapOr(r, null)`. Returns the success value or null.\n *\n * @param r - The Result to unwrap\n * @returns The success value if successful, otherwise null\n */\nexport const runOrNull = <T, E, C>(r: Result<T, E, C>): T | null =>\n r.ok ? r.value : null;\n\n/**\n * Convenience alias for `unwrapOr(r, undefined)`. Returns the success value or undefined.\n *\n * @param r - The Result to unwrap\n * @returns The success value if successful, otherwise undefined\n */\nexport const runOrUndefined = <T, E, C>(r: Result<T, E, C>): T | undefined =>\n r.ok ? r.value : undefined;\n\n// =============================================================================\n// Wrapping Functions\n// =============================================================================\n\n/**\n * Wraps a synchronous throwing function in a Result.\n *\n * @remarks When to use: Wrap sync code that might throw so exceptions become Err values.\n *\n * ## When to Use\n *\n * Use `from()` when:\n * - You have a synchronous function that throws exceptions\n * - You want to convert exceptions to typed errors\n * - You're integrating with libraries that throw (e.g., JSON.parse, fs.readFileSync)\n * - You need to handle errors without try/catch blocks\n *\n * ## Why Use This\n *\n * - **Type-safe errors**: Convert thrown exceptions to typed Result errors\n * - **No try/catch**: Cleaner code without nested try/catch blocks\n * - **Composable**: Results can be chained with `andThen`, `map`, etc.\n * - **Explicit errors**: Forces you to handle errors explicitly\n *\n * @param fn - The synchronous function to execute (may throw)\n * @returns A Result with the function's return value or the thrown error\n *\n * @example\n * ```typescript\n * // Wrap JSON.parse\n * const parsed = from(() => JSON.parse('{\"key\": \"value\"}'));\n * // parsed: { ok: true, value: { key: \"value\" } }\n *\n * const error = from(() => JSON.parse('invalid'));\n * // error: { ok: false, error: SyntaxError }\n * ```\n */\nexport function from<T>(fn: () => T): Ok<T> | Err<unknown, unknown>;\n/**\n * Wraps a synchronous throwing function in a Result with custom error mapping.\n *\n * Use this overload when you want to map thrown exceptions to your typed error union.\n *\n * @param fn - The synchronous function to execute (may throw)\n * @param onError - Function to map the thrown exception to a typed error\n * @returns A Result with the function's return value or the mapped error\n *\n * @example\n * ```typescript\n * // Map exceptions to typed errors\n * const parsed = from(\n * () => JSON.parse(input),\n * (cause) => ({ type: 'PARSE_ERROR' as const, cause })\n * );\n * // parsed.error: { type: 'PARSE_ERROR', cause: SyntaxError }\n *\n * // Map to simple error codes\n * const value = from(\n * () => riskyOperation(),\n * () => 'OPERATION_FAILED' as const\n * );\n * ```\n */\nexport function from<T, E>(fn: () => T, onError: (cause: unknown) => E): Ok<T> | Err<E, unknown>;\nexport function from<T, E>(fn: () => T, onError?: (cause: unknown) => E) {\n try {\n return ok(fn());\n } catch (cause) {\n return onError ? err(onError(cause), { cause }) : err(cause);\n }\n}\n\n/**\n * Wraps a Promise in a Result, converting rejections to errors.\n *\n * @remarks When to use: Wrap a Promise and keep the raw rejection as Err; use tryAsync to map errors.\n *\n * ## When to Use\n *\n * Use `fromPromise()` when:\n * - You have an existing Promise that might reject\n * - You want to convert Promise rejections to typed errors\n * - You're working with libraries that return Promises (fetch, database clients)\n * - You need to handle rejections without .catch() chains\n *\n * ## Why Use This\n *\n * - **Type-safe errors**: Convert Promise rejections to typed Result errors\n * - **Composable**: Results can be chained with `andThen`, `map`, etc.\n * - **Explicit handling**: Forces you to handle errors explicitly\n * - **No .catch() chains**: Cleaner than Promise.catch() patterns\n *\n * @param promise - The Promise to await (may reject)\n * @returns A Promise resolving to a Result with the resolved value or rejection reason\n *\n * @example\n * ```typescript\n * // Wrap fetch\n * const result = await fromPromise(\n * fetch('/api').then(r => r.json())\n * );\n * // result.ok: true if fetch succeeded, false if rejected\n * ```\n */\nexport function fromPromise<T>(promise: Promise<T>): Promise<Ok<T> | Err<unknown, unknown>>;\n/**\n * Wraps a Promise in a Result with custom error mapping.\n *\n * Use this overload when you want to map Promise rejections to your typed error union.\n *\n * @param promise - The Promise to await (may reject)\n * @param onError - Function to map the rejection reason to a typed error\n * @returns A Promise resolving to a Result with the resolved value or mapped error\n *\n * @example\n * ```typescript\n * // Map fetch errors to typed errors\n * const result = await fromPromise(\n * fetch('/api').then(r => {\n * if (!r.ok) throw new Error(`HTTP ${r.status}`);\n * return r.json();\n * }),\n * () => 'FETCH_FAILED' as const\n * );\n * // result.error: 'FETCH_FAILED' if fetch failed\n *\n * // Map with error details\n * const data = await fromPromise(\n * db.query(sql),\n * (cause) => ({ type: 'DB_ERROR' as const, message: String(cause) })\n * );\n * ```\n */\nexport function fromPromise<T, E>(\n promise: Promise<T>,\n onError: (cause: unknown) => E\n): Promise<Ok<T> | Err<E, unknown>>;\nexport async function fromPromise<T, E>(\n promise: Promise<T>,\n onError?: (cause: unknown) => E\n): Promise<Ok<T> | Err<E, unknown> | Err<unknown, unknown>> {\n try {\n return ok(await promise);\n } catch (cause) {\n return onError ? err(onError(cause), { cause }) : err(cause);\n }\n}\n\n/**\n * Wraps an async function in a Result, catching both thrown exceptions and Promise rejections.\n *\n * @remarks When to use: Wrap async work and map thrown/rejected values into your typed error union.\n *\n * ## When to Use\n *\n * Use `tryAsync()` when:\n * - You have an async function that might throw or reject\n * - You want to convert both exceptions and rejections to typed errors\n * - You're creating new async functions (use `fromPromise` for existing Promises)\n * - You need to handle errors without try/catch or .catch()\n *\n * ## Why Use This Instead of `fromPromise`\n *\n * - **Function form**: Takes a function, not a Promise (lazy evaluation)\n * - **Catches both**: Handles both thrown exceptions and Promise rejections\n * - **Cleaner syntax**: No need to wrap in Promise manually\n *\n * @param fn - The async function to execute (may throw or reject)\n * @returns A Promise resolving to a Result with the function's return value or error\n *\n * @example\n * ```typescript\n * // Wrap async function\n * const result = await tryAsync(async () => {\n * const data = await fetchData();\n * return processData(data);\n * });\n * ```\n */\nexport function tryAsync<T>(fn: () => Promise<T>): AsyncResult<T, unknown>;\n/**\n * Wraps an async function in a Result with custom error mapping.\n *\n * Use this overload when you want to map errors to your typed error union.\n *\n * @param fn - The async function to execute (may throw or reject)\n * @param onError - Function to map the error (exception or rejection) to a typed error\n * @returns A Promise resolving to a Result with the function's return value or mapped error\n *\n * @example\n * ```typescript\n * // Map errors to typed errors\n * const result = await tryAsync(\n * async () => await fetchData(),\n * () => 'FETCH_ERROR' as const\n * );\n *\n * // Map with error details\n * const data = await tryAsync(\n * async () => await processFile(path),\n * (cause) => ({ type: 'PROCESSING_ERROR' as const, cause })\n * );\n * ```\n */\nexport function tryAsync<T, E>(\n fn: () => Promise<T>,\n onError: (cause: unknown) => E\n): AsyncResult<T, E>;\nexport async function tryAsync<T, E>(\n fn: () => Promise<T>,\n onError?: (cause: unknown) => E\n): AsyncResult<T, E | unknown> {\n try {\n return ok(await fn());\n } catch (cause) {\n return onError ? err(onError(cause), { cause }) : err(cause);\n }\n}\n\n/**\n * Converts a nullable value to a Result.\n *\n * @remarks When to use: Turn null/undefined into a typed error before continuing.\n *\n * ## When to Use\n *\n * Use `fromNullable()` when:\n * - You have a value that might be `null` or `undefined`\n * - You want to treat null/undefined as an error case\n * - You're working with APIs that return nullable values (DOM APIs, optional properties)\n * - You want to avoid null checks scattered throughout your code\n *\n * ## Why Use This\n *\n * - **Type-safe**: Converts nullable types to non-nullable Results\n * - **Explicit errors**: Forces you to handle null/undefined cases\n * - **Composable**: Results can be chained with `andThen`, `map`, etc.\n * - **No null checks**: Eliminates need for `if (value == null)` checks\n *\n * @param value - The value that may be null or undefined\n * @param onNull - Function that returns an error when value is null/undefined\n * @returns A Result with the value if not null/undefined, otherwise the error from `onNull`\n *\n * @example\n * ```typescript\n * // Convert DOM element lookup\n * const element = fromNullable(\n * document.getElementById('app'),\n * () => 'ELEMENT_NOT_FOUND' as const\n * );\n *\n * // Convert optional property\n * const userId = fromNullable(\n * user.id,\n * () => 'USER_ID_MISSING' as const\n * );\n *\n * // Convert database query result\n * const record = fromNullable(\n * await db.find(id),\n * () => ({ type: 'NOT_FOUND' as const, id })\n * );\n * ```\n */\nexport function fromNullable<T, E>(\n value: T | null | undefined,\n onNull: () => E\n): Result<T, E> {\n return value != null ? ok(value) : err(onNull());\n}\n\n// =============================================================================\n// Transformers\n// =============================================================================\n\n/**\n * Transforms the success value of a Result.\n *\n * @remarks When to use: Transform only the Ok value while leaving Err untouched.\n *\n * ## When to Use\n *\n * Use `map()` when:\n * - You need to transform a success value to another type\n * - You want to apply a pure function to the value\n * - You're building a pipeline of transformations\n * - The transformation cannot fail (use `andThen` if it can fail)\n *\n * ## Why Use This\n *\n * - **Functional style**: Composable, chainable transformations\n * - **Error-preserving**: Errors pass through unchanged\n * - **Type-safe**: TypeScript tracks the transformation\n * - **No unwrapping**: Avoids manual `if (r.ok)` checks\n *\n * @param r - The Result to transform\n * @param fn - Pure function that transforms the success value (must not throw)\n * @returns A new Result with the transformed value, or the original error if `r` was an error\n *\n * @example\n * ```typescript\n * // Transform numeric value\n * const doubled = map(ok(21), n => n * 2);\n * // doubled: { ok: true, value: 42 }\n *\n * // Transform object property\n * const name = map(fetchUser(id), user => user.name);\n *\n * // Chain transformations\n * const formatted = map(\n * map(parseNumber(input), n => n * 2),\n * n => `Result: ${n}`\n * );\n * ```\n */\nexport function map<T, U>(r: Ok<T>, fn: (value: T) => U): Ok<U>;\nexport function map<T, U, E, C>(r: Err<E, C>, fn: (value: T) => U): Err<E, C>;\nexport function map<T, U, E, C>(r: Result<T, E, C>, fn: (value: T) => U): Result<U, E, C>;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function map(r: any, fn: any): any {\n return r.ok ? ok(fn(r.value)) : r;\n}\n\n/**\n * Transforms the error value of a Result.\n *\n * @remarks When to use: Retype or normalize errors while leaving Ok values unchanged.\n *\n * ## When to Use\n *\n * Use `mapError()` when:\n * - You need to normalize or transform error types\n * - You want to convert errors to a different error type\n * - You're building error handling pipelines\n * - You need to format error messages or codes\n *\n * ## Why Use This\n *\n * - **Error normalization**: Convert errors to a common format\n * - **Type transformation**: Change error type while preserving value type\n * - **Composable**: Can be chained with other transformers\n * - **Success-preserving**: Success values pass through unchanged\n *\n * @param r - The Result to transform\n * @param fn - Function that transforms the error value (must not throw)\n * @returns A new Result with the original value, or the transformed error if `r` was an error\n *\n * @example\n * ```typescript\n * // Normalize error codes\n * const normalized = mapError(err('not_found'), e => e.toUpperCase());\n * // normalized: { ok: false, error: 'NOT_FOUND' }\n *\n * // Convert error types\n * const typed = mapError(\n * err('404'),\n * code => ({ type: 'HTTP_ERROR' as const, status: parseInt(code) })\n * );\n *\n * // Format error messages\n * const formatted = mapError(\n * err('PARSE_ERROR'),\n * code => `Failed to parse: ${code}`\n * );\n * ```\n */\nexport function mapError<T, E, F, C>(\n r: Result<T, E, C>,\n fn: (error: E) => F\n): Result<T, F, C> {\n return r.ok ? r : err(fn(r.error), { cause: r.cause });\n}\n\n/**\n * Pattern matches on a Result, calling the appropriate handler.\n *\n * @remarks When to use: Handle both Ok and Err in a single expression that returns a value.\n *\n * ## When to Use\n *\n * Use `match()` when:\n * - You need to handle both success and error cases\n * - You want to transform a Result to a different type\n * - You need exhaustive handling (both cases must be handled)\n * - You're building user-facing messages or responses\n *\n * ## Why Use This\n *\n * - **Exhaustive**: Forces you to handle both success and error cases\n * - **Type-safe**: TypeScript ensures both handlers are provided\n * - **Functional**: Pattern matching style, similar to Rust's `match` or Haskell's `case`\n * - **Single expression**: Can be used in expressions, not just statements\n *\n * @param r - The Result to match\n * @param handlers - Object with `ok` and `err` handler functions\n * @param handlers.ok - Function called with the success value\n * @param handlers.err - Function called with the error and optional cause\n * @returns The return value of the appropriate handler (both must return the same type `R`)\n *\n * @example\n * ```typescript\n * // Build user-facing messages\n * const message = match(result, {\n * ok: (user) => `Hello ${user.name}`,\n * err: (error) => `Error: ${error}`,\n * });\n *\n * // Transform to API response\n * const response = match(operation(), {\n * ok: (data) => ({ status: 200, body: data }),\n * err: (error) => ({ status: 400, error: String(error) }),\n * });\n *\n * // Handle with cause\n * const response = match(result, {\n * ok: (value) => ({ status: 'success', data: value }),\n * err: (error, cause) => ({ status: 'error', error, cause }),\n * });\n * ```\n */\nexport function match<T, E, C, R>(handlers: { ok: (value: T) => R; err: (error: E, cause?: C) => R }): (r: Result<T, E, C>) => R;\nexport function match<T, E, C, R>(r: Ok<T>, handlers: { ok: (value: T) => R; err: (error: E, cause?: C) => R }): R;\nexport function match<T, E, C, R>(r: Err<E, C>, handlers: { ok: (value: T) => R; err: (error: E, cause?: C) => R }): R;\nexport function match<T, E, C, R>(r: Result<T, E, C>, handlers: { ok: (value: T) => R; err: (error: E, cause?: C) => R }): R;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function match(r: any, handlers?: any): any {\n if (handlers === undefined) {\n const h = r;\n return (result: Result<unknown, unknown, unknown>) => match(result, h);\n }\n return r.ok ? handlers.ok(r.value) : handlers.err(r.error, r.cause);\n}\n\n/**\n * Chains Results together (flatMap/monadic bind).\n *\n * @remarks When to use: Chain dependent operations that return Result without nested branching.\n *\n * ## When to Use\n *\n * Use `andThen()` when:\n * - You need to chain operations that can fail\n * - The next operation depends on the previous success value\n * - You're building a pipeline of dependent operations\n * - You want to avoid nested `if (r.ok)` checks\n *\n * ## Why Use This Instead of `map`\n *\n * - **Can fail**: The chained function returns a Result (can fail)\n * - **Short-circuits**: If first Result fails, second operation never runs\n * - **Error accumulation**: Errors from both operations are in the union\n * - **Composable**: Can chain multiple operations together\n *\n * ## Common Pattern\n *\n * This is the fundamental building block for Result pipelines:\n * ```typescript\n * andThen(operation1(), value1 =>\n * andThen(operation2(value1), value2 =>\n * ok({ value1, value2 })\n * )\n * )\n * ```\n *\n * @param r - The first Result\n * @param fn - Function that takes the success value and returns a new Result (may fail)\n * @returns The Result from `fn` if `r` was successful, otherwise the original error\n *\n * @example\n * ```typescript\n * // Chain dependent operations\n * const userPosts = andThen(\n * fetchUser('1'),\n * user => fetchPosts(user.id)\n * );\n *\n * // Build complex pipelines\n * const result = andThen(parseInput(input), parsed =>\n * andThen(validate(parsed), validated =>\n * process(validated)\n * )\n * );\n *\n * // Chain with different error types\n * const data = andThen(\n * fetchUser(id), // Returns Result<User, 'FETCH_ERROR'>\n * user => fetchPosts(user.id) // Returns Result<Post[], 'NOT_FOUND'>\n * );\n * // data.error: 'FETCH_ERROR' | 'NOT_FOUND'\n * ```\n */\nexport function andThen<T, U>(r: Ok<T>, fn: (value: T) => Ok<U>): Ok<U>;\nexport function andThen<T, F, C2>(r: Ok<T>, fn: (value: T) => Err<F, C2>): Err<F, C2>;\nexport function andThen<T, U, F, C2>(r: Ok<T>, fn: (value: T) => Result<U, F, C2>): Result<U, F, C2>;\nexport function andThen<T, U, E, F, C1, C2>(r: Err<E, C1>, fn: (value: T) => Result<U, F, C2>): Err<E, C1>;\nexport function andThen<T, U, E, F, C1, C2>(r: Result<T, E, C1>, fn: (value: T) => Result<U, F, C2>): Result<U, E | F, C1 | C2>;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function andThen(r: any, fn: any): any {\n return r.ok ? fn(r.value) : r;\n}\n\n/**\n * Executes a side effect on a successful Result without changing it.\n *\n * @remarks When to use: Add side effects (logging, metrics) on Ok without changing the Result.\n *\n * ## When to Use\n *\n * Use `tap()` when:\n * - You need to log, debug, or observe success values\n * - You want to perform side effects in a pipeline\n * - You need to mutate external state based on success\n * - You're debugging and want to inspect values without breaking the chain\n *\n * ## Why Use This\n *\n * - **Non-breaking**: Doesn't change the Result, just performs side effect\n * - **Composable**: Can be inserted anywhere in a pipeline\n * - **Type-preserving**: Returns the same Result type\n * - **Lazy**: Side effect only runs if Result is successful\n *\n * @param r - The Result to tap\n * @param fn - Side effect function called with the success value (return value ignored)\n * @returns The original Result unchanged (for chaining)\n *\n * @example\n * ```typescript\n * // Log success values\n * const logged = tap(result, user => console.log('Got user:', user.name));\n * // logged === result, but console.log was called\n *\n * // Debug in pipeline\n * const debugged = pipe(\n * fetchUser(id),\n * r => tap(r, user => console.log('Fetched:', user)),\n * r => map(r, user => user.name)\n * );\n *\n * // Mutate external state\n * const tracked = tap(result, data => {\n * analytics.track('operation_success', data);\n * });\n * ```\n */\nexport function tap<T, E, C>(\n r: Result<T, E, C>,\n fn: (value: T) => void\n): Result<T, E, C> {\n if (r.ok) fn(r.value);\n return r;\n}\n\n/**\n * Executes a side effect on an error Result without changing it.\n *\n * @remarks When to use: Add side effects (logging, metrics) on Err without changing the Result.\n *\n * ## When to Use\n *\n * Use `tapError()` when:\n * - You need to log, debug, or observe error values\n * - You want to perform side effects on errors in a pipeline\n * - You need to report errors to external systems (logging, monitoring)\n * - You're debugging and want to inspect errors without breaking the chain\n *\n * ## Why Use This\n *\n * - **Non-breaking**: Doesn't change the Result, just performs side effect\n * - **Composable**: Can be inserted anywhere in a pipeline\n * - **Type-preserving**: Returns the same Result type\n * - **Lazy**: Side effect only runs if Result is an error\n *\n * @param r - The Result to tap\n * @param fn - Side effect function called with the error and optional cause (return value ignored)\n * @returns The original Result unchanged (for chaining)\n *\n * @example\n * ```typescript\n * // Log errors\n * const logged = tapError(result, (error, cause) => {\n * console.error('Error:', error, cause);\n * });\n *\n * // Report to error tracking\n * const tracked = tapError(result, (error, cause) => {\n * errorTracker.report(error, cause);\n * });\n *\n * // Debug in pipeline\n * const debugged = pipe(\n * operation(),\n * r => tapError(r, (err, cause) => console.error('Failed:', err)),\n * r => mapError(r, err => 'FORMATTED_ERROR')\n * );\n * ```\n */\nexport function tapError<T, E, C>(\n r: Result<T, E, C>,\n fn: (error: E, cause?: C) => void\n): Result<T, E, C> {\n if (!r.ok) fn(r.error, r.cause);\n return r;\n}\n\n/**\n * Transforms the success value of a Result, catching any errors thrown by the transform.\n *\n * @remarks When to use: Transform Ok values with a function that might throw and capture the failure.\n *\n * ## When to Use\n *\n * Use `mapTry()` when:\n * - Your transform function might throw exceptions\n * - You want to convert transform errors to typed errors\n * - You're working with libraries that throw (e.g., JSON.parse, Date parsing)\n * - You need to handle both Result errors and transform exceptions\n *\n * ## Why Use This Instead of `map`\n *\n * - **Exception-safe**: Catches exceptions from the transform function\n * - **Error mapping**: Converts thrown exceptions to typed errors\n * - **Dual error handling**: Handles both Result errors and transform exceptions\n *\n * @param result - The Result to transform\n * @param transform - Function to transform the success value (may throw exceptions)\n * @param onError - Function to map thrown exceptions to a typed error\n * @returns A Result with:\n * - Transformed value if both Result and transform succeed\n * - Original error if Result was an error\n * - Transform error if transform threw an exception\n *\n * @example\n * ```typescript\n * // Safe JSON parsing\n * const parsed = mapTry(\n * ok('{\"key\": \"value\"}'),\n * JSON.parse,\n * () => 'PARSE_ERROR' as const\n * );\n *\n * // Safe date parsing\n * const date = mapTry(\n * ok('2024-01-01'),\n * str => new Date(str),\n * () => 'INVALID_DATE' as const\n * );\n *\n * // Transform with error details\n * const processed = mapTry(\n * result,\n * value => riskyTransform(value),\n * (cause) => ({ type: 'TRANSFORM_ERROR' as const, cause })\n * );\n * ```\n */\nexport function mapTry<T, U, E, F, C>(\n result: Result<T, E, C>,\n transform: (value: T) => U,\n onError: (cause: unknown) => F\n): Result<U, E | F, C | unknown> {\n if (!result.ok) return result;\n try {\n return ok(transform(result.value));\n } catch (error) {\n return err(onError(error), { cause: error });\n }\n}\n\n/**\n * Transforms the error value of a Result, catching any errors thrown by the transform.\n *\n * @remarks When to use: Transform errors when the mapping might throw and you want that captured.\n *\n * ## When to Use\n *\n * Use `mapErrorTry()` when:\n * - Your error transform function might throw exceptions\n * - You're doing complex error transformations (e.g., string formatting, object construction)\n * - You want to handle both Result errors and transform exceptions\n * - You need to safely normalize error types\n *\n * ## Why Use This Instead of `mapError`\n *\n * - **Exception-safe**: Catches exceptions from the error transform function\n * - **Error mapping**: Converts thrown exceptions to typed errors\n * - **Dual error handling**: Handles both Result errors and transform exceptions\n *\n * @param result - The Result to transform\n * @param transform - Function to transform the error value (may throw exceptions)\n * @param onError - Function to map thrown exceptions to a typed error\n * @returns A Result with:\n * - Original value if Result was successful\n * - Transformed error if both Result was error and transform succeeded\n * - Transform error if transform threw an exception\n *\n * @example\n * ```typescript\n * // Safe error formatting\n * const formatted = mapErrorTry(\n * err('not_found'),\n * e => e.toUpperCase(), // Might throw if e is not a string\n * () => 'FORMAT_ERROR' as const\n * );\n *\n * // Complex error transformation\n * const normalized = mapErrorTry(\n * result,\n * error => ({ type: 'NORMALIZED', message: String(error) }),\n * () => 'TRANSFORM_ERROR' as const\n * );\n * ```\n */\nexport function mapErrorTry<T, E, F, G, C>(\n result: Result<T, E, C>,\n transform: (error: E) => F,\n onError: (cause: unknown) => G\n): Result<T, F | G, C | unknown> {\n if (result.ok) return result;\n try {\n return err(transform(result.error), { cause: result.cause });\n } catch (error) {\n return err(onError(error), { cause: error });\n }\n}\n\n/**\n * Transforms both the success value and error value of a Result simultaneously.\n *\n * ## When to Use\n *\n * Use `bimap()` when:\n * - You need to transform both success and error in one operation\n * - You're normalizing Results to a common format\n * - You want symmetric transformation of both cases\n * - You're building adapters between different Result types\n *\n * ## Why Use This Instead of `map` + `mapError`\n *\n * - **Single operation**: Transforms both cases in one call\n * - **Clearer intent**: Shows you're handling both cases symmetrically\n * - **Less code**: Avoids chaining map and mapError\n *\n * @param r - The Result to transform\n * @param onOk - Function that transforms the success value\n * @param onErr - Function that transforms the error value\n * @returns A new Result with transformed value or transformed error\n *\n * @example\n * ```typescript\n * // Normalize to API response format\n * const response = bimap(\n * fetchUser(id),\n * user => ({ status: 'success', data: user }),\n * error => ({ status: 'error', code: error })\n * );\n *\n * // Transform types\n * const stringified = bimap(\n * parseNumber(input),\n * n => `Value: ${n}`,\n * e => `Error: ${e}`\n * );\n *\n * // Adapt between error types\n * const adapted = bimap(\n * externalResult,\n * value => internalValue(value),\n * error => internalError(error)\n * );\n * ```\n */\nexport function bimap<T, U, E, F, C>(\n r: Result<T, E, C>,\n onOk: (value: T) => U,\n onErr: (error: E) => F\n): Result<U, F, C> {\n return r.ok ? ok(onOk(r.value)) : err(onErr(r.error), { cause: r.cause });\n}\n\n/**\n * Recovers from an error by returning a new Result.\n * Similar to neverthrow's `.orElse()`.\n *\n * @remarks When to use: Recover from Err by returning a fallback Result or retyping the error.\n *\n * ## When to Use\n *\n * Use `orElse()` when:\n * - You want to recover from errors with fallback operations\n * - The recovery might also fail (returns a Result)\n * - You need to chain fallback strategies\n * - You're implementing retry or fallback patterns\n *\n * ## Why Use This\n *\n * - **Fallback chains**: Try alternative operations on failure\n * - **Error recovery**: Convert errors to success with fallback values\n * - **Composable**: Can chain multiple orElse calls for cascading fallbacks\n * - **Type-safe**: TypeScript tracks the error union through recovery\n *\n * @param r - The Result to potentially recover from\n * @param fn - Function that takes the error and returns a new Result (may succeed or fail)\n * @returns The original Result if successful, or the result of the recovery function\n *\n * @example\n * ```typescript\n * // Fallback to default user\n * const user = orElse(\n * fetchUser(id),\n * error => error === 'NOT_FOUND' ? ok(defaultUser) : err(error)\n * );\n *\n * // Try cache, then database, then fail\n * const data = orElse(\n * orElse(\n * fetchFromCache(key),\n * () => fetchFromDatabase(key)\n * ),\n * () => err('DATA_UNAVAILABLE' as const)\n * );\n *\n * // Convert specific errors to success\n * const result = orElse(\n * riskyOperation(),\n * error => error.code === 'RETRY' ? ok(defaultValue) : err(error)\n * );\n * ```\n */\nexport function orElse<T, E, E2, C, C2>(\n r: Result<T, E, C>,\n fn: (error: E, cause?: C) => Result<T, E2, C2>\n): Result<T, E2, C2> {\n return r.ok ? r : fn(r.error, r.cause);\n}\n\n/**\n * Async version of orElse for recovering from errors with async operations.\n *\n * @param r - The Result or AsyncResult to potentially recover from\n * @param fn - Async function that takes the error and returns a new Result\n * @returns Promise of the original Result if successful, or the result of the recovery function\n *\n * @example\n * ```typescript\n * // Try primary API, fall back to secondary\n * const data = await orElseAsync(\n * await fetchFromPrimaryApi(),\n * async (error) => {\n * if (error === 'UNAVAILABLE') {\n * return await fetchFromSecondaryApi();\n * }\n * return err(error);\n * }\n * );\n * ```\n */\nexport async function orElseAsync<T, E, E2, C, C2>(\n r: Result<T, E, C> | Promise<Result<T, E, C>>,\n fn: (error: E, cause?: C) => Result<T, E2, C2> | Promise<Result<T, E2, C2>>\n): Promise<Result<T, E2, C2>> {\n const resolved = await r;\n return resolved.ok ? resolved : fn(resolved.error, resolved.cause);\n}\n\n/**\n * Recovers from an error by returning a plain value (not a Result).\n * Useful when you want to provide a default value on error.\n *\n * ## When to Use\n *\n * Use `recover()` when:\n * - You want to provide a fallback value on error\n * - Recovery cannot fail (unlike orElse which returns a Result)\n * - You're implementing default value patterns\n * - You want to guarantee a successful Result\n *\n * ## Why Use This Instead of `orElse`\n *\n * - **Simpler**: Recovery function returns plain value, not Result\n * - **Guaranteed success**: Always returns ok() after recovery\n * - **Clearer intent**: Shows recovery cannot fail\n *\n * @param r - The Result to potentially recover from\n * @param fn - Function that takes the error and returns a recovery value\n * @returns The original Result if successful, or ok(recoveryValue) if error\n *\n * @example\n * ```typescript\n * // Provide default user on NOT_FOUND\n * const user = recover(\n * fetchUser(id),\n * error => error === 'NOT_FOUND' ? defaultUser : guestUser\n * );\n *\n * // Convert all errors to default\n * const config = recover(\n * loadConfig(),\n * () => defaultConfig\n * );\n *\n * // Recover with error-based defaults\n * const value = recover(\n * parseNumber(input),\n * error => error === 'EMPTY' ? 0 : -1\n * );\n * ```\n */\nexport function recover<T, E, C>(\n r: Result<T, E, C>,\n fn: (error: E, cause?: C) => T\n): Ok<T> {\n return r.ok ? ok(r.value) : ok(fn(r.error, r.cause));\n}\n\n/**\n * Async version of recover for recovering with async operations.\n *\n * @param r - The Result or AsyncResult to potentially recover from\n * @param fn - Async function that takes the error and returns a recovery value\n * @returns Promise of ok(value) - either original or recovered\n *\n * @example\n * ```typescript\n * // Recover by fetching default from API\n * const user = await recoverAsync(\n * await fetchUser(id),\n * async (error) => await fetchDefaultUser()\n * );\n * ```\n */\nexport async function recoverAsync<T, E, C>(\n r: Result<T, E, C> | Promise<Result<T, E, C>>,\n fn: (error: E, cause?: C) => T | Promise<T>\n): Promise<Ok<T>> {\n const resolved = await r;\n if (resolved.ok) return ok(resolved.value);\n return ok(await fn(resolved.error, resolved.cause));\n}\n\n// =============================================================================\n// Result Hydration (Serialization)\n// =============================================================================\n\n/**\n * Validates and type-narrows a value to a Result.\n *\n * Since this library uses plain objects for Results, serialization is trivial -\n * the serialized form IS the Result. This function validates the structure and\n * provides type-safe narrowing.\n *\n * ## When to Use\n *\n * Use `hydrate()` when:\n * - Receiving Results over RPC/network\n * - Deserializing Results from storage\n * - Validating untrusted data as Results\n *\n * @param value - The unknown value to validate as a Result\n * @returns The value as a typed Result, or null if invalid\n *\n * @example\n * ```typescript\n * // Deserialize from JSON\n * const parsed = JSON.parse(jsonString);\n * const result = hydrate<User, ApiError>(parsed);\n * if (result) {\n * // result is Result<User, ApiError>\n * }\n *\n * // Validate RPC response\n * const rpcResponse = await fetchFromService();\n * const result = hydrate<Data, ServiceError>(rpcResponse);\n * ```\n */\nexport function hydrate<T, E, C = unknown>(value: unknown): Result<T, E, C> | null {\n if (\n value !== null &&\n typeof value === \"object\" &&\n \"ok\" in value &&\n typeof value.ok === \"boolean\"\n ) {\n if (value.ok === true && \"value\" in value) {\n return value as Result<T, E, C>;\n }\n if (value.ok === false && \"error\" in value) {\n return value as Result<T, E, C>;\n }\n }\n return null;\n}\n\n/**\n * Type guard to check if a value is a valid serialized Result.\n *\n * @param value - The value to check\n * @returns True if the value is a valid Result structure\n *\n * @example\n * ```typescript\n * if (isSerializedResult(data)) {\n * // data is Result<unknown, unknown, unknown>\n * if (data.ok) {\n * console.log(data.value);\n * }\n * }\n * ```\n */\nexport function isSerializedResult(\n value: unknown\n): value is Result<unknown, unknown, unknown> {\n return hydrate(value) !== null;\n}\n\n// =============================================================================\n// Batch Operations\n// =============================================================================\n\ntype AllValues<T extends readonly Result<unknown, unknown, unknown>[]> = {\n [K in keyof T]: T[K] extends Ok<infer V>\n ? V\n : T[K] extends Err<unknown, unknown>\n ? never\n : T[K] extends Result<infer V, unknown, unknown>\n ? V\n : never;\n};\ntype AllErrors<T extends readonly Result<unknown, unknown, unknown>[]> = {\n [K in keyof T]: T[K] extends Ok<unknown>\n ? never\n : T[K] extends Err<infer E, unknown>\n ? E\n : T[K] extends Result<unknown, infer E, unknown>\n ? E\n : never;\n}[number];\ntype AllCauses<T extends readonly Result<unknown, unknown, unknown>[]> = {\n [K in keyof T]: T[K] extends Ok<unknown>\n ? never\n : T[K] extends Err<unknown, infer C>\n ? C\n : T[K] extends Result<unknown, unknown, infer C>\n ? C\n : never;\n}[number];\n\n// Conditional type: returns Ok<...> when there are no errors, Result<...> otherwise\n// Note: We only check AllErrors, not AllCauses - causes only matter when there are errors\ntype AllResult<T extends readonly Result<unknown, unknown, unknown>[]> =\n [AllErrors<T>] extends [never]\n ? Ok<AllValues<T>>\n : Result<AllValues<T>, AllErrors<T>, AllCauses<T>>;\n\n/**\n * Combines multiple Results into one, requiring all to succeed.\n *\n * ## When to Use\n *\n * Use `all()` when:\n * - You have multiple independent operations that all must succeed\n * - You want to short-circuit on the first error (fail-fast)\n * - You need all values together (e.g., combining API responses)\n * - Performance matters (stops on first error, doesn't wait for all)\n *\n * ## Why Use This\n *\n * - **Fail-fast**: Stops immediately on first error (better performance)\n * - **Type-safe**: TypeScript infers the array type from input\n * - **Short-circuit**: Doesn't evaluate remaining Results after error\n * - **Composable**: Can be chained with other operations\n *\n * ## Important\n *\n * - **Short-circuits**: Returns first error immediately, doesn't wait for all Results\n * - **All must succeed**: If any Result fails, the entire operation fails\n * - **Use `allSettled`**: If you need to collect all errors (e.g., form validation)\n *\n * @param results - Array of Results to combine (all must succeed)\n * @returns A Result with an array of all success values, or the first error encountered\n *\n * @example\n * ```typescript\n * // Combine multiple successful Results\n * const combined = all([ok(1), ok(2), ok(3)]);\n * // combined: { ok: true, value: [1, 2, 3] }\n *\n * // Short-circuits on first error\n * const error = all([ok(1), err('ERROR'), ok(3)]);\n * // error: { ok: false, error: 'ERROR' }\n * // Note: ok(3) is never evaluated\n *\n * // Combine API responses\n * const data = all([\n * fetchUser(id),\n * fetchPosts(id),\n * fetchComments(id)\n * ]);\n * // data.value: [user, posts, comments] if all succeed\n * ```\n */\nexport function all<const T extends readonly Result<unknown, unknown, unknown>[]>(\n results: T\n): AllResult<T> {\n const values: unknown[] = [];\n for (const result of results) {\n if (!result.ok) {\n return result as unknown as AllResult<T>;\n }\n values.push(result.value);\n }\n return ok(values) as AllResult<T>;\n}\n\n/**\n * Combines multiple Results or Promises of Results into one (async version of `all`).\n *\n * ## When to Use\n *\n * Use `allAsync()` when:\n * - You have multiple async operations that all must succeed\n * - You want to run operations in parallel (better performance)\n * - You want to short-circuit on the first error (fail-fast)\n * - You need all values together from parallel operations\n *\n * ## Why Use This Instead of `all`\n *\n * - **Parallel execution**: All Promises start immediately (faster)\n * - **Async support**: Works with Promises and AsyncResults\n * - **Promise rejection handling**: Converts Promise rejections to `PromiseRejectedError`\n *\n * ## Important\n *\n * - **Short-circuits**: Returns first error immediately, cancels remaining operations\n * - **Parallel**: All operations start simultaneously (unlike sequential `andThen`)\n * - **Use `allSettledAsync`**: If you need to collect all errors\n *\n * @param results - Array of Results or Promises of Results to combine (all must succeed)\n * @returns A Promise resolving to a Result with an array of all success values, or the first error\n *\n * @example\n * ```typescript\n * // Parallel API calls\n * const combined = await allAsync([\n * fetchUser('1'),\n * fetchPosts('1'),\n * fetchComments('1')\n * ]);\n * // All three calls start simultaneously\n * // combined: { ok: true, value: [user, posts, comments] } if all succeed\n *\n * // Mix Results and Promises\n * const data = await allAsync([\n * ok(cachedUser), // Already resolved\n * fetchPosts(userId), // Promise\n * ]);\n * ```\n */\nexport async function allAsync<\n const T extends readonly (Result<unknown, unknown, unknown> | Promise<Result<unknown, unknown, unknown>>)[]\n>(\n results: T\n): Promise<\n Result<\n { [K in keyof T]: T[K] extends Result<infer V, unknown, unknown> | Promise<Result<infer V, unknown, unknown>> ? V : never },\n { [K in keyof T]: T[K] extends Result<unknown, infer E, unknown> | Promise<Result<unknown, infer E, unknown>> ? E : never }[number] | PromiseRejectedError,\n { [K in keyof T]: T[K] extends Result<unknown, unknown, infer C> | Promise<Result<unknown, unknown, infer C>> ? C : never }[number] | PromiseRejectionCause\n >\n> {\n type Values = { [K in keyof T]: T[K] extends Result<infer V, unknown, unknown> | Promise<Result<infer V, unknown, unknown>> ? V : never };\n type Errors = { [K in keyof T]: T[K] extends Result<unknown, infer E, unknown> | Promise<Result<unknown, infer E, unknown>> ? E : never }[number] | PromiseRejectedError;\n type Causes = { [K in keyof T]: T[K] extends Result<unknown, unknown, infer C> | Promise<Result<unknown, unknown, infer C>> ? C : never }[number] | PromiseRejectionCause;\n\n if (results.length === 0) {\n return ok([]) as Result<Values, Errors, Causes>;\n }\n\n return new Promise((resolve) => {\n let settled = false;\n let pendingCount = results.length;\n const values: unknown[] = new Array(results.length);\n\n for (let i = 0; i < results.length; i++) {\n const index = i;\n Promise.resolve(results[index])\n .catch((reason) => err(\n { type: \"PROMISE_REJECTED\" as const, cause: reason },\n { cause: { type: \"PROMISE_REJECTION\" as const, reason } as PromiseRejectionCause }\n ))\n .then((result) => {\n if (settled) return;\n\n if (!result.ok) {\n settled = true;\n resolve(result as Result<Values, Errors, Causes>);\n return;\n }\n\n values[index] = result.value;\n pendingCount--;\n\n if (pendingCount === 0) {\n resolve(ok(values) as Result<Values, Errors, Causes>);\n }\n });\n }\n });\n}\n\nexport type SettledError<E, C = unknown> = { error: E; cause?: C };\n\n// Conditional type: returns Ok<...> when there are no errors, Result<...> otherwise\ntype AllSettledResult<T extends readonly Result<unknown, unknown, unknown>[]> =\n [AllErrors<T>] extends [never]\n ? Ok<AllValues<T>>\n : Result<AllValues<T>, SettledError<AllErrors<T>, AllCauses<T>>[]>;\n\n/**\n * Combines multiple Results, collecting all errors instead of short-circuiting.\n *\n * ## When to Use\n *\n * Use `allSettled()` when:\n * - You need to see ALL errors, not just the first one\n * - You're doing form validation (show all field errors)\n * - You want to collect partial results (some succeed, some fail)\n * - You need to process all Results regardless of failures\n *\n * ## Why Use This Instead of `all`\n *\n * - **Collects all errors**: Returns array of all errors, not just first\n * - **No short-circuit**: Evaluates all Results even if some fail\n * - **Partial success**: Can see which operations succeeded and which failed\n * - **Better UX**: Show users all validation errors at once\n *\n * ## Important\n *\n * - **No short-circuit**: All Results are evaluated (slower if many fail early)\n * - **Error array**: Returns array of `{ error, cause }` objects, not single error\n * - **Use `all`**: If you want fail-fast behavior (better performance)\n *\n * @param results - Array of Results to combine (all are evaluated)\n * @returns A Result with:\n * - Array of all success values if all succeed\n * - Array of `{ error, cause }` objects if any fail\n *\n * @example\n * ```typescript\n * // Form validation - show all errors\n * const validated = allSettled([\n * validateEmail(email),\n * validatePassword(password),\n * validateAge(age),\n * ]);\n * // If email and password fail:\n * // { ok: false, error: [\n * // { error: 'INVALID_EMAIL' },\n * // { error: 'WEAK_PASSWORD' }\n * // ]}\n *\n * // Collect partial results\n * const results = allSettled([\n * fetchUser('1'), // succeeds\n * fetchUser('2'), // fails\n * fetchUser('3'), // succeeds\n * ]);\n * // Can see which succeeded and which failed\n * ```\n */\nexport function allSettled<const T extends readonly Result<unknown, unknown, unknown>[]>(\n results: T\n): AllSettledResult<T> {\n const values: unknown[] = [];\n const errors: SettledError<unknown>[] = [];\n\n for (const result of results) {\n if (result.ok) {\n values.push(result.value);\n } else {\n errors.push({ error: result.error, cause: result.cause });\n }\n }\n\n if (errors.length > 0) {\n return err(errors) as unknown as AllSettledResult<T>;\n }\n\n return ok(values) as unknown as AllSettledResult<T>;\n}\n\n/**\n * Splits an array of Results into separate arrays of success values and errors.\n *\n * ## When to Use\n *\n * Use `partition()` when:\n * - You have an array of Results and need to separate successes from failures\n * - You want to process successes and errors separately\n * - You're collecting results from multiple operations (some may fail)\n * - You need to handle partial success scenarios\n *\n * ## Why Use This\n *\n * - **Simple separation**: One call splits successes and errors\n * - **Type-safe**: TypeScript knows `values` is `T[]` and `errors` is `E[]`\n * - **No unwrapping**: Doesn't require manual `if (r.ok)` checks\n * - **Preserves order**: Maintains original array order in both arrays\n *\n * ## Common Pattern\n *\n * Often used after `Promise.all()` with Results:\n * ```typescript\n * const results = await Promise.all(ids.map(id => fetchUser(id)));\n * const { values: users, errors } = partition(results);\n * // Process successful users, handle errors separately\n * ```\n *\n * @param results - Array of Results to partition\n * @returns An object with:\n * - `values`: Array of all success values (type `T[]`)\n * - `errors`: Array of all error values (type `E[]`)\n *\n * @example\n * ```typescript\n * // Split successes and errors\n * const results = [ok(1), err('ERROR_1'), ok(3), err('ERROR_2')];\n * const { values, errors } = partition(results);\n * // values: [1, 3]\n * // errors: ['ERROR_1', 'ERROR_2']\n *\n * // Process batch operations\n * const userResults = await Promise.all(userIds.map(id => fetchUser(id)));\n * const { values: users, errors: fetchErrors } = partition(userResults);\n *\n * // Process successful users\n * users.forEach(user => processUser(user));\n *\n * // Handle errors\n * fetchErrors.forEach(error => logError(error));\n * ```\n */\nexport function partition<T, E, C>(\n results: readonly Result<T, E, C>[]\n): { values: T[]; errors: E[] } {\n const values: T[] = [];\n const errors: E[] = [];\n\n for (const result of results) {\n if (result.ok) {\n values.push(result.value);\n } else {\n errors.push(result.error);\n }\n }\n\n return { values, errors };\n}\n\ntype AnyValue<T extends readonly Result<unknown, unknown, unknown>[]> =\n T[number] extends Result<infer U, unknown, unknown> ? U : never;\ntype AnyErrors<T extends readonly Result<unknown, unknown, unknown>[]> = {\n -readonly [K in keyof T]: T[K] extends Result<unknown, infer E, unknown> ? E : never;\n}[number];\ntype AnyCauses<T extends readonly Result<unknown, unknown, unknown>[]> = {\n -readonly [K in keyof T]: T[K] extends Result<unknown, unknown, infer C> ? C : never;\n}[number];\n\n/**\n * Returns the first successful Result from an array (succeeds fast).\n *\n * ## When to Use\n *\n * Use `any()` when:\n * - You have multiple fallback options and need the first that succeeds\n * - You're trying multiple strategies (e.g., cache → DB → API)\n * - You want fail-fast success (stops on first success)\n * - You have redundant data sources and any one will do\n *\n * ## Why Use This\n *\n * - **Succeeds fast**: Returns immediately on first success (better performance)\n * - **Fallback pattern**: Perfect for trying multiple options\n * - **Short-circuits**: Stops evaluating after first success\n * - **Type-safe**: TypeScript infers the success type\n *\n * ## Important\n *\n * - **First success wins**: Returns first successful Result, ignores rest\n * - **All errors**: If all fail, returns first error (not all errors)\n * - **Empty array**: Returns `EmptyInputError` if array is empty\n * - **Use `all`**: If you need ALL to succeed\n *\n * @param results - Array of Results to check (evaluated in order)\n * @returns The first successful Result, or first error if all fail, or `EmptyInputError` if empty\n *\n * @example\n * ```typescript\n * // Try multiple fallback strategies\n * const data = any([\n * fetchFromCache(id),\n * fetchFromDB(id),\n * fetchFromAPI(id)\n * ]);\n * // Returns first that succeeds\n *\n * // Try multiple formats\n * const parsed = any([\n * parseJSON(input),\n * parseXML(input),\n * parseYAML(input)\n * ]);\n *\n * // All errors case\n * const allErrors = any([err('A'), err('B'), err('C')]);\n * // allErrors: { ok: false, error: 'A' } (first error)\n * ```\n */\nexport function any<const T extends readonly Result<unknown, unknown, unknown>[]>(\n results: T\n): Result<AnyValue<T>, AnyErrors<T> | EmptyInputError, AnyCauses<T>> {\n type ReturnErr = Result<never, AnyErrors<T> | EmptyInputError, AnyCauses<T>>;\n type ReturnOk = Result<AnyValue<T>, never, AnyCauses<T>>;\n\n if (results.length === 0) {\n return err({\n type: \"EMPTY_INPUT\",\n message: \"any() requires at least one Result\",\n }) as ReturnErr;\n }\n let firstError: Result<never, unknown, unknown> | null = null;\n for (const result of results) {\n if (result.ok) return result as ReturnOk;\n if (!firstError) firstError = result;\n }\n return firstError as ReturnErr;\n}\n\ntype AnyAsyncValue<T extends readonly MaybeAsyncResult<unknown, unknown, unknown>[]> =\n Awaited<T[number]> extends Result<infer U, unknown, unknown> ? U : never;\ntype AnyAsyncErrors<T extends readonly MaybeAsyncResult<unknown, unknown, unknown>[]> = {\n -readonly [K in keyof T]: Awaited<T[K]> extends Result<unknown, infer E, unknown>\n ? E\n : never;\n}[number];\ntype AnyAsyncCauses<T extends readonly MaybeAsyncResult<unknown, unknown, unknown>[]> = {\n -readonly [K in keyof T]: Awaited<T[K]> extends Result<unknown, unknown, infer C>\n ? C\n : never;\n}[number];\n\n/**\n * Returns the first successful Result from an array of Results or Promises (async version of `any`).\n *\n * ## When to Use\n *\n * Use `anyAsync()` when:\n * - You have multiple async fallback options and need the first that succeeds\n * - You're trying multiple async strategies in parallel (cache → DB → API)\n * - You want fail-fast success from parallel operations\n * - You have redundant async data sources and any one will do\n *\n * ## Why Use This Instead of `any`\n *\n * - **Parallel execution**: All Promises start immediately (faster)\n * - **Async support**: Works with Promises and AsyncResults\n * - **Promise rejection handling**: Converts Promise rejections to `PromiseRejectedError`\n *\n * ## Important\n *\n * - **First success wins**: Returns first successful Result (from any Promise)\n * - **Parallel**: All operations run simultaneously\n * - **All errors**: If all fail, returns first error encountered\n *\n * @param results - Array of Results or Promises of Results to check (all start in parallel)\n * @returns A Promise resolving to the first successful Result, or first error if all fail\n *\n * @example\n * ```typescript\n * // Try multiple async fallbacks in parallel\n * const data = await anyAsync([\n * fetchFromCache(id), // Fastest wins\n * fetchFromDB(id),\n * fetchFromAPI(id)\n * ]);\n *\n * // Try multiple API endpoints\n * const response = await anyAsync([\n * fetch('/api/v1/data'),\n * fetch('/api/v2/data'),\n * fetch('/backup-api/data')\n * ]);\n * ```\n */\nexport async function anyAsync<\n const T extends readonly MaybeAsyncResult<unknown, unknown, unknown>[],\n>(\n results: T\n): Promise<\n Result<AnyAsyncValue<T>, AnyAsyncErrors<T> | EmptyInputError | PromiseRejectedError, AnyAsyncCauses<T> | PromiseRejectionCause>\n> {\n type ReturnErr = Result<\n never,\n AnyAsyncErrors<T> | EmptyInputError | PromiseRejectedError,\n AnyAsyncCauses<T> | PromiseRejectionCause\n >;\n type ReturnOk = Result<AnyAsyncValue<T>, never, AnyAsyncCauses<T>>;\n\n if (results.length === 0) {\n return err({\n type: \"EMPTY_INPUT\",\n message: \"anyAsync() requires at least one Result\",\n }) as ReturnErr;\n }\n\n return new Promise((resolve) => {\n let settled = false;\n let pendingCount = results.length;\n let firstError: Result<never, unknown, unknown> | null = null;\n\n for (const item of results) {\n Promise.resolve(item)\n .catch((reason) =>\n err(\n { type: \"PROMISE_REJECTED\" as const, cause: reason },\n { cause: { type: \"PROMISE_REJECTION\" as const, reason } as PromiseRejectionCause }\n )\n )\n .then((result) => {\n if (settled) return;\n\n if (result.ok) {\n settled = true;\n resolve(result as ReturnOk);\n return;\n }\n\n if (!firstError) firstError = result;\n pendingCount--;\n\n if (pendingCount === 0) {\n resolve(firstError as ReturnErr);\n }\n });\n }\n });\n}\n\ntype AllAsyncValues<T extends readonly MaybeAsyncResult<unknown, unknown, unknown>[]> = {\n [K in keyof T]: Awaited<T[K]> extends Result<infer V, unknown, unknown> ? V : never;\n};\ntype AllAsyncErrors<T extends readonly MaybeAsyncResult<unknown, unknown, unknown>[]> = {\n [K in keyof T]: Awaited<T[K]> extends Result<unknown, infer E, unknown> ? E : never;\n}[number];\ntype AllAsyncCauses<T extends readonly MaybeAsyncResult<unknown, unknown, unknown>[]> = {\n [K in keyof T]: Awaited<T[K]> extends Result<unknown, unknown, infer C> ? C : never;\n}[number];\n\n/**\n * Combines multiple Results or Promises of Results, collecting all errors (async version of `allSettled`).\n *\n * ## When to Use\n *\n * Use `allSettledAsync()` when:\n * - You have multiple async operations and need ALL errors reported\n * - You're doing async form validation (show all field errors at once)\n * - You want to run operations in parallel and collect all results\n *\n * ## Behavior\n *\n * **Note:** Unlike `Promise.allSettled()`, this returns a Result:\n * - `ok(values[])` if ALL succeed\n * - `err(SettledError[])` if ANY fail (with all collected errors)\n *\n * This is consistent with awaitly's philosophy - all functions return Results.\n * `Promise.allSettled()` always succeeds with per-item status objects; this function\n * returns a single Result indicating overall success or failure.\n *\n * ## Why Use This Instead of `allSettled`\n *\n * - **Parallel execution**: All Promises start immediately (faster)\n * - **Async support**: Works with Promises and AsyncResults\n * - **Promise rejection handling**: Converts Promise rejections to `PromiseRejectedError`\n *\n * ## Important\n *\n * - **No short-circuit**: All operations complete (even if some fail)\n * - **Parallel**: All operations run simultaneously\n * - **Error array**: Returns array of `SettledError` objects (`{ error, cause? }`)\n *\n * @param results - Array of Results or Promises of Results to combine (all are evaluated)\n * @returns A Promise resolving to a Result with:\n * - `ok(values[])` - Array of all success values if ALL succeed\n * - `err(errors[])` - Array of `SettledError` objects if ANY fail\n *\n * @example\n * ```typescript\n * // Async form validation - see all errors at once\n * const validated = await allSettledAsync([\n * validateEmailAsync(email),\n * validatePasswordAsync(password),\n * checkUsernameAvailableAsync(username),\n * ]);\n *\n * if (!validated.ok) {\n * // validated.error is array of all validation failures\n * console.log('Errors:', validated.error.map(e => e.error));\n * }\n *\n * // Parallel API calls with error collection\n * const results = await allSettledAsync([\n * fetchUser('1'),\n * fetchUser('2'),\n * fetchUser('3'),\n * ]);\n * ```\n */\nexport async function allSettledAsync<\n const T extends readonly MaybeAsyncResult<unknown, unknown, unknown>[],\n>(\n results: T\n): Promise<Result<AllAsyncValues<T>, SettledError<AllAsyncErrors<T> | PromiseRejectedError, AllAsyncCauses<T> | PromiseRejectionCause>[]>> {\n const settled = await Promise.all(\n results.map((item) =>\n Promise.resolve(item)\n .then((result) => ({ status: \"result\" as const, result }))\n .catch((reason) => ({\n status: \"rejected\" as const,\n error: { type: \"PROMISE_REJECTED\" as const, cause: reason } as PromiseRejectedError,\n cause: { type: \"PROMISE_REJECTION\" as const, reason } as PromiseRejectionCause,\n }))\n )\n );\n\n const values: unknown[] = [];\n const errors: SettledError<unknown, unknown>[] = [];\n\n for (const item of settled) {\n if (item.status === \"rejected\") {\n errors.push({ error: item.error, cause: item.cause });\n } else if (item.result.ok) {\n values.push(item.result.value);\n } else {\n errors.push({ error: item.result.error, cause: item.result.cause });\n }\n }\n\n if (errors.length > 0) {\n return err(errors) as unknown as Result<AllAsyncValues<T>, SettledError<AllAsyncErrors<T> | PromiseRejectedError, AllAsyncCauses<T> | PromiseRejectionCause>[]>;\n }\n return ok(values) as unknown as Result<AllAsyncValues<T>, SettledError<AllAsyncErrors<T> | PromiseRejectedError, AllAsyncCauses<T> | PromiseRejectionCause>[]>;\n}\n\n/**\n * Combines two Results into a tuple Result.\n *\n * ## When to Use\n *\n * Use `zip()` when:\n * - You have two independent Results and need both values together\n * - You want to combine validation results before processing\n * - You need a pair/tuple from two separate operations\n *\n * ## Why Use This Instead of `all()`\n *\n * - **Simpler types**: Returns `[A, B]` instead of array inference\n * - **Two-argument**: Cleaner API for common case of combining two Results\n * - **Compose with andThen**: Chain multiple zips for complex combinations\n *\n * ## Important\n *\n * - **Short-circuits**: Returns first error if either fails\n * - **Order matters**: If both fail, returns error from first argument\n * - **Use `all()`**: For more than 2 Results\n *\n * @param a - First Result\n * @param b - Second Result\n * @returns A Result containing a tuple `[A, B]` if both succeed, or the first error\n *\n * @example\n * ```typescript\n * // Combine two Results\n * const userResult = await fetchUser('1');\n * const postsResult = await fetchPosts('1');\n * const combined = zip(userResult, postsResult);\n * // combined: Result<[User, Post[]], UserError | PostsError>\n *\n * // Use with andThen for chaining\n * const result = andThen(\n * zip(fetchUser('1'), fetchPosts('1')),\n * ([user, posts]) => createDashboard(user, posts)\n * );\n *\n * // Validation combination\n * const validated = zip(\n * validateEmail(email),\n * validatePassword(password)\n * );\n * if (validated.ok) {\n * const [email, password] = validated.value;\n * createAccount(email, password);\n * }\n * ```\n */\nexport function zip<A, EA, CA, B, EB, CB>(\n a: Result<A, EA, CA>,\n b: Result<B, EB, CB>\n): Result<[A, B], EA | EB, CA | CB> {\n if (!a.ok) return a as Result<never, EA, CA>;\n if (!b.ok) return b as Result<never, EB, CB>;\n return ok([a.value, b.value]) as Result<[A, B], never, never>;\n}\n\n/**\n * Async version of `zip()` - combines two Results or Promises of Results into a tuple.\n *\n * ## When to Use\n *\n * Use `zipAsync()` when:\n * - You have two async operations and need both results together\n * - You want to run two fetches in parallel and combine results\n * - You need to combine Promises of Results into a single Result\n *\n * ## Why Use This Instead of `allAsync()`\n *\n * - **Simpler types**: Returns `[A, B]` instead of array inference\n * - **Two-argument**: Cleaner API for common case of combining two async Results\n * - **Parallel execution**: Both Promises start immediately\n *\n * ## Important\n *\n * - **Parallel**: Both operations run simultaneously (faster than sequential)\n * - **Short-circuits result**: Returns first argument's error if it fails, else second's\n * - **Waits for both**: Both Promises complete before returning (unlike `allAsync` fail-fast)\n * - **Rejection handling**: Promise rejections are wrapped as `PromiseRejectedError`\n * - **Use `allAsync()`**: For more than 2 Results\n *\n * @param a - First Result or Promise of Result\n * @param b - Second Result or Promise of Result\n * @returns A Promise of Result containing a tuple `[A, B]` if both succeed\n *\n * @example\n * ```typescript\n * // Parallel async operations\n * const result = await zipAsync(\n * fetchUser('1'),\n * fetchPosts('1')\n * );\n * // Both fetches run in parallel\n * // result: Result<[User, Post[]], UserError | PostsError>\n *\n * // Mix sync and async\n * const combined = await zipAsync(\n * ok({ cached: true }), // Already resolved\n * fetchFromAPI(id), // Async fetch\n * );\n *\n * // With chaining\n * const dashboard = await zipAsync(fetchUser('1'), fetchPosts('1'))\n * .then(result => andThen(result, ([user, posts]) => createDashboard(user, posts)));\n * ```\n */\nexport async function zipAsync<A, EA, CA, B, EB, CB>(\n a: Result<A, EA, CA> | Promise<Result<A, EA, CA>>,\n b: Result<B, EB, CB> | Promise<Result<B, EB, CB>>\n): AsyncResult<[A, B], EA | EB | PromiseRejectedError, CA | CB | PromiseRejectionCause> {\n // Wrap rejections into PromiseRejectedError (consistent with allAsync)\n const wrapRejection = <T, E, C>(\n p: Result<T, E, C> | Promise<Result<T, E, C>>\n ): Promise<Result<T, E | PromiseRejectedError, C | PromiseRejectionCause>> =>\n Promise.resolve(p).catch((reason) =>\n err(\n { type: \"PROMISE_REJECTED\" as const, cause: reason } as PromiseRejectedError,\n { cause: { type: \"PROMISE_REJECTION\" as const, reason } as PromiseRejectionCause }\n )\n );\n\n const [ra, rb] = await Promise.all([wrapRejection(a), wrapRejection(b)]);\n return zip(ra, rb);\n}\n","/**\n * Type guards for workflow events and errors.\n */\n\nimport type { WorkflowEvent } from \"../core\";\nimport type { WorkflowCancelledError, PendingApproval, ApprovalRejected, PendingHook, ResumeState } from \"./types\";\n\n/**\n * Type guard to check if an event is a step_complete event.\n * Use this to filter events for state persistence.\n *\n * @param event - The workflow event to check\n * @returns `true` if the event is a step_complete event, `false` otherwise\n *\n * @example\n * ```typescript\n * const savedSteps = new Map<string, Result<unknown, unknown>>();\n *\n * const workflow = createWorkflow({ fetchUser }, {\n * onEvent: (event) => {\n * if (isStepComplete(event)) {\n * savedSteps.set(event.stepKey, event.result);\n * }\n * }\n * });\n * ```\n */\n/**\n * Type guard for runtime ResumeState (steps is a Map). Use to discriminate from WorkflowSnapshot when loading.\n */\nexport function isResumeState(x: unknown): x is ResumeState {\n return (\n typeof x === \"object\" &&\n x !== null &&\n \"steps\" in x &&\n (x as ResumeState).steps instanceof Map\n );\n}\n\nexport function isStepComplete(\n event: WorkflowEvent<unknown>\n): event is Extract<WorkflowEvent<unknown>, { type: \"step_complete\" }> {\n return event.type === \"step_complete\";\n}\n\n/**\n * Type guard to check if an error is a WorkflowCancelledError.\n *\n * @param error - The error to check\n * @returns `true` if the error is a WorkflowCancelledError, `false` otherwise\n */\nexport function isWorkflowCancelled(error: unknown): error is WorkflowCancelledError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as WorkflowCancelledError).type === \"WORKFLOW_CANCELLED\"\n );\n}\n\n/**\n * Type guard to check if an error is a PendingApproval.\n *\n * @param error - The error to check\n * @returns `true` if the error is a PendingApproval, `false` otherwise\n *\n * @example\n * ```typescript\n * const result = await workflow(...);\n * if (!result.ok && isPendingApproval(result.error)) {\n * console.log(`Waiting for approval: ${result.error.stepKey}`);\n * }\n * ```\n */\nexport function isPendingApproval(error: unknown): error is PendingApproval {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as PendingApproval).type === \"PENDING_APPROVAL\"\n );\n}\n\n/**\n * Type guard to check if an error is an ApprovalRejected.\n *\n * @param error - The error to check\n * @returns `true` if the error is an ApprovalRejected, `false` otherwise\n */\nexport function isApprovalRejected(error: unknown): error is ApprovalRejected {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as ApprovalRejected).type === \"APPROVAL_REJECTED\"\n );\n}\n\n/**\n * Type guard to check if an error is a PendingHook.\n *\n * @param error - The error to check\n * @returns `true` if the error is a PendingHook, `false` otherwise\n */\nexport function isPendingHook(error: unknown): error is PendingHook {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as PendingHook).type === \"PENDING_HOOK\"\n );\n}\n","/**\n * awaitly/persistence\n *\n * Simplified Persistence API for workflow snapshots.\n * Provides JSON-serializable snapshot format and store adapters.\n */\n\nimport type { Result } from \"./core\";\nimport type { StepCache } from \"./workflow\";\n\n// =============================================================================\n// JSON-Safe Type Enforcement\n// =============================================================================\n\n/**\n * Enforce JSON-safety at type level.\n * Only allows values that can be safely serialized with JSON.stringify.\n */\nexport type JSONValue =\n | null\n | boolean\n | number\n | string\n | JSONValue[]\n | { [k: string]: JSONValue };\n\n// =============================================================================\n// WorkflowSnapshot Types (Simplified API)\n// =============================================================================\n\n/**\n * Canonical error wire format - handles both Error instances and thrown non-Errors.\n * This is the single source of truth for serialized errors in snapshots.\n */\nexport type SerializedCause =\n | { type: \"error\"; name: string; message: string; stack?: string; cause?: SerializedCause }\n | { type: \"thrown\"; originalType?: string; value?: JSONValue; stringRepresentation: string; truncated?: true };\n\n/**\n * Single source of truth for step outcome (no error/cause confusion).\n * Uses discriminated union with `ok` field.\n */\nexport type StepResult =\n | { ok: true; value: JSONValue }\n | { ok: false; error: JSONValue; cause: SerializedCause; meta?: { origin: \"result\" | \"throw\" } };\n\n/**\n * JSON-serializable workflow snapshot.\n * Designed to be passed directly to JSON.stringify without special handling.\n *\n * @example\n * ```typescript\n * // Persist\n * localStorage.setItem('wf-123', JSON.stringify(wf.getSnapshot()));\n *\n * // Restore (safe pattern - storage can be empty/corrupt)\n * const raw = localStorage.getItem('wf-123');\n * const snapshot = raw ? JSON.parse(raw) : null;\n * createWorkflow(deps, { snapshot }); // null = fresh start\n * ```\n */\nexport interface WorkflowSnapshot {\n /** Snapshot format version (literal type - bump when shape changes) */\n formatVersion: 1;\n /** Workflow name (from createWorkflow first argument). */\n workflowName?: string;\n /** Step results keyed by step ID. Uses Object.create(null) internally. */\n steps: Record<string, StepResult>;\n /** Execution state metadata */\n execution: {\n status: \"running\" | \"completed\" | \"failed\";\n /** ISO timestamp (UTC toISOString()) */\n lastUpdated: string;\n /** ISO timestamp if finished */\n completedAt?: string;\n /**\n * For paused/running workflows: the step key of the current step.\n * Aligns with Workflow Diagram DSL step state ids (see awaitly/workflow diagram-dsl)\n * so visualizers can highlight the current node.\n */\n currentStepId?: string;\n };\n /** Optional metadata for workflow identification and replay */\n metadata?: {\n /** Detect wrong snapshot for wrong workflow */\n workflowId?: string;\n /** Optional: detect definition changes (user-supplied, advisory only) */\n definitionHash?: string;\n /** Original input for replay */\n input?: JSONValue;\n [key: string]: JSONValue | undefined;\n };\n /** Warnings for lossy serialization (keeps step results pure) */\n warnings?: Array<{\n type: \"lossy_value\";\n stepId: string;\n path: string;\n reason: \"non-json\" | \"circular\" | \"encode-failed\";\n }>;\n}\n\n/**\n * Warning entry for lossy value serialization.\n */\nexport type SnapshotWarning = NonNullable<WorkflowSnapshot[\"warnings\"]>[number];\n\n// =============================================================================\n// Snapshot Validation\n// =============================================================================\n\n/**\n * Error thrown when snapshot structure is invalid.\n */\nexport class SnapshotFormatError extends Error {\n constructor(\n message: string,\n public readonly errors: string[] = []\n ) {\n super(message);\n this.name = \"SnapshotFormatError\";\n }\n}\n\n/**\n * Error thrown when snapshot doesn't match workflow (unknown steps, workflowId mismatch).\n */\nexport class SnapshotMismatchError extends Error {\n constructor(\n message: string,\n public readonly mismatchType: \"unknown_steps\" | \"workflow_id\" | \"definition_hash\",\n public readonly details?: {\n unknownSteps?: string[];\n snapshotWorkflowId?: string;\n expectedWorkflowId?: string;\n snapshotHash?: string;\n expectedHash?: string;\n }\n ) {\n super(message);\n this.name = \"SnapshotMismatchError\";\n }\n}\n\n/**\n * Error thrown when decode fails during restore.\n */\nexport class SnapshotDecodeError extends Error {\n constructor(\n message: string,\n public readonly stepId: string,\n public readonly originalError?: unknown\n ) {\n super(message);\n this.name = \"SnapshotDecodeError\";\n }\n}\n\n/**\n * Light check to see if an object looks like a WorkflowSnapshot.\n * Cheap check for basic structure - use validateSnapshot() for full validation.\n *\n * @example\n * ```typescript\n * const raw = JSON.parse(localStorage.getItem('wf-123') || 'null');\n * if (looksLikeWorkflowSnapshot(raw)) {\n * createWorkflow(deps, { snapshot: raw });\n * }\n * ```\n */\nexport function looksLikeWorkflowSnapshot(obj: unknown): obj is WorkflowSnapshot {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"formatVersion\" in obj &&\n (obj as { formatVersion: unknown }).formatVersion === 1 &&\n \"steps\" in obj &&\n typeof (obj as { steps: unknown }).steps === \"object\" &&\n (obj as { steps: unknown }).steps !== null &&\n \"execution\" in obj &&\n typeof (obj as { execution: unknown }).execution === \"object\"\n );\n}\n\n/**\n * Type guard for WorkflowSnapshot. Same as looksLikeWorkflowSnapshot; use for consistent naming with isResumeState / isSerializedResumeState.\n */\nexport const isWorkflowSnapshot = looksLikeWorkflowSnapshot;\n\n/**\n * Full validation with detailed errors.\n * Returns either a validated snapshot or an array of validation errors.\n */\nexport function validateSnapshot(obj: unknown): { valid: true; snapshot: WorkflowSnapshot } | { valid: false; errors: string[] } {\n const errors: string[] = [];\n\n if (typeof obj !== \"object\" || obj === null) {\n return { valid: false, errors: [\"Snapshot must be an object\"] };\n }\n\n const snapshot = obj as Record<string, unknown>;\n\n // Check formatVersion\n if (!(\"formatVersion\" in snapshot)) {\n errors.push(\"Missing required field: formatVersion\");\n } else if (snapshot.formatVersion !== 1) {\n errors.push(`Invalid formatVersion: expected 1, got ${snapshot.formatVersion}`);\n }\n\n // Check steps\n if (!(\"steps\" in snapshot)) {\n errors.push(\"Missing required field: steps\");\n } else if (typeof snapshot.steps !== \"object\" || snapshot.steps === null) {\n errors.push(\"steps must be an object\");\n } else {\n // Validate each step result\n const steps = snapshot.steps as Record<string, unknown>;\n for (const [stepId, stepResult] of Object.entries(steps)) {\n if (typeof stepResult !== \"object\" || stepResult === null) {\n errors.push(`steps[\"${stepId}\"] must be an object`);\n continue;\n }\n\n const step = stepResult as Record<string, unknown>;\n if (!(\"ok\" in step)) {\n errors.push(`steps[\"${stepId}\"] missing required field: ok`);\n } else if (typeof step.ok !== \"boolean\") {\n errors.push(`steps[\"${stepId}\"].ok must be a boolean`);\n } else if (step.ok === false) {\n if (!(\"error\" in step)) {\n errors.push(`steps[\"${stepId}\"] is error result but missing error field`);\n }\n if (!(\"cause\" in step)) {\n errors.push(`steps[\"${stepId}\"] is error result but missing cause field`);\n }\n }\n }\n }\n\n // Check execution\n if (!(\"execution\" in snapshot)) {\n errors.push(\"Missing required field: execution\");\n } else if (typeof snapshot.execution !== \"object\" || snapshot.execution === null) {\n errors.push(\"execution must be an object\");\n } else {\n const execution = snapshot.execution as Record<string, unknown>;\n if (!(\"status\" in execution)) {\n errors.push(\"execution missing required field: status\");\n } else if (![\"running\", \"completed\", \"failed\"].includes(execution.status as string)) {\n errors.push(`execution.status must be one of: running, completed, failed`);\n }\n if (!(\"lastUpdated\" in execution)) {\n errors.push(\"execution missing required field: lastUpdated\");\n } else if (typeof execution.lastUpdated !== \"string\") {\n errors.push(\"execution.lastUpdated must be a string (ISO timestamp)\");\n }\n }\n\n if (errors.length > 0) {\n return { valid: false, errors };\n }\n\n return { valid: true, snapshot: obj as WorkflowSnapshot };\n}\n\n/**\n * Throwing helper for cleaner code.\n * Validates a snapshot and throws SnapshotFormatError if invalid.\n *\n * @throws {SnapshotFormatError} If snapshot is invalid\n */\nexport function assertValidSnapshot(obj: unknown): WorkflowSnapshot {\n const result = validateSnapshot(obj);\n if (!result.valid) {\n throw new SnapshotFormatError(`Invalid snapshot format: ${result.errors[0]}`, result.errors);\n }\n return result.snapshot;\n}\n\n// =============================================================================\n// Snapshot Merge Helper\n// =============================================================================\n\n/**\n * Merge two snapshots (for incremental updates).\n * Delta steps overwrite base steps; execution from delta; metadata shallow merge.\n */\nexport function mergeSnapshots(base: WorkflowSnapshot, delta: WorkflowSnapshot): WorkflowSnapshot {\n // Create new steps object using Object.create(null) for prototype safety\n const mergedSteps = Object.create(null) as Record<string, StepResult>;\n\n // Copy base steps (use Object.prototype.hasOwnProperty for ES2020 compat)\n for (const [key, value] of Object.entries(base.steps)) {\n if (Object.prototype.hasOwnProperty.call(base.steps, key)) {\n mergedSteps[key] = value;\n }\n }\n\n // Overlay delta steps\n for (const [key, value] of Object.entries(delta.steps)) {\n if (Object.prototype.hasOwnProperty.call(delta.steps, key)) {\n mergedSteps[key] = value;\n }\n }\n\n // Merge metadata (delta wins for conflicts)\n const mergedMetadata = base.metadata || delta.metadata\n ? { ...base.metadata, ...delta.metadata }\n : undefined;\n\n // Merge warnings: only keep base warnings for steps not re-executed in delta.\n // If a step was re-executed and serialized cleanly, its old warning should disappear.\n const baseWarnings = (base.warnings || []).filter(\n (w) => !Object.prototype.hasOwnProperty.call(delta.steps, w.stepId)\n );\n const mergedWarnings = [...baseWarnings, ...(delta.warnings || [])];\n\n return {\n formatVersion: 1,\n steps: mergedSteps,\n execution: { ...delta.execution },\n metadata: mergedMetadata,\n warnings: mergedWarnings.length > 0 ? mergedWarnings : undefined,\n };\n}\n\n// =============================================================================\n// Serialization Helpers\n// =============================================================================\n\n/**\n * Maximum length for string representation in thrown non-Error values.\n */\nconst MAX_STRING_REPRESENTATION_LENGTH = 1000;\n\n/**\n * Serialize an Error object to SerializedCause format.\n * Preserves Error.cause recursively.\n */\nexport function serializeError(error: Error): SerializedCause {\n const serialized: SerializedCause = {\n type: \"error\",\n name: error.name,\n message: error.message,\n };\n\n if (error.stack) {\n serialized.stack = error.stack;\n }\n\n // Recursively serialize Error.cause if present (ES2022 feature, but commonly available)\n const errorWithCause = error as Error & { cause?: unknown };\n if (errorWithCause.cause !== undefined) {\n if (errorWithCause.cause instanceof Error) {\n serialized.cause = serializeError(errorWithCause.cause);\n } else {\n // cause is not an Error, serialize as thrown value\n serialized.cause = serializeThrown(errorWithCause.cause);\n }\n }\n\n return serialized;\n}\n\n/**\n * Serialize a non-Error thrown value to SerializedCause format.\n */\nexport function serializeThrown(value: unknown): SerializedCause {\n // Get string representation\n let stringRepresentation: string;\n let truncated = false;\n\n try {\n stringRepresentation = String(value);\n if (stringRepresentation.length > MAX_STRING_REPRESENTATION_LENGTH) {\n stringRepresentation = stringRepresentation.slice(0, MAX_STRING_REPRESENTATION_LENGTH);\n truncated = true;\n }\n } catch {\n stringRepresentation = \"[unable to convert to string]\";\n }\n\n // Try to get the original type\n const originalType = value === null\n ? \"null\"\n : typeof value === \"object\"\n ? (value.constructor?.name ?? \"Object\")\n : typeof value;\n\n // Try to serialize the value as JSON\n let jsonValue: JSONValue | undefined;\n try {\n const serialized = JSON.stringify(value);\n if (serialized !== undefined) {\n jsonValue = JSON.parse(serialized) as JSONValue;\n }\n } catch {\n // Non-JSON-serializable, will only use stringRepresentation\n }\n\n const result: SerializedCause = {\n type: \"thrown\",\n originalType,\n stringRepresentation,\n };\n\n if (jsonValue !== undefined) {\n result.value = jsonValue;\n }\n\n if (truncated) {\n result.truncated = true;\n }\n\n return result;\n}\n\n/**\n * Deserialize a SerializedCause back to its original form.\n */\nexport function deserializeCauseNew(serialized: SerializedCause): unknown {\n if (serialized.type === \"error\") {\n const error = new Error(serialized.message);\n error.name = serialized.name;\n if (serialized.stack) {\n error.stack = serialized.stack;\n }\n if (serialized.cause) {\n (error as Error & { cause: unknown }).cause = deserializeCauseNew(serialized.cause);\n }\n return error;\n }\n\n // type === \"thrown\"\n // Return the JSON value if available, otherwise the string representation\n return serialized.value !== undefined ? serialized.value : serialized.stringRepresentation;\n}\n\n// =============================================================================\n// SnapshotStore Interface (New Simplified API)\n// =============================================================================\n\n/**\n * Simplified store interface for workflow snapshot persistence.\n * Works directly with WorkflowSnapshot objects.\n *\n * Adapters may implement an extended contract (see awaitly/workflow): save can accept\n * WorkflowSnapshot | ResumeState; load can return WorkflowSnapshot | ResumeState | null.\n * Use isWorkflowSnapshot / isSerializedResumeState and serializeResumeState / deserializeResumeState\n * when branching. For type-safe restore, use store.loadResumeState(id) or toResumeState(await store.load(id)).\n *\n * @example\n * ```typescript\n * import { postgres } from 'awaitly-postgres';\n * import { createWorkflow } from 'awaitly/workflow';\n *\n * const store = postgres('postgresql://localhost/mydb');\n * const workflow = createWorkflow(deps);\n *\n * // Run and persist resume state\n * const { result, resumeState } = await workflow.runWithState(fn);\n * await store.save('wf-123', resumeState);\n *\n * // Restore\n * const loaded = await store.load('wf-123');\n * const resumeState = toResumeState(loaded);\n * if (resumeState) await workflow.run(fn, { resumeState });\n * ```\n */\nexport interface SnapshotStore {\n /** Save a workflow snapshot (upsert - insert or update). Adapters may also accept ResumeState. */\n save(id: string, snapshot: WorkflowSnapshot): Promise<void>;\n /** Load a workflow snapshot. Returns null if not found. Adapters may return ResumeState when stored as such. */\n load(id: string): Promise<WorkflowSnapshot | null>;\n /** Delete a workflow snapshot. */\n delete(id: string): Promise<void>;\n /** List workflow IDs with their last update time. */\n list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>>;\n /** Clean shutdown for tests/graceful exit. */\n close(): Promise<void>;\n}\n\n// =============================================================================\n// In-Memory Cache Adapter\n// =============================================================================\n\n/**\n * Options for the in-memory cache adapter.\n */\nexport interface MemoryCacheOptions {\n /**\n * Maximum number of entries to store.\n * Oldest entries are evicted when limit is reached.\n */\n maxSize?: number;\n\n /**\n * Time-to-live in milliseconds.\n * Entries are automatically removed after this duration.\n */\n ttl?: number;\n}\n\n/**\n * Create an in-memory StepCache with optional LRU eviction and TTL.\n *\n * @param options - Cache options\n * @returns StepCache implementation\n *\n * @example\n * ```typescript\n * const cache = createMemoryCache({ maxSize: 1000, ttl: 60000 });\n * const workflow = createWorkflow(deps, { cache });\n * ```\n */\nexport function createMemoryCache(options: MemoryCacheOptions = {}): StepCache {\n const { maxSize, ttl } = options;\n const cache = new Map<string, {\n result: Result<unknown, unknown, unknown>;\n timestamp: number;\n entryTtl?: number;\n }>();\n\n const isExpired = (entry: { timestamp: number; entryTtl?: number }): boolean => {\n const effectiveTtl = entry.entryTtl ?? ttl;\n if (!effectiveTtl) return false;\n return Date.now() - entry.timestamp > effectiveTtl;\n };\n\n const evictExpired = (): void => {\n for (const [key, entry] of cache) {\n if (isExpired(entry)) {\n cache.delete(key);\n }\n }\n };\n\n const evictOldest = (): void => {\n if (!maxSize || cache.size < maxSize) return;\n\n // Find oldest entry\n let oldestKey: string | undefined;\n let oldestTime = Infinity;\n\n for (const [key, entry] of cache) {\n if (entry.timestamp < oldestTime) {\n oldestTime = entry.timestamp;\n oldestKey = key;\n }\n }\n\n if (oldestKey) {\n cache.delete(oldestKey);\n }\n };\n\n return {\n get(key: string): Result<unknown, unknown, unknown> | undefined {\n evictExpired();\n const entry = cache.get(key);\n if (!entry) return undefined;\n if (isExpired(entry)) {\n cache.delete(key);\n return undefined;\n }\n return entry.result;\n },\n\n set(key: string, result: Result<unknown, unknown, unknown>, options?: { ttl?: number }): void {\n evictExpired();\n evictOldest();\n cache.set(key, { result, timestamp: Date.now(), entryTtl: options?.ttl });\n },\n\n has(key: string): boolean {\n evictExpired();\n const entry = cache.get(key);\n if (!entry) return false;\n if (isExpired(entry)) {\n cache.delete(key);\n return false;\n }\n return true;\n },\n\n delete(key: string): boolean {\n return cache.delete(key);\n },\n\n clear(): void {\n cache.clear();\n },\n };\n}\n","/**\n * Cache entry encoding for step results.\n * Preserves StepFailureMeta for proper replay. Internal use only.\n */\n\nimport { err, type StepFailureMeta, type Err } from \"../core\";\n\n/**\n * Marker for cached error entries that include step failure metadata.\n * This allows us to preserve origin:\"throw\" vs origin:\"result\" when replaying,\n * while also preserving the original cause value for direct cache access.\n * @internal\n */\nexport interface CachedErrorCause<C = unknown> {\n __cachedMeta: true;\n /** The original cause from the step result (preserved for direct access) */\n originalCause: C;\n /** Metadata for proper replay behavior */\n meta: StepFailureMeta;\n}\n\nexport function isCachedErrorCause(cause: unknown): cause is CachedErrorCause {\n return (\n typeof cause === \"object\" &&\n cause !== null &&\n (cause as CachedErrorCause).__cachedMeta === true\n );\n}\n\n/**\n * Encode an error result for caching, preserving both the original cause\n * and metadata needed for proper replay.\n */\nexport function encodeCachedError<E, C>(\n error: E,\n meta: StepFailureMeta,\n originalCause: C\n): Err<E, CachedErrorCause<C>> {\n return err(error, {\n cause: { __cachedMeta: true, originalCause, meta } as CachedErrorCause<C>,\n });\n}\n\nexport function decodeCachedMeta(cause: unknown): StepFailureMeta {\n if (isCachedErrorCause(cause)) {\n return cause.meta;\n }\n // Fallback for any non-encoded cause (shouldn't happen, but safe default)\n return { origin: \"result\", resultCause: cause };\n}\n","/**\n * Serialization for ResumeState to/from JSON-safe format.\n * Centralizes Map serialization so adapters don't re-implement it.\n */\n\nimport type { ResumeState, ResumeStateEntry } from \"./types\";\nimport type { StepResult, SerializedCause, JSONValue } from \"../persistence\";\nimport { serializeError, serializeThrown, deserializeCauseNew } from \"../persistence\";\nimport { ok } from \"../core\";\nimport { isCachedErrorCause } from \"./cache-encoding\";\nimport { encodeCachedError } from \"./cache-encoding\";\n\n/**\n * JSON-serializable resume state. Use this shape when persisting to storage.\n * Discriminator `kind: \"ResumeState\"` enables adapters and migrations to detect format.\n */\nexport type SerializedResumeState = {\n kind: \"ResumeState\";\n steps: [string, StepResult][];\n};\n\n/**\n * Serialize resume state to a JSON-safe object. Preserves step order (array).\n * Use with JSON.stringify for storage; adapters can rely on this shape.\n *\n * @example\n * const serialized = serializeResumeState(collector.getResumeState());\n * await store.save(id, JSON.stringify(serialized));\n */\nexport function serializeResumeState(state: ResumeState): SerializedResumeState {\n const steps: [string, StepResult][] = [];\n for (const [key, entry] of state.steps) {\n const stepResult = entryToStepResult(entry);\n steps.push([key, stepResult]);\n }\n return { kind: \"ResumeState\", steps };\n}\n\n/**\n * Type guard for SerializedResumeState. Use when loading from storage to discriminate from WorkflowSnapshot.\n */\nexport function isSerializedResumeState(x: unknown): x is SerializedResumeState {\n return (\n typeof x === \"object\" &&\n x !== null &&\n (x as SerializedResumeState).kind === \"ResumeState\" &&\n Array.isArray((x as SerializedResumeState).steps)\n );\n}\n\n/**\n * Deserialize from JSON-parsed object back to ResumeState (runtime Map).\n *\n * @example\n * const parsed = JSON.parse(await store.load(id));\n * if (isSerializedResumeState(parsed)) {\n * const state = deserializeResumeState(parsed);\n * await workflow.run(fn, { resumeState: state });\n * }\n */\nexport function deserializeResumeState(raw: SerializedResumeState): ResumeState {\n const steps = new Map<string, ResumeStateEntry>();\n for (const [key, stepResult] of raw.steps) {\n steps.set(key, stepResultToEntry(stepResult));\n }\n return { steps };\n}\n\nfunction entryToStepResult(entry: ResumeStateEntry): StepResult {\n const { result, meta } = entry;\n if (result.ok) {\n return { ok: true, value: result.value as JSONValue };\n }\n const causeToSerialize = isCachedErrorCause(result.cause)\n ? result.cause.originalCause\n : result.cause;\n const serializedCause: SerializedCause =\n causeToSerialize instanceof Error\n ? serializeError(causeToSerialize)\n : serializeThrown(causeToSerialize);\n const origin: \"result\" | \"throw\" =\n meta?.origin === \"throw\" ? \"throw\" : \"result\";\n return {\n ok: false,\n error: result.error as JSONValue,\n cause: serializedCause,\n meta: { origin },\n };\n}\n\nfunction stepResultToEntry(sr: StepResult): ResumeStateEntry {\n if (sr.ok) {\n return { result: ok(sr.value) };\n }\n const deserializedCause = deserializeCauseNew(sr.cause);\n const meta =\n sr.meta?.origin === \"throw\"\n ? { origin: \"throw\" as const, thrown: deserializedCause }\n : { origin: \"result\" as const, resultCause: deserializedCause };\n const errorResult = encodeCachedError(sr.error, meta, deserializedCause);\n return { result: errorResult, meta };\n}\n","/**\n * Extended persistence contract: save/load accept both WorkflowSnapshot and ResumeState.\n * Use type guards (isWorkflowSnapshot, isResumeState, isSerializedResumeState) in adapters.\n * Core persistence.SnapshotStore stays narrow; this module defines the broad contract and helpers.\n */\n\nimport type { WorkflowSnapshot } from \"../persistence\";\nimport type { ResumeState } from \"./types\";\nimport type { SerializedResumeState } from \"./serialize-resume-state\";\nimport { isResumeState } from \"./guards\";\n\n/** What stores persist (JSON/database shape). Use for type discrimination in adapters. */\nexport type PersistedWorkflowState = WorkflowSnapshot | SerializedResumeState;\n\n/** What save() can accept: snapshot or runtime resume state. Adapters branch with type guards. */\nexport type StoreSaveInput = WorkflowSnapshot | ResumeState;\n\n/** What load() can return. Use toResumeState() or loadResumeState for type-safe restore. */\nexport type StoreLoadResult = WorkflowSnapshot | ResumeState | null;\n\n/**\n * Convert a loaded value to ResumeState for workflow.run(fn, { resumeState }).\n * - If loaded is already ResumeState, returns it.\n * - If loaded is null or WorkflowSnapshot, returns undefined (use run(fn, { snapshot }) for snapshots).\n * Two explicit flows: resume from ResumeState vs resume from snapshot; don't pass snapshot to resumeState.\n *\n * @example\n * const loaded = await store.load('wf-123');\n * const resumeState = toResumeState(loaded);\n * if (resumeState) await workflow.run(fn, { resumeState });\n */\nexport function toResumeState(loaded: StoreLoadResult): ResumeState | undefined {\n if (loaded === null) return undefined;\n if (isResumeState(loaded)) return loaded;\n return undefined;\n}\n","/**\n * Workflow Versioning and Migration\n *\n * Handle schema changes when resuming workflows that were persisted\n * with older step shapes.\n *\n * @example\n * ```typescript\n * import { createVersionedWorkflow } from 'awaitly';\n *\n * const workflow = createVersionedWorkflow(\n * { fetchUser, chargeCard },\n * {\n * version: 2,\n * migrations: {\n * 1: (state) => migrateV1ToV2(state),\n * },\n * resumeState: loadState(runId),\n * }\n * );\n * ```\n */\n\nimport type { ResumeState, ResumeStateEntry } from \"./workflow/types\";\nimport { ok, err, type Result } from \"./core\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Version number type.\n */\nexport type Version = number;\n\n/**\n * Migration function that transforms state from one version to the next.\n */\nexport type MigrationFn = (state: ResumeState) => ResumeState | Promise<ResumeState>;\n\n/**\n * Map of migrations keyed by the source version.\n * Migration at key N transforms state from version N to version N+1.\n */\nexport type Migrations = Record<Version, MigrationFn>;\n\n/**\n * Versioned state includes the version number.\n */\nexport interface VersionedState {\n version: Version;\n state: ResumeState;\n}\n\n/**\n * Configuration for versioned workflow.\n */\nexport interface VersionedWorkflowConfig {\n /**\n * Current workflow version.\n */\n version: Version;\n\n /**\n * Migrations for upgrading old states.\n * Key is the source version, value transforms to next version.\n */\n migrations?: Migrations;\n\n /**\n * Strict mode - fail if state version is higher than current.\n * @default true\n */\n strictVersioning?: boolean;\n}\n\n/**\n * Error when version migration fails.\n */\nexport interface MigrationError {\n type: \"MIGRATION_ERROR\";\n fromVersion: Version;\n toVersion: Version;\n cause: unknown;\n}\n\n/**\n * Error when state version is incompatible.\n */\nexport interface VersionIncompatibleError {\n type: \"VERSION_INCOMPATIBLE\";\n stateVersion: Version;\n currentVersion: Version;\n reason: string;\n}\n\n/**\n * Type guard for MigrationError.\n */\nexport function isMigrationError(error: unknown): error is MigrationError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as MigrationError).type === \"MIGRATION_ERROR\"\n );\n}\n\n/**\n * Type guard for VersionIncompatibleError.\n */\nexport function isVersionIncompatibleError(\n error: unknown\n): error is VersionIncompatibleError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as VersionIncompatibleError).type === \"VERSION_INCOMPATIBLE\"\n );\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Migrate state from one version to another.\n *\n * @param state - The versioned state to migrate\n * @param targetVersion - The target version\n * @param migrations - Migration functions\n * @returns The migrated state or an error\n *\n * @example\n * ```typescript\n * const migrated = await migrateState(\n * { version: 1, state: oldState },\n * 3,\n * {\n * 1: (s) => transformV1ToV2(s),\n * 2: (s) => transformV2ToV3(s),\n * }\n * );\n * ```\n */\nexport async function migrateState(\n state: VersionedState,\n targetVersion: Version,\n migrations: Migrations\n): Promise<Result<VersionedState, MigrationError | VersionIncompatibleError>> {\n let currentState = state.state;\n let currentVersion = state.version;\n\n // Check if state is from the future\n if (currentVersion > targetVersion) {\n return err({\n type: \"VERSION_INCOMPATIBLE\",\n stateVersion: currentVersion,\n currentVersion: targetVersion,\n reason: \"State version is higher than current workflow version. Cannot downgrade.\",\n });\n }\n\n // Already at target version\n if (currentVersion === targetVersion) {\n return ok({ version: currentVersion, state: currentState });\n }\n\n // Apply migrations sequentially\n while (currentVersion < targetVersion) {\n const migration = migrations[currentVersion];\n\n if (!migration) {\n return err({\n type: \"VERSION_INCOMPATIBLE\",\n stateVersion: state.version,\n currentVersion: targetVersion,\n reason: `No migration found for version ${currentVersion} to ${currentVersion + 1}`,\n });\n }\n\n try {\n currentState = await migration(currentState);\n currentVersion++;\n } catch (cause) {\n return err({\n type: \"MIGRATION_ERROR\",\n fromVersion: currentVersion,\n toVersion: currentVersion + 1,\n cause,\n });\n }\n }\n\n return ok({ version: currentVersion, state: currentState });\n}\n\n/**\n * Create a versioned resume state loader.\n *\n * This wraps a state loader to automatically apply migrations\n * when loading older state versions.\n *\n * @param config - Versioning configuration\n * @returns A function that loads and migrates state\n *\n * @example\n * ```typescript\n * const loadVersionedState = createVersionedStateLoader({\n * version: 3,\n * migrations: {\n * 1: migrateV1ToV2,\n * 2: migrateV2ToV3,\n * },\n * });\n *\n * // In workflow\n * const workflow = createWorkflow(deps, {\n * resumeState: () => loadVersionedState(savedState),\n * });\n * ```\n */\nexport function createVersionedStateLoader(\n config: VersionedWorkflowConfig\n): (\n versionedState: VersionedState | null | undefined\n) => Promise<Result<ResumeState | undefined, MigrationError | VersionIncompatibleError>> {\n const { version, migrations = {}, strictVersioning = true } = config;\n\n return async (\n versionedState: VersionedState | null | undefined\n ): Promise<Result<ResumeState | undefined, MigrationError | VersionIncompatibleError>> => {\n // No saved state\n if (!versionedState) {\n return ok(undefined);\n }\n\n // Check for future version\n if (strictVersioning && versionedState.version > version) {\n return err({\n type: \"VERSION_INCOMPATIBLE\",\n stateVersion: versionedState.version,\n currentVersion: version,\n reason: \"Saved state is from a newer workflow version\",\n });\n }\n\n // Same version - no migration needed\n if (versionedState.version === version) {\n return ok(versionedState.state);\n }\n\n // Apply migrations\n const result = await migrateState(versionedState, version, migrations);\n if (!result.ok) {\n return result;\n }\n\n return ok(result.value.state);\n };\n}\n\n/**\n * Create versioned state from current resume state.\n *\n * Use this when saving state to storage.\n *\n * @param state - The current resume state\n * @param version - The current workflow version\n * @returns A versioned state object\n *\n * @example\n * ```typescript\n * const collector = createResumeStateCollector();\n * // ... run workflow ...\n *\n * const versionedState = createVersionedState(collector.getResumeState(), 2);\n * await db.saveWorkflowState(workflowId, versionedState);\n * ```\n */\nexport function createVersionedState(\n state: ResumeState,\n version: Version\n): VersionedState {\n return { version, state };\n}\n\n/**\n * Parse versioned state from JSON.\n *\n * Handles the serialization/deserialization of ResumeState with Map.\n *\n * @param json - The JSON string or parsed object\n * @returns The versioned state or null if invalid\n *\n * @example\n * ```typescript\n * const json = await db.loadWorkflowState(workflowId);\n * const versionedState = parseVersionedState(json);\n * if (versionedState) {\n * const loader = createVersionedStateLoader(config);\n * const state = await loader(versionedState);\n * }\n * ```\n */\ninterface SerializedVersionedState {\n version: number;\n state: { steps: Array<[string, ResumeStateEntry]> };\n}\n\nexport function parseVersionedState(\n json: string | SerializedVersionedState | null | undefined\n): VersionedState | null {\n if (!json) return null;\n\n try {\n const parsed: unknown = typeof json === \"string\" ? JSON.parse(json) : json;\n\n // Type guard\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n !(\"version\" in parsed) ||\n typeof (parsed as SerializedVersionedState).version !== \"number\" ||\n !(\"state\" in parsed) ||\n !(parsed as SerializedVersionedState).state ||\n !Array.isArray((parsed as SerializedVersionedState).state.steps)\n ) {\n return null;\n }\n\n const typedParsed = parsed as SerializedVersionedState;\n\n // Convert steps array back to Map\n const steps = new Map<string, ResumeStateEntry>(typedParsed.state.steps);\n\n return {\n version: typedParsed.version,\n state: { steps },\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Serialize versioned state to JSON.\n *\n * Converts the Map to an array for JSON serialization.\n *\n * @param state - The versioned state\n * @returns JSON string\n *\n * @example\n * ```typescript\n * const json = stringifyVersionedState(versionedState);\n * await db.saveWorkflowState(workflowId, json);\n * ```\n */\nexport function stringifyVersionedState(state: VersionedState): string {\n return JSON.stringify({\n version: state.version,\n state: {\n steps: Array.from(state.state.steps.entries()),\n },\n });\n}\n\n// =============================================================================\n// Migration Helpers\n// =============================================================================\n\n/**\n * Create a migration that renames step keys.\n *\n * @param renames - Map of old key to new key\n * @returns A migration function\n *\n * @example\n * ```typescript\n * const migrations = {\n * 1: createKeyRenameMigration({\n * 'user:fetch': 'user:load',\n * 'order:create': 'order:submit',\n * }),\n * };\n * ```\n */\nexport function createKeyRenameMigration(\n renames: Record<string, string>\n): MigrationFn {\n return (state: ResumeState): ResumeState => {\n const newSteps = new Map<string, ResumeStateEntry>();\n\n for (const [key, entry] of state.steps) {\n const newKey = renames[key] ?? key;\n newSteps.set(newKey, entry);\n }\n\n return { steps: newSteps };\n };\n}\n\n/**\n * Create a migration that removes specific step keys.\n *\n * @param keysToRemove - Array of keys to remove\n * @returns A migration function\n *\n * @example\n * ```typescript\n * const migrations = {\n * 1: createKeyRemoveMigration(['deprecated:step', 'old:cache']),\n * };\n * ```\n */\nexport function createKeyRemoveMigration(keysToRemove: string[]): MigrationFn {\n const keysSet = new Set(keysToRemove);\n return (state: ResumeState): ResumeState => {\n const newSteps = new Map<string, ResumeStateEntry>();\n\n for (const [key, entry] of state.steps) {\n if (!keysSet.has(key)) {\n newSteps.set(key, entry);\n }\n }\n\n return { steps: newSteps };\n };\n}\n\n/**\n * Create a migration that transforms step values.\n *\n * @param transforms - Map of key to transform function\n * @returns A migration function\n *\n * @example\n * ```typescript\n * const migrations = {\n * 1: createValueTransformMigration({\n * 'user:fetch': (entry) => ({\n * ...entry,\n * result: entry.result.ok\n * ? ok({ ...entry.result.value, newField: 'default' })\n * : entry.result,\n * }),\n * }),\n * };\n * ```\n */\nexport function createValueTransformMigration(\n transforms: Record<string, (entry: ResumeStateEntry) => ResumeStateEntry>\n): MigrationFn {\n return (state: ResumeState): ResumeState => {\n const newSteps = new Map<string, ResumeStateEntry>();\n\n for (const [key, entry] of state.steps) {\n const transform = transforms[key];\n newSteps.set(key, transform ? transform(entry) : entry);\n }\n\n return { steps: newSteps };\n };\n}\n\n/**\n * Compose multiple migrations into a single migration.\n *\n * @param migrations - Array of migration functions\n * @returns A single migration function that applies all migrations in order\n *\n * @example\n * ```typescript\n * const migrations = {\n * 1: composeMigrations([\n * createKeyRenameMigration({ 'old': 'new' }),\n * createKeyRemoveMigration(['deprecated']),\n * ]),\n * };\n * ```\n */\nexport function composeMigrations(migrations: MigrationFn[]): MigrationFn {\n return async (state: ResumeState): Promise<ResumeState> => {\n let currentState = state;\n for (const migration of migrations) {\n currentState = await migration(currentState);\n }\n return currentState;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACqMO,SAAS,GAAM,OAAiB;AACrC,SAAO,EAAE,IAAI,MAAe,MAAM;AACpC;AAsBO,SAAS,IAAoB,OAAU,SAAoC;AAChF,QAAM,QAAQ,SAAS;AACvB,SAAO,EAAE,IAAI,OAAgB,OAAO,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC,EAAG;AAChF;;;AClMO,SAAS,cAAc,GAA8B;AAC1D,SACE,OAAO,MAAM,YACb,MAAM,QACN,WAAW,KACV,EAAkB,iBAAiB;AAExC;;;AC4EO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAC7C,YACE,SACgB,SAAmB,CAAC,GACpC;AACA,UAAM,OAAO;AAFG;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EAJkB;AAKpB;AAKO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAC/C,YACE,SACgB,cACA,SAOhB;AACA,UAAM,OAAO;AATG;AACA;AAShB,SAAK,OAAO;AAAA,EACd;AAAA,EAXkB;AAAA,EACA;AAWpB;AAKO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAC7C,YACE,SACgB,QACA,eAChB;AACA,UAAM,OAAO;AAHG;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAAA,EACA;AAKpB;AAcO,SAAS,0BAA0B,KAAuC;AAC/E,SACE,OAAO,QAAQ,YACf,QAAQ,QACR,mBAAmB,OAClB,IAAmC,kBAAkB,KACtD,WAAW,OACX,OAAQ,IAA2B,UAAU,YAC5C,IAA2B,UAAU,QACtC,eAAe,OACf,OAAQ,IAA+B,cAAc;AAEzD;AAKO,IAAM,qBAAqB;AAM3B,SAAS,iBAAiB,KAAgG;AAC/H,QAAM,SAAmB,CAAC;AAE1B,MAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,WAAO,EAAE,OAAO,OAAO,QAAQ,CAAC,4BAA4B,EAAE;AAAA,EAChE;AAEA,QAAM,WAAW;AAGjB,MAAI,EAAE,mBAAmB,WAAW;AAClC,WAAO,KAAK,uCAAuC;AAAA,EACrD,WAAW,SAAS,kBAAkB,GAAG;AACvC,WAAO,KAAK,0CAA0C,SAAS,aAAa,EAAE;AAAA,EAChF;AAGA,MAAI,EAAE,WAAW,WAAW;AAC1B,WAAO,KAAK,+BAA+B;AAAA,EAC7C,WAAW,OAAO,SAAS,UAAU,YAAY,SAAS,UAAU,MAAM;AACxE,WAAO,KAAK,yBAAyB;AAAA,EACvC,OAAO;AAEL,UAAM,QAAQ,SAAS;AACvB,eAAW,CAAC,QAAQ,UAAU,KAAK,OAAO,QAAQ,KAAK,GAAG;AACxD,UAAI,OAAO,eAAe,YAAY,eAAe,MAAM;AACzD,eAAO,KAAK,UAAU,MAAM,sBAAsB;AAClD;AAAA,MACF;AAEA,YAAM,OAAO;AACb,UAAI,EAAE,QAAQ,OAAO;AACnB,eAAO,KAAK,UAAU,MAAM,+BAA+B;AAAA,MAC7D,WAAW,OAAO,KAAK,OAAO,WAAW;AACvC,eAAO,KAAK,UAAU,MAAM,yBAAyB;AAAA,MACvD,WAAW,KAAK,OAAO,OAAO;AAC5B,YAAI,EAAE,WAAW,OAAO;AACtB,iBAAO,KAAK,UAAU,MAAM,4CAA4C;AAAA,QAC1E;AACA,YAAI,EAAE,WAAW,OAAO;AACtB,iBAAO,KAAK,UAAU,MAAM,4CAA4C;AAAA,QAC1E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,EAAE,eAAe,WAAW;AAC9B,WAAO,KAAK,mCAAmC;AAAA,EACjD,WAAW,OAAO,SAAS,cAAc,YAAY,SAAS,cAAc,MAAM;AAChF,WAAO,KAAK,6BAA6B;AAAA,EAC3C,OAAO;AACL,UAAM,YAAY,SAAS;AAC3B,QAAI,EAAE,YAAY,YAAY;AAC5B,aAAO,KAAK,0CAA0C;AAAA,IACxD,WAAW,CAAC,CAAC,WAAW,aAAa,QAAQ,EAAE,SAAS,UAAU,MAAgB,GAAG;AACnF,aAAO,KAAK,6DAA6D;AAAA,IAC3E;AACA,QAAI,EAAE,iBAAiB,YAAY;AACjC,aAAO,KAAK,+CAA+C;AAAA,IAC7D,WAAW,OAAO,UAAU,gBAAgB,UAAU;AACpD,aAAO,KAAK,wDAAwD;AAAA,IACtE;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,GAAG;AACrB,WAAO,EAAE,OAAO,OAAO,OAAO;AAAA,EAChC;AAEA,SAAO,EAAE,OAAO,MAAM,UAAU,IAAwB;AAC1D;AAQO,SAAS,oBAAoB,KAAgC;AAClE,QAAM,SAAS,iBAAiB,GAAG;AACnC,MAAI,CAAC,OAAO,OAAO;AACjB,UAAM,IAAI,oBAAoB,4BAA4B,OAAO,OAAO,CAAC,CAAC,IAAI,OAAO,MAAM;AAAA,EAC7F;AACA,SAAO,OAAO;AAChB;AAUO,SAAS,eAAe,MAAwB,OAA2C;AAEhG,QAAM,cAAc,uBAAO,OAAO,IAAI;AAGtC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,KAAK,GAAG;AACrD,QAAI,OAAO,UAAU,eAAe,KAAK,KAAK,OAAO,GAAG,GAAG;AACzD,kBAAY,GAAG,IAAI;AAAA,IACrB;AAAA,EACF;AAGA,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,KAAK,GAAG;AACtD,QAAI,OAAO,UAAU,eAAe,KAAK,MAAM,OAAO,GAAG,GAAG;AAC1D,kBAAY,GAAG,IAAI;AAAA,IACrB;AAAA,EACF;AAGA,QAAM,iBAAiB,KAAK,YAAY,MAAM,WAC1C,EAAE,GAAG,KAAK,UAAU,GAAG,MAAM,SAAS,IACtC;AAIJ,QAAM,gBAAgB,KAAK,YAAY,CAAC,GAAG;AAAA,IACzC,CAAC,MAAM,CAAC,OAAO,UAAU,eAAe,KAAK,MAAM,OAAO,EAAE,MAAM;AAAA,EACpE;AACA,QAAM,iBAAiB,CAAC,GAAG,cAAc,GAAI,MAAM,YAAY,CAAC,CAAE;AAElE,SAAO;AAAA,IACL,eAAe;AAAA,IACf,OAAO;AAAA,IACP,WAAW,EAAE,GAAG,MAAM,UAAU;AAAA,IAChC,UAAU;AAAA,IACV,UAAU,eAAe,SAAS,IAAI,iBAAiB;AAAA,EACzD;AACF;AASA,IAAM,mCAAmC;AAMlC,SAAS,eAAe,OAA+B;AAC5D,QAAM,aAA8B;AAAA,IAClC,MAAM;AAAA,IACN,MAAM,MAAM;AAAA,IACZ,SAAS,MAAM;AAAA,EACjB;AAEA,MAAI,MAAM,OAAO;AACf,eAAW,QAAQ,MAAM;AAAA,EAC3B;AAGA,QAAM,iBAAiB;AACvB,MAAI,eAAe,UAAU,QAAW;AACtC,QAAI,eAAe,iBAAiB,OAAO;AACzC,iBAAW,QAAQ,eAAe,eAAe,KAAK;AAAA,IACxD,OAAO;AAEL,iBAAW,QAAQ,gBAAgB,eAAe,KAAK;AAAA,IACzD;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,gBAAgB,OAAiC;AAE/D,MAAI;AACJ,MAAI,YAAY;AAEhB,MAAI;AACF,2BAAuB,OAAO,KAAK;AACnC,QAAI,qBAAqB,SAAS,kCAAkC;AAClE,6BAAuB,qBAAqB,MAAM,GAAG,gCAAgC;AACrF,kBAAY;AAAA,IACd;AAAA,EACF,QAAQ;AACN,2BAAuB;AAAA,EACzB;AAGA,QAAM,eAAe,UAAU,OAC3B,SACA,OAAO,UAAU,WACd,MAAM,aAAa,QAAQ,WAC5B,OAAO;AAGb,MAAI;AACJ,MAAI;AACF,UAAM,aAAa,KAAK,UAAU,KAAK;AACvC,QAAI,eAAe,QAAW;AAC5B,kBAAY,KAAK,MAAM,UAAU;AAAA,IACnC;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,QAAM,SAA0B;AAAA,IAC9B,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF;AAEA,MAAI,cAAc,QAAW;AAC3B,WAAO,QAAQ;AAAA,EACjB;AAEA,MAAI,WAAW;AACb,WAAO,YAAY;AAAA,EACrB;AAEA,SAAO;AACT;AAKO,SAAS,oBAAoB,YAAsC;AACxE,MAAI,WAAW,SAAS,SAAS;AAC/B,UAAM,QAAQ,IAAI,MAAM,WAAW,OAAO;AAC1C,UAAM,OAAO,WAAW;AACxB,QAAI,WAAW,OAAO;AACpB,YAAM,QAAQ,WAAW;AAAA,IAC3B;AACA,QAAI,WAAW,OAAO;AACpB,MAAC,MAAqC,QAAQ,oBAAoB,WAAW,KAAK;AAAA,IACpF;AACA,WAAO;AAAA,EACT;AAIA,SAAO,WAAW,UAAU,SAAY,WAAW,QAAQ,WAAW;AACxE;AA+EO,SAAS,kBAAkB,UAA8B,CAAC,GAAc;AAC7E,QAAM,EAAE,SAAS,IAAI,IAAI;AACzB,QAAM,QAAQ,oBAAI,IAIf;AAEH,QAAM,YAAY,CAAC,UAA6D;AAC9E,UAAM,eAAe,MAAM,YAAY;AACvC,QAAI,CAAC,aAAc,QAAO;AAC1B,WAAO,KAAK,IAAI,IAAI,MAAM,YAAY;AAAA,EACxC;AAEA,QAAM,eAAe,MAAY;AAC/B,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO;AAChC,UAAI,UAAU,KAAK,GAAG;AACpB,cAAM,OAAO,GAAG;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAAc,MAAY;AAC9B,QAAI,CAAC,WAAW,MAAM,OAAO,QAAS;AAGtC,QAAI;AACJ,QAAI,aAAa;AAEjB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO;AAChC,UAAI,MAAM,YAAY,YAAY;AAChC,qBAAa,MAAM;AACnB,oBAAY;AAAA,MACd;AAAA,IACF;AAEA,QAAI,WAAW;AACb,YAAM,OAAO,SAAS;AAAA,IACxB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,KAA4D;AAC9D,mBAAa;AACb,YAAM,QAAQ,MAAM,IAAI,GAAG;AAC3B,UAAI,CAAC,MAAO,QAAO;AACnB,UAAI,UAAU,KAAK,GAAG;AACpB,cAAM,OAAO,GAAG;AAChB,eAAO;AAAA,MACT;AACA,aAAO,MAAM;AAAA,IACf;AAAA,IAEA,IAAI,KAAa,QAA2CA,UAAkC;AAC5F,mBAAa;AACb,kBAAY;AACZ,YAAM,IAAI,KAAK,EAAE,QAAQ,WAAW,KAAK,IAAI,GAAG,UAAUA,UAAS,IAAI,CAAC;AAAA,IAC1E;AAAA,IAEA,IAAI,KAAsB;AACxB,mBAAa;AACb,YAAM,QAAQ,MAAM,IAAI,GAAG;AAC3B,UAAI,CAAC,MAAO,QAAO;AACnB,UAAI,UAAU,KAAK,GAAG;AACpB,cAAM,OAAO,GAAG;AAChB,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT;AAAA,IAEA,OAAO,KAAsB;AAC3B,aAAO,MAAM,OAAO,GAAG;AAAA,IACzB;AAAA,IAEA,QAAc;AACZ,YAAM,MAAM;AAAA,IACd;AAAA,EACF;AACF;;;AC3jBO,SAAS,mBAAmB,OAA2C;AAC5E,SACE,OAAO,UAAU,YACjB,UAAU,QACT,MAA2B,iBAAiB;AAEjD;AAMO,SAAS,kBACd,OACA,MACA,eAC6B;AAC7B,SAAO,IAAI,OAAO;AAAA,IAChB,OAAO,EAAE,cAAc,MAAM,eAAe,KAAK;AAAA,EACnD,CAAC;AACH;;;ACZO,SAAS,qBAAqB,OAA2C;AAC9E,QAAM,QAAgC,CAAC;AACvC,aAAW,CAAC,KAAK,KAAK,KAAK,MAAM,OAAO;AACtC,UAAM,aAAa,kBAAkB,KAAK;AAC1C,UAAM,KAAK,CAAC,KAAK,UAAU,CAAC;AAAA,EAC9B;AACA,SAAO,EAAE,MAAM,eAAe,MAAM;AACtC;AAKO,SAAS,wBAAwB,GAAwC;AAC9E,SACE,OAAO,MAAM,YACb,MAAM,QACL,EAA4B,SAAS,iBACtC,MAAM,QAAS,EAA4B,KAAK;AAEpD;AAYO,SAAS,uBAAuB,KAAyC;AAC9E,QAAM,QAAQ,oBAAI,IAA8B;AAChD,aAAW,CAAC,KAAK,UAAU,KAAK,IAAI,OAAO;AACzC,UAAM,IAAI,KAAK,kBAAkB,UAAU,CAAC;AAAA,EAC9C;AACA,SAAO,EAAE,MAAM;AACjB;AAEA,SAAS,kBAAkB,OAAqC;AAC9D,QAAM,EAAE,QAAQ,KAAK,IAAI;AACzB,MAAI,OAAO,IAAI;AACb,WAAO,EAAE,IAAI,MAAM,OAAO,OAAO,MAAmB;AAAA,EACtD;AACA,QAAM,mBAAmB,mBAAmB,OAAO,KAAK,IACpD,OAAO,MAAM,gBACb,OAAO;AACX,QAAM,kBACJ,4BAA4B,QACxB,eAAe,gBAAgB,IAC/B,gBAAgB,gBAAgB;AACtC,QAAM,SACJ,MAAM,WAAW,UAAU,UAAU;AACvC,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,OAAO,OAAO;AAAA,IACd,OAAO;AAAA,IACP,MAAM,EAAE,OAAO;AAAA,EACjB;AACF;AAEA,SAAS,kBAAkB,IAAkC;AAC3D,MAAI,GAAG,IAAI;AACT,WAAO,EAAE,QAAQ,GAAG,GAAG,KAAK,EAAE;AAAA,EAChC;AACA,QAAM,oBAAoB,oBAAoB,GAAG,KAAK;AACtD,QAAM,OACJ,GAAG,MAAM,WAAW,UAChB,EAAE,QAAQ,SAAkB,QAAQ,kBAAkB,IACtD,EAAE,QAAQ,UAAmB,aAAa,kBAAkB;AAClE,QAAM,cAAc,kBAAkB,GAAG,OAAO,MAAM,iBAAiB;AACvE,SAAO,EAAE,QAAQ,aAAa,KAAK;AACrC;;;ACtEO,SAAS,cAAc,QAAkD;AAC9E,MAAI,WAAW,KAAM,QAAO;AAC5B,MAAI,cAAc,MAAM,EAAG,QAAO;AAClC,SAAO;AACT;;;ACgEO,SAAS,iBAAiB,OAAyC;AACxE,SACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAyB,SAAS;AAEvC;AAKO,SAAS,2BACd,OACmC;AACnC,SACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAmC,SAAS;AAEjD;AA0BA,eAAsB,aACpB,OACA,eACA,YAC4E;AAC5E,MAAI,eAAe,MAAM;AACzB,MAAI,iBAAiB,MAAM;AAG3B,MAAI,iBAAiB,eAAe;AAClC,WAAO,IAAI;AAAA,MACT,MAAM;AAAA,MACN,cAAc;AAAA,MACd,gBAAgB;AAAA,MAChB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAGA,MAAI,mBAAmB,eAAe;AACpC,WAAO,GAAG,EAAE,SAAS,gBAAgB,OAAO,aAAa,CAAC;AAAA,EAC5D;AAGA,SAAO,iBAAiB,eAAe;AACrC,UAAM,YAAY,WAAW,cAAc;AAE3C,QAAI,CAAC,WAAW;AACd,aAAO,IAAI;AAAA,QACT,MAAM;AAAA,QACN,cAAc,MAAM;AAAA,QACpB,gBAAgB;AAAA,QAChB,QAAQ,kCAAkC,cAAc,OAAO,iBAAiB,CAAC;AAAA,MACnF,CAAC;AAAA,IACH;AAEA,QAAI;AACF,qBAAe,MAAM,UAAU,YAAY;AAC3C;AAAA,IACF,SAAS,OAAO;AACd,aAAO,IAAI;AAAA,QACT,MAAM;AAAA,QACN,aAAa;AAAA,QACb,WAAW,iBAAiB;AAAA,QAC5B;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO,GAAG,EAAE,SAAS,gBAAgB,OAAO,aAAa,CAAC;AAC5D;AA2BO,SAAS,2BACd,QAGuF;AACvF,QAAM,EAAE,SAAS,aAAa,CAAC,GAAG,mBAAmB,KAAK,IAAI;AAE9D,SAAO,OACL,mBACwF;AAExF,QAAI,CAAC,gBAAgB;AACnB,aAAO,GAAG,MAAS;AAAA,IACrB;AAGA,QAAI,oBAAoB,eAAe,UAAU,SAAS;AACxD,aAAO,IAAI;AAAA,QACT,MAAM;AAAA,QACN,cAAc,eAAe;AAAA,QAC7B,gBAAgB;AAAA,QAChB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAGA,QAAI,eAAe,YAAY,SAAS;AACtC,aAAO,GAAG,eAAe,KAAK;AAAA,IAChC;AAGA,UAAM,SAAS,MAAM,aAAa,gBAAgB,SAAS,UAAU;AACrE,QAAI,CAAC,OAAO,IAAI;AACd,aAAO;AAAA,IACT;AAEA,WAAO,GAAG,OAAO,MAAM,KAAK;AAAA,EAC9B;AACF;AAoBO,SAAS,qBACd,OACA,SACgB;AAChB,SAAO,EAAE,SAAS,MAAM;AAC1B;AAyBO,SAAS,oBACd,MACuB;AACvB,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI;AACF,UAAM,SAAkB,OAAO,SAAS,WAAW,KAAK,MAAM,IAAI,IAAI;AAGtE,QACE,OAAO,WAAW,YAClB,WAAW,QACX,EAAE,aAAa,WACf,OAAQ,OAAoC,YAAY,YACxD,EAAE,WAAW,WACb,CAAE,OAAoC,SACtC,CAAC,MAAM,QAAS,OAAoC,MAAM,KAAK,GAC/D;AACA,aAAO;AAAA,IACT;AAEA,UAAM,cAAc;AAGpB,UAAM,QAAQ,IAAI,IAA8B,YAAY,MAAM,KAAK;AAEvE,WAAO;AAAA,MACL,SAAS,YAAY;AAAA,MACrB,OAAO,EAAE,MAAM;AAAA,IACjB;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAgBO,SAAS,wBAAwB,OAA+B;AACrE,SAAO,KAAK,UAAU;AAAA,IACpB,SAAS,MAAM;AAAA,IACf,OAAO;AAAA,MACL,OAAO,MAAM,KAAK,MAAM,MAAM,MAAM,QAAQ,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AACH;AAsBO,SAAS,yBACd,SACa;AACb,SAAO,CAAC,UAAoC;AAC1C,UAAM,WAAW,oBAAI,IAA8B;AAEnD,eAAW,CAAC,KAAK,KAAK,KAAK,MAAM,OAAO;AACtC,YAAM,SAAS,QAAQ,GAAG,KAAK;AAC/B,eAAS,IAAI,QAAQ,KAAK;AAAA,IAC5B;AAEA,WAAO,EAAE,OAAO,SAAS;AAAA,EAC3B;AACF;AAeO,SAAS,yBAAyB,cAAqC;AAC5E,QAAM,UAAU,IAAI,IAAI,YAAY;AACpC,SAAO,CAAC,UAAoC;AAC1C,UAAM,WAAW,oBAAI,IAA8B;AAEnD,eAAW,CAAC,KAAK,KAAK,KAAK,MAAM,OAAO;AACtC,UAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;AACrB,iBAAS,IAAI,KAAK,KAAK;AAAA,MACzB;AAAA,IACF;AAEA,WAAO,EAAE,OAAO,SAAS;AAAA,EAC3B;AACF;AAsBO,SAAS,8BACd,YACa;AACb,SAAO,CAAC,UAAoC;AAC1C,UAAM,WAAW,oBAAI,IAA8B;AAEnD,eAAW,CAAC,KAAK,KAAK,KAAK,MAAM,OAAO;AACtC,YAAM,YAAY,WAAW,GAAG;AAChC,eAAS,IAAI,KAAK,YAAY,UAAU,KAAK,IAAI,KAAK;AAAA,IACxD;AAEA,WAAO,EAAE,OAAO,SAAS;AAAA,EAC3B;AACF;AAkBO,SAAS,kBAAkB,YAAwC;AACxE,SAAO,OAAO,UAA6C;AACzD,QAAI,eAAe;AACnB,eAAW,aAAa,YAAY;AAClC,qBAAe,MAAM,UAAU,YAAY;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AACF;","names":["options"]}
|