runsheet 0.4.0 → 0.5.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/define-step.ts","../src/errors.ts","../src/middleware.ts","../src/when.ts","../src/pipeline.ts","../src/parallel.ts","../src/builder.ts"],"sourcesContent":["export { defineStep } from './define-step.js';\nexport { RunsheetError } from './errors.js';\nexport type { RunsheetErrorCode } from './errors.js';\nexport { buildPipeline } from './pipeline.js';\nexport type { Pipeline, PipelineConfig } from './pipeline.js';\nexport { when } from './when.js';\nexport type { ConditionalStep } from './when.js';\nexport { parallel } from './parallel.js';\nexport type { StepMiddleware, StepInfo, StepExecutor } from './middleware.js';\nexport { createPipeline } from './builder.js';\nexport type { PipelineBuilder } from './builder.js';\n\nexport type {\n Step,\n TypedStep,\n StepConfig,\n StepContext,\n StepOutput,\n ExtractRequires,\n ExtractProvides,\n RetryPolicy,\n PipelineResult,\n PipelineSuccess,\n PipelineFailure,\n PipelineExecutionMeta,\n RollbackReport,\n RollbackFailure,\n} from './types.js';\n\n// Re-export Result types so consumers never need to import composable-functions\nexport type { Result, Success, Failure } from 'composable-functions';\n","import { composable } from 'composable-functions';\nimport type { Result } from 'composable-functions';\nimport type { RetryPolicy, StepConfig, StepContext, StepOutput, Step, TypedStep } from './types.js';\nimport { RunsheetError } from './errors.js';\n\n// ---------------------------------------------------------------------------\n// Timeout and retry wrappers\n// ---------------------------------------------------------------------------\n\nfunction withTimeout(\n run: (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>>,\n stepName: string,\n ms: number,\n): (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>> {\n return async (ctx) => {\n let timer: ReturnType<typeof setTimeout>;\n const timeout = new Promise<Result<StepOutput>>((resolve) => {\n timer = setTimeout(() => {\n const error = new RunsheetError('TIMEOUT', `${stepName} timed out after ${ms}ms`);\n resolve({ success: false as const, errors: [error] });\n }, ms);\n });\n try {\n return await Promise.race([run(ctx), timeout]);\n } finally {\n clearTimeout(timer!);\n }\n };\n}\n\nfunction computeDelay(policy: RetryPolicy, attempt: number): number {\n const base = policy.delay ?? 0;\n if (base === 0) return 0;\n const strategy = policy.backoff ?? 'linear';\n return strategy === 'exponential' ? base * 2 ** (attempt - 1) : base * attempt;\n}\n\nfunction withRetry(\n run: (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>>,\n stepName: string,\n policy: RetryPolicy,\n): (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>> {\n return async (ctx) => {\n let lastResult = await run(ctx);\n if (lastResult.success) return lastResult;\n\n for (let attempt = 1; attempt <= policy.count; attempt++) {\n // Check if the failure is retryable\n if (policy.retryIf && !policy.retryIf(lastResult.errors)) return lastResult;\n\n const delay = computeDelay(policy, attempt);\n if (delay > 0) await new Promise((r) => setTimeout(r, delay));\n lastResult = await run(ctx);\n if (lastResult.success) return lastResult;\n }\n\n // Wrap the last failure with RETRY_EXHAUSTED\n const error = new RunsheetError(\n 'RETRY_EXHAUSTED',\n `${stepName} failed after ${policy.count} retries`,\n );\n return { success: false as const, errors: [...lastResult.errors, error] };\n };\n}\n\nfunction wrapWithTimeoutAndRetry(\n run: (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>>,\n stepName: string,\n timeout: number | undefined,\n retry: RetryPolicy | undefined,\n): (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>> {\n // Timeout wraps the individual run call, retry wraps the timeout+run combo\n let wrapped = run;\n if (timeout !== undefined) wrapped = withTimeout(wrapped, stepName, timeout);\n if (retry !== undefined) wrapped = withRetry(wrapped, stepName, retry);\n return wrapped;\n}\n\n/**\n * Define a pipeline step.\n *\n * Returns a frozen {@link TypedStep} with concrete types for `run`,\n * `rollback`, `requires`, and `provides`. The `run` function can be\n * sync or async — both are supported.\n *\n * **With schemas** (runtime validation + type inference):\n * ```ts\n * const charge = defineStep({\n * name: 'charge',\n * requires: z.object({ amount: z.number() }),\n * provides: z.object({ chargeId: z.string() }),\n * run: async (ctx) => ({ chargeId: 'ch_123' }),\n * });\n * ```\n *\n * **With generics only** (no runtime validation):\n * ```ts\n * const log = defineStep<{ order: Order }, { loggedAt: Date }>({\n * name: 'log',\n * run: async (ctx) => ({ loggedAt: new Date() }),\n * });\n * ```\n *\n * **Invariants:**\n * - The returned step object is always frozen (immutable).\n * - The `run` function is wrapped with `composable()` from\n * composable-functions, which catches thrown errors and produces\n * `Result` values. Step authors should throw to signal failure.\n * - This is the single type-erasure cast point in the library.\n *\n * @typeParam Requires - The context shape this step reads from.\n * @typeParam Provides - The output shape this step produces.\n * @param config - The step configuration. See {@link StepConfig}.\n * @returns A frozen {@link TypedStep} ready for use in pipelines.\n */\nexport function defineStep<Requires extends StepContext, Provides extends StepContext>(\n config: StepConfig<Requires, Provides>,\n): TypedStep<Requires, Provides> {\n const baseRun = composable(config.run) as unknown as (\n ctx: Readonly<StepContext>,\n ) => Promise<Result<StepOutput>>;\n const wrappedRun = wrapWithTimeoutAndRetry(baseRun, config.name, config.timeout, config.retry);\n\n // The cast below is the single point where typed step functions are erased\n // to the runtime Step representation. This is safe because:\n // 1. Schema validation at step boundaries (requires/provides) enforces\n // correct types at runtime before and after each step executes.\n // 2. The pipeline accumulates context immutably, so the runtime object\n // structurally matches what the typed function expects.\n // 3. The phantom brands on TypedStep preserve compile-time type tracking\n // through the builder API without affecting runtime behavior.\n return Object.freeze({\n name: config.name,\n requires: config.requires ?? undefined,\n provides: config.provides ?? undefined,\n run: wrappedRun as unknown as Step['run'],\n rollback: config.rollback\n ? async (ctx: Readonly<StepContext>, output: Readonly<StepContext>) => {\n await (config.rollback as NonNullable<typeof config.rollback>)(\n ctx as Readonly<Requires>,\n output as Readonly<Provides>,\n );\n }\n : undefined,\n retry: config.retry ?? undefined,\n timeout: config.timeout ?? undefined,\n }) as TypedStep<Requires, Provides>;\n}\n","/**\n * Error codes for errors produced by the runsheet library itself.\n *\n * Use these to distinguish library errors from application errors\n * in a pipeline's `errors` array:\n *\n * ```ts\n * if (!result.success) {\n * for (const error of result.errors) {\n * if (error instanceof RunsheetError) {\n * console.log(error.code); // 'REQUIRES_VALIDATION', etc.\n * }\n * }\n * }\n * ```\n */\nexport type RunsheetErrorCode =\n | 'REQUIRES_VALIDATION'\n | 'PROVIDES_VALIDATION'\n | 'ARGS_VALIDATION'\n | 'PREDICATE'\n | 'TIMEOUT'\n | 'RETRY_EXHAUSTED'\n | 'STRICT_OVERLAP';\n\n/**\n * Base error class for all errors produced by the runsheet library.\n *\n * Application errors (thrown by step `run` or `rollback` functions)\n * are never wrapped in `RunsheetError` — they pass through as-is.\n * If you see a `RunsheetError` in a result's `errors` array, the\n * library itself produced it.\n *\n * Use `instanceof RunsheetError` to distinguish library errors from\n * application errors, and the `code` property to identify the\n * specific failure.\n */\nexport class RunsheetError extends Error {\n /** Discriminant code identifying the type of library error. */\n readonly code: RunsheetErrorCode;\n\n /**\n * @param code - The error code.\n * @param message - A human-readable description of the failure.\n */\n constructor(code: RunsheetErrorCode, message: string) {\n super(message);\n this.name = 'RunsheetError';\n this.code = code;\n }\n}\n","import type { Result } from 'composable-functions';\nimport type { Step, StepContext, StepOutput } from './types.js';\n\n/**\n * Metadata about the step being executed, passed to middleware.\n *\n * This is a read-only view of the step's public configuration.\n * Middleware can use it for logging, metrics, or conditional behavior.\n */\nexport type StepInfo = {\n /** The step's name. */\n readonly name: string;\n /** The step's requires schema, or `undefined` if not provided. */\n readonly requires: Step['requires'];\n /** The step's provides schema, or `undefined` if not provided. */\n readonly provides: Step['provides'];\n};\n\n/**\n * A function that executes a step (or the next middleware in the chain).\n *\n * Receives the frozen accumulated context and returns a `Result` — either\n * `{ success: true, data }` or `{ success: false, errors }`.\n */\nexport type StepExecutor = (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>>;\n\n/**\n * Middleware that wraps the entire step lifecycle, including schema\n * validation.\n *\n * A middleware receives the step metadata and a `next` function, and\n * returns a new executor. Call `next(ctx)` to proceed to the next\n * middleware (or the actual step execution). You can:\n *\n * - **Observe**: read the context or result for logging/metrics.\n * - **Transform**: modify the result before returning it.\n * - **Short-circuit**: return a `Result` without calling `next`.\n *\n * If a middleware throws, the pipeline catches it and treats it as a\n * step failure (triggering rollback for previously completed steps).\n *\n * @example\n * ```ts\n * const timing: StepMiddleware = (step, next) => async (ctx) => {\n * const start = performance.now();\n * const result = await next(ctx);\n * console.log(`${step.name}: ${performance.now() - start}ms`);\n * return result;\n * };\n * ```\n *\n * @param step - Metadata about the step being wrapped.\n * @param next - The next executor in the chain. Call it to continue.\n * @returns A new executor that wraps `next`.\n */\nexport type StepMiddleware = (step: StepInfo, next: StepExecutor) => StepExecutor;\n\n/**\n * Compose an array of middlewares around a step executor.\n *\n * First middleware in the array is the outermost wrapper (executes\n * first on the way in, last on the way out).\n *\n * @param middlewares - Middleware functions, in declaration order.\n * @param step - Metadata about the step being wrapped.\n * @param executor - The base step executor to wrap.\n * @returns A composed executor with all middleware applied.\n */\nexport function applyMiddleware(\n middlewares: readonly StepMiddleware[],\n step: StepInfo,\n executor: StepExecutor,\n): StepExecutor {\n return middlewares.reduceRight<StepExecutor>((next, mw) => mw(step, next), executor);\n}\n","import type { Step, StepContext, TypedStep } from './types.js';\n\n/**\n * A step with a conditional predicate attached.\n *\n * The pipeline engine checks for the `predicate` property to decide\n * whether to execute or skip the step. Use the {@link when} function\n * to create conditional steps — don't construct this type directly.\n */\nexport type ConditionalStep = Step & {\n /** Returns `true` to execute the step, `false` to skip it. */\n readonly predicate: (ctx: Readonly<StepContext>) => boolean;\n};\n\n/**\n * Wrap a step with a guard predicate.\n *\n * The step only executes when the predicate returns `true`. When\n * skipped:\n * - No context snapshot is taken.\n * - No rollback entry is created.\n * - The step name is recorded in `result.meta.stepsSkipped`.\n *\n * If the predicate throws, the pipeline treats it as a step failure\n * and triggers rollback for any previously completed steps.\n *\n * @example\n * ```ts\n * const steps = [\n * validateOrder,\n * when((ctx) => ctx.order.amount > 100, notifyManager),\n * sendConfirmation,\n * ];\n * ```\n *\n * @typeParam Requires - Inferred from the step's requires type.\n * @typeParam Provides - Inferred from the step's provides type.\n * @param predicate - Guard function. Receives the current accumulated\n * context (frozen). Return `true` to execute, `false` to skip.\n * @param step - The step to conditionally execute.\n * @returns A frozen {@link TypedStep} with the predicate attached.\n */\nexport function when<Requires extends StepContext, Provides extends StepContext>(\n predicate: (ctx: Readonly<Requires>) => boolean,\n step: TypedStep<Requires, Provides>,\n): TypedStep<Requires, Provides> {\n return Object.freeze({\n ...step,\n predicate: predicate as ConditionalStep['predicate'],\n }) as TypedStep<Requires, Provides>;\n}\n\n/** Type guard for conditional steps. */\nexport function isConditionalStep(step: Step): step is ConditionalStep {\n return 'predicate' in step && typeof (step as StepContext)['predicate'] === 'function';\n}\n","import type { ParserSchema, Result } from 'composable-functions';\nimport type {\n ExtractProvides,\n PipelineFailure,\n PipelineResult,\n PipelineSuccess,\n RollbackFailure,\n RollbackReport,\n Step,\n StepContext,\n StepOutput,\n UnionToIntersection,\n} from './types.js';\nimport type { StepMiddleware } from './middleware.js';\nimport type { RunsheetErrorCode } from './errors.js';\nimport { RunsheetError } from './errors.js';\nimport { applyMiddleware } from './middleware.js';\nimport { isConditionalStep } from './when.js';\n\n// ---------------------------------------------------------------------------\n// Pipeline configuration\n// ---------------------------------------------------------------------------\n\n/**\n * Internal configuration shape for the pipeline engine.\n *\n * Users typically don't construct this directly — use `buildPipeline()`\n * or `createPipeline()` instead.\n */\nexport type PipelineConfig = {\n /** Pipeline name, used in execution metadata and error messages. */\n readonly name: string;\n /** Steps to execute in order. */\n readonly steps: readonly Step[];\n /** Optional middleware applied to every step. First in array = outermost. */\n readonly middleware?: readonly StepMiddleware[];\n /** Optional schema that validates the pipeline's input arguments. */\n readonly argsSchema?: ParserSchema<StepContext>;\n /**\n * When `true`, throws at build time if two or more steps provide the\n * same key. Only checks steps that have a `provides` schema with an\n * inspectable `.shape` property (e.g., Zod objects). Steps without\n * provides schemas are not checked.\n */\n readonly strict?: boolean;\n};\n\n// ---------------------------------------------------------------------------\n// Strict mode — detect provides key collisions at build time\n// ---------------------------------------------------------------------------\n\nfunction checkStrictOverlap(steps: readonly Step[]): void {\n const seen = new Map<string, string>(); // key → step name\n\n for (const step of steps) {\n if (!step.provides) continue;\n\n // Extract keys from schemas that expose .shape (e.g., Zod objects)\n const shape = (step.provides as Record<string, unknown>).shape;\n if (!shape || typeof shape !== 'object') continue;\n\n for (const key of Object.keys(shape)) {\n const existing = seen.get(key);\n if (existing) {\n throw new RunsheetError(\n 'STRICT_OVERLAP',\n `strict mode: key \"${key}\" is provided by both \"${existing}\" and \"${step.name}\"`,\n );\n }\n seen.set(key, step.name);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline\n// ---------------------------------------------------------------------------\n\n/**\n * A built pipeline, ready to execute.\n *\n * Call `run(args)` to execute the pipeline. The result is a\n * {@link PipelineResult} — either a success with the fully accumulated\n * context, or a failure with error details and a rollback report.\n *\n * Pipeline objects are frozen (immutable) and can be called repeatedly.\n *\n * @typeParam Args - The input type accepted by `run()`.\n * @typeParam Ctx - The accumulated output type on success.\n */\nexport type Pipeline<Args extends StepContext, Ctx> = {\n /** The pipeline's name, as provided at build time. */\n readonly name: string;\n /**\n * Execute the pipeline.\n *\n * @param args - The initial arguments. Merged into the context before\n * the first step runs. Validated against `argsSchema` if one was\n * provided.\n * @returns A {@link PipelineResult} — discriminate on `success` to\n * access `data` (on success) or `errors`/`rollback` (on failure).\n */\n readonly run: (args: Args) => Promise<PipelineResult<Ctx>>;\n};\n\n// ---------------------------------------------------------------------------\n// Schema validation\n// ---------------------------------------------------------------------------\n\nfunction validateSchema<T>(\n schema: ParserSchema<T> | undefined,\n data: unknown,\n label: string,\n code: RunsheetErrorCode,\n): { success: true; data: T } | { success: false; errors: RunsheetError[] } {\n if (!schema) return { success: true, data: data as T };\n\n const parsed = schema.safeParse(data);\n if (parsed.success) return { success: true, data: parsed.data };\n\n const errors = parsed.error.issues.map(\n (issue) => new RunsheetError(code, `${label}: ${issue.path.join('.')}: ${issue.message}`),\n );\n return { success: false, errors };\n}\n\n// ---------------------------------------------------------------------------\n// Rollback\n// ---------------------------------------------------------------------------\n\nasync function executeRollback(\n executedSteps: readonly Step[],\n snapshots: readonly StepContext[],\n outputs: readonly StepOutput[],\n): Promise<RollbackReport> {\n const completed: string[] = [];\n const failed: RollbackFailure[] = [];\n\n for (let i = executedSteps.length - 1; i >= 0; i--) {\n const step = executedSteps[i];\n if (!step.rollback) continue;\n\n try {\n await step.rollback(snapshots[i], outputs[i]);\n completed.push(step.name);\n } catch (err) {\n failed.push({\n step: step.name,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n }\n }\n\n return Object.freeze({ completed, failed });\n}\n\n// ---------------------------------------------------------------------------\n// Execution state — accumulated during pipeline run\n// ---------------------------------------------------------------------------\n\ntype ExecutionState = {\n context: StepContext;\n readonly snapshots: StepContext[];\n readonly outputs: StepOutput[];\n readonly executedSteps: Step[];\n readonly stepsExecuted: string[];\n readonly stepsSkipped: string[];\n};\n\nfunction createExecutionState(args: StepContext): ExecutionState {\n return {\n context: Object.freeze({ ...args }),\n snapshots: [],\n outputs: [],\n executedSteps: [],\n stepsExecuted: [],\n stepsSkipped: [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Result constructors\n// ---------------------------------------------------------------------------\n\nfunction pipelineFailure(\n pipelineName: string,\n args: StepContext,\n state: ExecutionState,\n failedStep: string,\n errors: Error[],\n rollback: RollbackReport,\n): PipelineFailure {\n return Object.freeze({\n success: false as const,\n errors,\n meta: Object.freeze({\n pipeline: pipelineName,\n args,\n stepsExecuted: state.stepsExecuted,\n stepsSkipped: state.stepsSkipped,\n }),\n failedStep,\n rollback,\n });\n}\n\nfunction pipelineSuccess(\n pipelineName: string,\n args: StepContext,\n state: ExecutionState,\n): PipelineSuccess<StepContext> {\n return Object.freeze({\n success: true as const,\n data: state.context,\n errors: [] as [],\n meta: Object.freeze({\n pipeline: pipelineName,\n args,\n stepsExecuted: state.stepsExecuted,\n stepsSkipped: state.stepsSkipped,\n }),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Step executor — the full lifecycle (validate requires → run → validate provides)\n// ---------------------------------------------------------------------------\n\nfunction createStepExecutor(\n step: Step,\n): (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>> {\n return async (ctx) => {\n // Validate requires\n const requiresCheck = validateSchema(\n step.requires,\n ctx,\n `${step.name} requires`,\n 'REQUIRES_VALIDATION',\n );\n if (!requiresCheck.success) {\n return { success: false as const, errors: requiresCheck.errors };\n }\n\n // Execute step run\n const result = await step.run(ctx);\n if (!result.success) return result;\n\n // Validate provides\n const providesCheck = validateSchema(\n step.provides,\n result.data,\n `${step.name} provides`,\n 'PROVIDES_VALIDATION',\n );\n if (!providesCheck.success) {\n return { success: false as const, errors: providesCheck.errors };\n }\n\n return {\n success: true as const,\n data: providesCheck.data as StepOutput,\n errors: [] as [],\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline execution\n// ---------------------------------------------------------------------------\n\nasync function executePipeline(\n config: PipelineConfig,\n args: StepContext,\n): Promise<PipelineResult<StepContext>> {\n // Validate pipeline args if schema provided\n if (config.argsSchema) {\n const argsCheck = validateSchema(\n config.argsSchema,\n args,\n `${config.name} args`,\n 'ARGS_VALIDATION',\n );\n if (!argsCheck.success) {\n const state = createExecutionState(args);\n return pipelineFailure(\n config.name,\n args,\n state,\n config.name,\n argsCheck.errors,\n Object.freeze({ completed: [], failed: [] }),\n );\n }\n }\n\n const state = createExecutionState(args);\n const middlewares = config.middleware ?? [];\n\n for (const step of config.steps) {\n // Evaluate conditional predicate\n try {\n if (isConditionalStep(step) && !step.predicate(state.context)) {\n state.stepsSkipped.push(step.name);\n continue;\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const error = new RunsheetError('PREDICATE', `${step.name} predicate: ${message}`);\n if (err instanceof Error) error.cause = err;\n const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);\n return pipelineFailure(config.name, args, state, step.name, [error], rollback);\n }\n\n // Snapshot pre-step context\n state.snapshots.push(state.context);\n\n // Build executor with middleware wrapping the full lifecycle\n const baseExecutor = createStepExecutor(step);\n const executor = applyMiddleware(\n middlewares,\n { name: step.name, requires: step.requires, provides: step.provides },\n baseExecutor,\n );\n\n // Execute (try/catch handles middleware throws outside the Result boundary)\n let result: Result<StepOutput>;\n try {\n result = await executor(state.context);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n state.snapshots.pop();\n const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);\n return pipelineFailure(config.name, args, state, step.name, [error], rollback);\n }\n\n if (!result.success) {\n // Remove the snapshot we just pushed — the step didn't complete\n state.snapshots.pop();\n const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);\n return pipelineFailure(config.name, args, state, step.name, result.errors, rollback);\n }\n\n // Track step output and accumulate context\n const output = result.data;\n state.outputs.push(output);\n state.executedSteps.push(step);\n state.stepsExecuted.push(step.name);\n state.context = Object.freeze({ ...state.context, ...output });\n }\n\n return pipelineSuccess(config.name, args, state);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Build a pipeline from an array of steps.\n *\n * The result type is inferred from the steps — `pipeline.run()` returns\n * a {@link PipelineResult} whose `data` is the intersection of the\n * initial `Args` and all step output types.\n *\n * @example\n * ```ts\n * const pipeline = buildPipeline({\n * name: 'placeOrder',\n * steps: [validateOrder, chargePayment, sendConfirmation],\n * middleware: [logging, timing],\n * argsSchema: z.object({ orderId: z.string() }),\n * });\n *\n * const result = await pipeline.run({ orderId: '123' });\n * if (result.success) {\n * result.data.chargeId; // string — fully typed\n * }\n * ```\n *\n * **Execution semantics:**\n * - Steps execute sequentially in array order.\n * - Context is frozen (`Object.freeze`) at every step boundary.\n * - Conditional steps (wrapped with `when()`) are skipped when their\n * predicate returns false — no snapshot, no rollback entry.\n * - On step failure, rollback handlers for all previously completed\n * steps execute in reverse order (best-effort).\n * - Middleware wraps the full step lifecycle including schema validation.\n *\n * **Invariants:**\n * - The returned pipeline object is frozen (immutable).\n * - Errors thrown by steps, predicates, or middleware are caught and\n * returned as `PipelineFailure` — `run()` never throws.\n *\n * @typeParam Args - The pipeline's input type. Inferred from `argsSchema`\n * if provided, otherwise defaults to `StepContext`.\n * @typeParam S - The step types in the array. Inferred automatically —\n * do not specify manually.\n * @param config - Pipeline configuration.\n * @param config.name - Pipeline name, used in metadata and error messages.\n * @param config.steps - Steps to execute in order.\n * @param config.middleware - Optional middleware applied to every step.\n * First in array = outermost wrapper.\n * @param config.argsSchema - Optional schema that validates `args` before\n * any steps run. Validation failure produces a `PipelineFailure` with\n * `failedStep` set to the pipeline name.\n * @returns A frozen {@link Pipeline} whose `run()` method executes the\n * steps and returns a {@link PipelineResult}.\n */\nexport function buildPipeline<\n Args extends StepContext = StepContext,\n S extends Step = Step,\n>(config: {\n readonly name: string;\n readonly steps: readonly S[];\n readonly middleware?: readonly StepMiddleware[];\n readonly argsSchema?: ParserSchema<Args>;\n readonly strict?: boolean;\n}): Pipeline<Args, Args & UnionToIntersection<ExtractProvides<S>>> {\n if (config.strict) checkStrictOverlap(config.steps);\n\n return Object.freeze({\n name: config.name,\n run: (args: Args) => executePipeline(config as PipelineConfig, args),\n }) as Pipeline<Args, Args & UnionToIntersection<ExtractProvides<S>>>;\n}\n","import type {\n ExtractProvides,\n ExtractRequires,\n Step,\n StepContext,\n StepOutput,\n TypedStep,\n UnionToIntersection,\n} from './types.js';\n\n/** Ensure a type satisfies StepContext, falling back to StepContext. */\ntype AsContext<T> = T extends StepContext ? T : StepContext;\nimport { RunsheetError } from './errors.js';\nimport { isConditionalStep } from './when.js';\n\n// ---------------------------------------------------------------------------\n// Inner step execution result\n// ---------------------------------------------------------------------------\n\ntype InnerResult = {\n step: Step;\n skipped: boolean;\n output?: StepOutput;\n errors?: Error[];\n};\n\n// ---------------------------------------------------------------------------\n// Schema validation (mirrors pipeline.ts — kept local to avoid coupling)\n// ---------------------------------------------------------------------------\n\nfunction validateInnerSchema(\n schema: Step['requires'] | Step['provides'],\n data: unknown,\n label: string,\n code: 'REQUIRES_VALIDATION' | 'PROVIDES_VALIDATION',\n): RunsheetError[] | null {\n if (!schema) return null;\n const parsed = schema.safeParse(data);\n if (parsed.success) return null;\n return parsed.error.issues.map(\n (issue) => new RunsheetError(code, `${label}: ${issue.path.join('.')}: ${issue.message}`),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Execute a single inner step\n// ---------------------------------------------------------------------------\n\nasync function executeInner(step: Step, ctx: Readonly<StepContext>): Promise<InnerResult> {\n // Conditional check\n try {\n if (isConditionalStep(step) && !step.predicate(ctx)) {\n return { step, skipped: true };\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const error = new RunsheetError('PREDICATE', `${step.name} predicate: ${message}`);\n if (err instanceof Error) error.cause = err;\n return { step, skipped: false, errors: [error] };\n }\n\n // Validate requires\n const requiresErrors = validateInnerSchema(\n step.requires,\n ctx,\n `${step.name} requires`,\n 'REQUIRES_VALIDATION',\n );\n if (requiresErrors) return { step, skipped: false, errors: requiresErrors };\n\n // Run step (composable() wrapper inside defineStep catches throws)\n const result = await step.run(ctx);\n if (!result.success) return { step, skipped: false, errors: [...result.errors] };\n\n // Validate provides\n const providesErrors = validateInnerSchema(\n step.provides,\n result.data,\n `${step.name} provides`,\n 'PROVIDES_VALIDATION',\n );\n if (providesErrors) return { step, skipped: false, errors: providesErrors };\n\n return { step, skipped: false, output: result.data };\n}\n\n// ---------------------------------------------------------------------------\n// parallel()\n// ---------------------------------------------------------------------------\n\n/**\n * Run multiple steps concurrently and merge their outputs.\n *\n * All inner steps receive the same pre-parallel context snapshot and\n * execute via `Promise.allSettled`. On success, outputs are merged in\n * array order (deterministic). On partial failure, succeeded inner\n * steps are rolled back in reverse array order before the failure\n * propagates.\n *\n * The returned step acts as a single step from the pipeline's\n * perspective — middleware wraps the group as a whole.\n *\n * Inner steps retain their own `requires`/`provides` validation,\n * `retry`, and `timeout` behavior. Conditional steps (via `when()`)\n * are evaluated per inner step.\n *\n * @example\n * ```ts\n * const pipeline = buildPipeline({\n * name: 'checkout',\n * steps: [\n * validateOrder,\n * parallel(reserveInventory, chargePayment),\n * sendConfirmation,\n * ],\n * });\n * ```\n *\n * @param steps - Two or more steps to execute concurrently.\n * @returns A frozen {@link TypedStep} whose `Requires` is the\n * intersection of all inner steps' requires, and `Provides` is the\n * intersection of all inner steps' provides.\n */\nexport function parallel<S extends readonly TypedStep[]>(\n ...steps: [...S]\n): TypedStep<\n AsContext<UnionToIntersection<ExtractRequires<S[number]>>>,\n AsContext<UnionToIntersection<ExtractProvides<S[number]>>>\n> {\n const name = `parallel(${(steps as readonly Step[]).map((s) => s.name).join(', ')})`;\n const innerSteps = steps as readonly Step[];\n\n // ----- run: execute all inner steps concurrently ----- //\n const run: Step['run'] = async (ctx) => {\n const settled = await Promise.allSettled(innerSteps.map((step) => executeInner(step, ctx)));\n\n const succeeded: { step: Step; output: StepOutput }[] = [];\n const allErrors: Error[] = [];\n\n for (const s of settled) {\n if (s.status === 'rejected') {\n allErrors.push(s.reason instanceof Error ? s.reason : new Error(String(s.reason)));\n } else {\n const r = s.value;\n if (r.skipped) continue;\n if (r.output) {\n succeeded.push({ step: r.step, output: r.output });\n } else if (r.errors) {\n allErrors.push(...r.errors);\n }\n }\n }\n\n if (allErrors.length > 0) {\n // Rollback succeeded inner steps in reverse array order (best-effort)\n for (let i = succeeded.length - 1; i >= 0; i--) {\n const { step, output } = succeeded[i];\n if (step.rollback) {\n try {\n await step.rollback(ctx, output);\n } catch {\n // Best-effort — inner rollback errors during partial failure\n // are not surfaced. The pipeline's own rollback report covers\n // the parallel step as a whole.\n }\n }\n }\n return { success: false as const, errors: allErrors };\n }\n\n // Merge outputs in array order (deterministic)\n const merged: StepOutput = {};\n for (const { output } of succeeded) {\n Object.assign(merged, output);\n }\n\n return { success: true as const, data: merged, errors: [] as [] };\n };\n\n // ----- rollback: called when a later sequential step fails ----- //\n // The pipeline passes the merged output. Each inner step's rollback\n // receives the full merged output (a superset of what it produced).\n // This is safe because rollback handlers only read their own keys.\n const rollback: NonNullable<Step['rollback']> = async (ctx, mergedOutput) => {\n const errors: Error[] = [];\n for (let i = innerSteps.length - 1; i >= 0; i--) {\n const step = innerSteps[i];\n if (!step.rollback) continue;\n try {\n await step.rollback(ctx, mergedOutput);\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)));\n }\n }\n if (errors.length > 0) {\n const error = new Error(`${name}: ${errors.length} rollback(s) failed`);\n error.cause = errors;\n throw error;\n }\n };\n\n return Object.freeze({\n name,\n requires: undefined,\n provides: undefined,\n run,\n rollback,\n retry: undefined,\n timeout: undefined,\n }) as unknown as TypedStep<\n AsContext<UnionToIntersection<ExtractRequires<S[number]>>>,\n AsContext<UnionToIntersection<ExtractProvides<S[number]>>>\n >;\n}\n","import type { ParserSchema } from 'composable-functions';\nimport type { Step, StepContext, TypedStep } from './types.js';\nimport type { StepMiddleware } from './middleware.js';\nimport { buildPipeline } from './pipeline.js';\nimport type { Pipeline } from './pipeline.js';\n\n// ---------------------------------------------------------------------------\n// Builder types\n// ---------------------------------------------------------------------------\n\n/**\n * A fluent pipeline builder that progressively narrows the accumulated\n * context type as steps are added.\n *\n * Each method returns a new, frozen builder — builders are immutable.\n * This means you can safely fork a builder to create variants:\n *\n * ```ts\n * const base = createPipeline('order').step(validate);\n * const withCharge = base.step(charge).build();\n * const withoutCharge = base.build(); // unaffected by the fork\n * ```\n *\n * @typeParam Args - The pipeline's initial input type.\n * @typeParam Ctx - The accumulated context type so far (grows with each `.step()`).\n */\nexport type PipelineBuilder<Args extends StepContext, Ctx extends StepContext> = {\n /**\n * Add a step to the pipeline.\n *\n * The step's `Requires` type must be satisfied by the current `Ctx`.\n * The returned builder's `Ctx` expands to include the step's `Provides`.\n *\n * @typeParam Provides - The output type of the step being added.\n * @param step - A {@link TypedStep} (from `defineStep` or `when`).\n * @returns A new builder with the expanded context type.\n */\n readonly step: <Provides extends StepContext>(\n step: TypedStep<Ctx, Provides>,\n ) => PipelineBuilder<Args, Ctx & Provides>;\n\n /**\n * Add middleware to the pipeline.\n *\n * Middleware is applied to every step. Multiple `.use()` calls\n * accumulate — earlier middleware is outermost (executes first).\n *\n * @param middleware - One or more {@link StepMiddleware} functions.\n * @returns A new builder with the middleware added.\n */\n readonly use: (...middleware: StepMiddleware[]) => PipelineBuilder<Args, Ctx>;\n\n /**\n * Build the pipeline.\n *\n * @returns A frozen {@link Pipeline} ready to execute with `run()`.\n */\n readonly build: () => Pipeline<Args, Ctx>;\n};\n\n// ---------------------------------------------------------------------------\n// Internal builder state (immutable — each method returns a new builder)\n// ---------------------------------------------------------------------------\n\ntype BuilderState = {\n readonly name: string;\n readonly steps: readonly Step[];\n readonly middleware: readonly StepMiddleware[];\n readonly argsSchema: ParserSchema<StepContext> | undefined;\n readonly strict: boolean;\n};\n\nfunction makeBuilder<Args extends StepContext, Ctx extends StepContext>(\n state: BuilderState,\n): PipelineBuilder<Args, Ctx> {\n return Object.freeze({\n step: <Provides extends StepContext>(step: TypedStep<Ctx, Provides>) =>\n makeBuilder<Args, Ctx & Provides>({\n ...state,\n steps: [...state.steps, step],\n }),\n\n use: (...middleware: StepMiddleware[]) =>\n makeBuilder<Args, Ctx>({\n ...state,\n middleware: [...state.middleware, ...middleware],\n }),\n\n build: () =>\n buildPipeline({\n name: state.name,\n steps: state.steps,\n middleware: state.middleware.length > 0 ? state.middleware : undefined,\n argsSchema: state.argsSchema as ParserSchema<Args> | undefined,\n strict: state.strict || undefined,\n }) as Pipeline<Args, Ctx>,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Start building a pipeline with the fluent builder API.\n *\n * The builder gives progressive type narrowing — each `.step()` call\n * extends the known context type, so TypeScript catches mismatches\n * at compile time.\n *\n * **Type-only args** (no runtime validation):\n * ```ts\n * createPipeline<{ orderId: string }>('placeOrder')\n * .step(validateOrder)\n * .step(chargePayment)\n * .build();\n * ```\n *\n * **Schema args** (runtime validation + type inference):\n * ```ts\n * createPipeline('placeOrder', z.object({ orderId: z.string() }))\n * .step(validateOrder)\n * .step(chargePayment)\n * .build();\n * ```\n *\n * **Strict mode** (no schema):\n * ```ts\n * createPipeline('placeOrder', { strict: true })\n * ```\n *\n * **Schema + strict mode:**\n * ```ts\n * createPipeline('placeOrder', z.object({ orderId: z.string() }), { strict: true })\n * ```\n *\n * @typeParam Args - The pipeline's input type. Inferred from `argsSchema`\n * if provided, otherwise specify via generic parameter.\n * @param name - Pipeline name, used in metadata and error messages.\n * @param schemaOrOptions - A schema for runtime args validation, or a\n * {@link PipelineOptions} object.\n * @param options - When the second argument is a schema, pass options here.\n * @returns A frozen {@link PipelineBuilder} ready for `.step()`,\n * `.use()`, and `.build()`.\n */\nexport type PipelineOptions = {\n strict?: boolean;\n};\n\n// Overload: name only\nexport function createPipeline<Args extends StepContext>(name: string): PipelineBuilder<Args, Args>;\n\n// Overload: name + schema\nexport function createPipeline<Args extends StepContext>(\n name: string,\n argsSchema: ParserSchema<Args>,\n): PipelineBuilder<Args, Args>;\n\n// Overload: name + options (no schema)\nexport function createPipeline<Args extends StepContext>(\n name: string,\n options: PipelineOptions,\n): PipelineBuilder<Args, Args>;\n\n// Overload: name + schema + options\nexport function createPipeline<Args extends StepContext>(\n name: string,\n argsSchema: ParserSchema<Args>,\n options: PipelineOptions,\n): PipelineBuilder<Args, Args>;\n\n// Implementation\nexport function createPipeline<Args extends StepContext>(\n name: string,\n schemaOrOptions?: ParserSchema<Args> | PipelineOptions,\n options?: PipelineOptions,\n): PipelineBuilder<Args, Args> {\n let argsSchema: ParserSchema<Args> | undefined;\n let strict = false;\n\n if (schemaOrOptions != null) {\n if ('safeParse' in schemaOrOptions) {\n argsSchema = schemaOrOptions;\n strict = options?.strict ?? false;\n } else {\n strict = schemaOrOptions.strict ?? false;\n }\n }\n\n return makeBuilder<Args, Args>({\n name,\n steps: [],\n middleware: [],\n argsSchema,\n strict,\n });\n}\n"],"mappings":"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","names":["state"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/define-step.ts","../src/errors.ts","../src/middleware.ts","../src/when.ts","../src/internal.ts","../src/pipeline.ts","../src/parallel.ts","../src/choice.ts","../src/map.ts","../src/filter.ts","../src/flat-map.ts","../src/builder.ts"],"sourcesContent":["export { defineStep } from './define-step.js';\nexport {\n RunsheetError,\n RequiresValidationError,\n ProvidesValidationError,\n ArgsValidationError,\n PredicateError,\n TimeoutError,\n RetryExhaustedError,\n StrictOverlapError,\n ChoiceNoMatchError,\n RollbackError,\n UnknownError,\n} from './errors.js';\nexport type { RunsheetErrorCode } from './errors.js';\nexport { buildPipeline } from './pipeline.js';\nexport type { Pipeline, PipelineConfig } from './pipeline.js';\nexport { when } from './when.js';\nexport type { ConditionalStep } from './when.js';\nexport { parallel } from './parallel.js';\nexport { choice } from './choice.js';\nexport { map } from './map.js';\nexport { filter } from './filter.js';\nexport { flatMap } from './flat-map.js';\nexport type { StepMiddleware, StepInfo, StepExecutor } from './middleware.js';\nexport { createPipeline } from './builder.js';\nexport type { PipelineBuilder } from './builder.js';\n\nexport type {\n Step,\n TypedStep,\n StepConfig,\n StepContext,\n StepOutput,\n ExtractRequires,\n ExtractProvides,\n RetryPolicy,\n PipelineResult,\n PipelineSuccess,\n PipelineFailure,\n PipelineExecutionMeta,\n RollbackReport,\n RollbackFailure,\n} from './types.js';\n\n// Re-export Result types so consumers never need to import composable-functions\nexport type { Result, Success, Failure } from 'composable-functions';\n","import { composable } from 'composable-functions';\nimport type { Result } from 'composable-functions';\nimport type { RetryPolicy, StepConfig, StepContext, StepOutput, Step, TypedStep } from './types.js';\nimport { RetryExhaustedError, TimeoutError } from './errors.js';\n\n// ---------------------------------------------------------------------------\n// Timeout and retry wrappers\n// ---------------------------------------------------------------------------\n\n// NOTE: Promise.race doesn't cancel the underlying run — if the timer wins,\n// the step's side effects continue in the background. True cancellation\n// would require AbortSignal propagation into step run functions.\nfunction withTimeout(\n run: (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>>,\n stepName: string,\n ms: number,\n): (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>> {\n return async (ctx) => {\n let timer: ReturnType<typeof setTimeout>;\n const timeout = new Promise<Result<StepOutput>>((resolve) => {\n timer = setTimeout(() => {\n const error = new TimeoutError(`${stepName} timed out after ${ms}ms`, ms);\n resolve({ success: false, errors: [error] });\n }, ms);\n });\n try {\n return await Promise.race([run(ctx), timeout]);\n } finally {\n clearTimeout(timer!);\n }\n };\n}\n\nfunction computeDelay(policy: RetryPolicy, attempt: number): number {\n const base = policy.delay ?? 0;\n if (base === 0) return 0;\n const strategy = policy.backoff ?? 'linear';\n return strategy === 'exponential' ? base * 2 ** (attempt - 1) : base * attempt;\n}\n\nfunction withRetry(\n run: (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>>,\n stepName: string,\n policy: RetryPolicy,\n): (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>> {\n return async (ctx) => {\n let lastResult = await run(ctx);\n if (lastResult.success) return lastResult;\n\n for (let attempt = 1; attempt <= policy.count; attempt++) {\n // Check if the failure is retryable\n if (policy.retryIf && !policy.retryIf(lastResult.errors)) return lastResult;\n\n const delay = computeDelay(policy, attempt);\n if (delay > 0) await new Promise((r) => setTimeout(r, delay));\n lastResult = await run(ctx);\n if (lastResult.success) return lastResult;\n }\n\n // Wrap the last failure with RETRY_EXHAUSTED\n const error = new RetryExhaustedError(\n `${stepName} failed after ${policy.count} retries`,\n policy.count + 1,\n );\n return { success: false, errors: [...lastResult.errors, error] };\n };\n}\n\nfunction wrapWithTimeoutAndRetry(\n run: (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>>,\n stepName: string,\n timeout: number | undefined,\n retry: RetryPolicy | undefined,\n): (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>> {\n // Timeout wraps the individual run call, retry wraps the timeout+run combo\n let wrapped = run;\n if (timeout !== undefined) wrapped = withTimeout(wrapped, stepName, timeout);\n if (retry !== undefined) wrapped = withRetry(wrapped, stepName, retry);\n return wrapped;\n}\n\n/**\n * Define a pipeline step.\n *\n * Returns a frozen {@link TypedStep} with concrete types for `run`,\n * `rollback`, `requires`, and `provides`. The `run` function can be\n * sync or async — both are supported.\n *\n * **With schemas** (runtime validation + type inference):\n * ```ts\n * const charge = defineStep({\n * name: 'charge',\n * requires: z.object({ amount: z.number() }),\n * provides: z.object({ chargeId: z.string() }),\n * run: async (ctx) => ({ chargeId: 'ch_123' }),\n * });\n * ```\n *\n * **With generics only** (no runtime validation):\n * ```ts\n * const log = defineStep<{ order: Order }, { loggedAt: Date }>({\n * name: 'log',\n * run: async (ctx) => ({ loggedAt: new Date() }),\n * });\n * ```\n *\n * **Invariants:**\n * - The returned step object is always frozen (immutable).\n * - The `run` function is wrapped with `composable()` from\n * composable-functions, which catches thrown errors and produces\n * `Result` values. Step authors should throw to signal failure.\n * - This is the single type-erasure cast point in the library.\n *\n * @typeParam Requires - The context shape this step reads from.\n * @typeParam Provides - The output shape this step produces.\n * @param config - The step configuration. See {@link StepConfig}.\n * @returns A frozen {@link TypedStep} ready for use in pipelines.\n */\nexport function defineStep<Requires extends StepContext, Provides extends StepContext>(\n config: StepConfig<Requires, Provides>,\n): TypedStep<Requires, Provides> {\n const baseRun = composable(config.run) as unknown as (\n ctx: Readonly<StepContext>,\n ) => Promise<Result<StepOutput>>;\n const wrappedRun = wrapWithTimeoutAndRetry(baseRun, config.name, config.timeout, config.retry);\n\n // The cast below is the single point where typed step functions are erased\n // to the runtime Step representation. This is safe because:\n // 1. Schema validation at step boundaries (requires/provides) enforces\n // correct types at runtime before and after each step executes.\n // 2. The pipeline accumulates context immutably, so the runtime object\n // structurally matches what the typed function expects.\n // 3. The phantom brands on TypedStep preserve compile-time type tracking\n // through the builder API without affecting runtime behavior.\n return Object.freeze({\n name: config.name,\n requires: config.requires ?? undefined,\n provides: config.provides ?? undefined,\n run: wrappedRun as unknown as Step['run'],\n rollback: config.rollback\n ? async (ctx: Readonly<StepContext>, output: Readonly<StepContext>) => {\n await config.rollback!(ctx as Readonly<Requires>, output as Readonly<Provides>);\n }\n : undefined,\n retry: config.retry ?? undefined,\n timeout: config.timeout ?? undefined,\n }) as TypedStep<Requires, Provides>;\n}\n","/**\n * Error codes for errors produced by the runsheet library itself.\n *\n * Use these to distinguish library errors from application errors\n * in a pipeline's `errors` array:\n *\n * ```ts\n * if (!result.success) {\n * for (const error of result.errors) {\n * if (error instanceof RunsheetError) {\n * console.log(error.code); // 'REQUIRES_VALIDATION', etc.\n * }\n * }\n * }\n * ```\n */\nexport type RunsheetErrorCode =\n | 'REQUIRES_VALIDATION'\n | 'PROVIDES_VALIDATION'\n | 'ARGS_VALIDATION'\n | 'PREDICATE'\n | 'TIMEOUT'\n | 'RETRY_EXHAUSTED'\n | 'STRICT_OVERLAP'\n | 'CHOICE_NO_MATCH'\n | 'ROLLBACK'\n | 'UNKNOWN';\n\n/**\n * Base error class for all errors produced by the runsheet library.\n *\n * Application errors (thrown by step `run` or `rollback` functions)\n * are never wrapped in `RunsheetError` — they pass through as-is.\n * If you see a `RunsheetError` in a result's `errors` array, the\n * library itself produced it.\n *\n * Use `instanceof RunsheetError` to distinguish library errors from\n * application errors. Use `instanceof` on a subclass (e.g.,\n * `TimeoutError`) or check the `code` property for specific failures.\n */\nexport class RunsheetError extends Error {\n /** Discriminant code identifying the type of library error. */\n readonly code: RunsheetErrorCode;\n\n /**\n * @param code - The error code.\n * @param message - A human-readable description of the failure.\n */\n constructor(code: RunsheetErrorCode, message: string) {\n super(message);\n this.name = 'RunsheetError';\n this.code = code;\n }\n}\n\n/** Schema validation failed on the accumulated context before a step ran. */\nexport class RequiresValidationError extends RunsheetError {\n constructor(message: string) {\n super('REQUIRES_VALIDATION', message);\n this.name = 'RequiresValidationError';\n }\n}\n\n/** Schema validation failed on a step's output after it ran. */\nexport class ProvidesValidationError extends RunsheetError {\n constructor(message: string) {\n super('PROVIDES_VALIDATION', message);\n this.name = 'ProvidesValidationError';\n }\n}\n\n/** Schema validation failed on the pipeline's input arguments. */\nexport class ArgsValidationError extends RunsheetError {\n constructor(message: string) {\n super('ARGS_VALIDATION', message);\n this.name = 'ArgsValidationError';\n }\n}\n\n/** A `when()` or `choice()` predicate threw an error. */\nexport class PredicateError extends RunsheetError {\n constructor(message: string) {\n super('PREDICATE', message);\n this.name = 'PredicateError';\n }\n}\n\n/** A step exceeded its configured timeout. */\nexport class TimeoutError extends RunsheetError {\n /** The timeout duration in milliseconds that was exceeded. */\n readonly timeoutMs: number;\n\n constructor(message: string, timeoutMs: number) {\n super('TIMEOUT', message);\n this.name = 'TimeoutError';\n this.timeoutMs = timeoutMs;\n }\n}\n\n/** A step failed after exhausting all retry attempts. */\nexport class RetryExhaustedError extends RunsheetError {\n /** Total number of attempts (initial + retries). */\n readonly attempts: number;\n\n constructor(message: string, attempts: number) {\n super('RETRY_EXHAUSTED', message);\n this.name = 'RetryExhaustedError';\n this.attempts = attempts;\n }\n}\n\n/** Two steps provide the same key (strict mode, detected at build time). */\nexport class StrictOverlapError extends RunsheetError {\n /** The key that is provided by multiple steps. */\n readonly key: string;\n /** The names of the two steps that both provide the key. */\n readonly steps: readonly [string, string];\n\n constructor(message: string, key: string, steps: readonly [string, string]) {\n super('STRICT_OVERLAP', message);\n this.name = 'StrictOverlapError';\n this.key = key;\n this.steps = steps;\n }\n}\n\n/** No branch matched in a `choice()` step. */\nexport class ChoiceNoMatchError extends RunsheetError {\n constructor(message: string) {\n super('CHOICE_NO_MATCH', message);\n this.name = 'ChoiceNoMatchError';\n }\n}\n\n/** A non-Error value was thrown and caught by the pipeline engine. */\nexport class UnknownError extends RunsheetError {\n /** The original thrown value before stringification. */\n readonly originalValue: unknown;\n\n constructor(message: string, originalValue: unknown) {\n super('UNKNOWN', message);\n this.name = 'UnknownError';\n this.originalValue = originalValue;\n }\n}\n\n/** One or more rollback handlers failed in a combinator. */\nexport class RollbackError extends RunsheetError {\n constructor(message: string) {\n super('ROLLBACK', message);\n this.name = 'RollbackError';\n }\n}\n","import type { Result } from 'composable-functions';\nimport type { Step, StepContext, StepOutput } from './types.js';\n\n/**\n * Metadata about the step being executed, passed to middleware.\n *\n * This is a read-only view of the step's public configuration.\n * Middleware can use it for logging, metrics, or conditional behavior.\n */\nexport type StepInfo = {\n /** The step's name. */\n readonly name: string;\n /** The step's requires schema, or `undefined` if not provided. */\n readonly requires: Step['requires'];\n /** The step's provides schema, or `undefined` if not provided. */\n readonly provides: Step['provides'];\n};\n\n/**\n * A function that executes a step (or the next middleware in the chain).\n *\n * Receives the frozen accumulated context and returns a `Result` — either\n * `{ success: true, data }` or `{ success: false, errors }`.\n */\nexport type StepExecutor = (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>>;\n\n/**\n * Middleware that wraps the entire step lifecycle, including schema\n * validation.\n *\n * A middleware receives the step metadata and a `next` function, and\n * returns a new executor. Call `next(ctx)` to proceed to the next\n * middleware (or the actual step execution). You can:\n *\n * - **Observe**: read the context or result for logging/metrics.\n * - **Transform**: modify the result before returning it.\n * - **Short-circuit**: return a `Result` without calling `next`.\n *\n * If a middleware throws, the pipeline catches it and treats it as a\n * step failure (triggering rollback for previously completed steps).\n *\n * @example\n * ```ts\n * const timing: StepMiddleware = (step, next) => async (ctx) => {\n * const start = performance.now();\n * const result = await next(ctx);\n * console.log(`${step.name}: ${performance.now() - start}ms`);\n * return result;\n * };\n * ```\n *\n * @param step - Metadata about the step being wrapped.\n * @param next - The next executor in the chain. Call it to continue.\n * @returns A new executor that wraps `next`.\n */\nexport type StepMiddleware = (step: StepInfo, next: StepExecutor) => StepExecutor;\n\n/**\n * Compose an array of middlewares around a step executor.\n *\n * First middleware in the array is the outermost wrapper (executes\n * first on the way in, last on the way out).\n *\n * @param middlewares - Middleware functions, in declaration order.\n * @param step - Metadata about the step being wrapped.\n * @param executor - The base step executor to wrap.\n * @returns A composed executor with all middleware applied.\n */\nexport function applyMiddleware(\n middlewares: readonly StepMiddleware[],\n step: StepInfo,\n executor: StepExecutor,\n): StepExecutor {\n return middlewares.reduceRight<StepExecutor>((next, mw) => mw(step, next), executor);\n}\n","import type { Step, StepContext, TypedStep } from './types.js';\n\n/**\n * A step with a conditional predicate attached.\n *\n * The pipeline engine checks for the `predicate` property to decide\n * whether to execute or skip the step. Use the {@link when} function\n * to create conditional steps — don't construct this type directly.\n */\nexport type ConditionalStep = Step & {\n /** Returns `true` to execute the step, `false` to skip it. */\n readonly predicate: (ctx: Readonly<StepContext>) => boolean;\n};\n\n/**\n * Wrap a step with a guard predicate.\n *\n * The step only executes when the predicate returns `true`. When\n * skipped:\n * - No context snapshot is taken.\n * - No rollback entry is created.\n * - The step name is recorded in `result.meta.stepsSkipped`.\n *\n * If the predicate throws, the pipeline treats it as a step failure\n * and triggers rollback for any previously completed steps.\n *\n * @example\n * ```ts\n * const steps = [\n * validateOrder,\n * when((ctx) => ctx.order.amount > 100, notifyManager),\n * sendConfirmation,\n * ];\n * ```\n *\n * @typeParam Requires - Inferred from the step's requires type.\n * @typeParam Provides - Inferred from the step's provides type.\n * @param predicate - Guard function. Receives the current accumulated\n * context (frozen). Return `true` to execute, `false` to skip.\n * @param step - The step to conditionally execute.\n * @returns A frozen {@link TypedStep} with the predicate attached.\n */\nexport function when<Requires extends StepContext, Provides extends StepContext>(\n predicate: (ctx: Readonly<Requires>) => boolean,\n step: TypedStep<Requires, Provides>,\n): TypedStep<Requires, Provides> {\n return Object.freeze({\n ...step,\n predicate: predicate as ConditionalStep['predicate'],\n }) as TypedStep<Requires, Provides>;\n}\n\n/** Type guard for conditional steps. */\nexport function isConditionalStep(step: Step): step is ConditionalStep {\n return 'predicate' in step && typeof step.predicate === 'function';\n}\n","import type { Result } from 'composable-functions';\nimport type { Step, StepContext, StepOutput } from './types.js';\nimport {\n RequiresValidationError,\n ProvidesValidationError,\n UnknownError,\n type RunsheetError,\n} from './errors.js';\n\n/** Ensure a type satisfies StepContext, falling back to StepContext. */\nexport type AsContext<T> = T extends StepContext ? T : StepContext;\n\n/** Normalize an unknown thrown value to an Error instance. */\nexport function toError(err: unknown): Error {\n if (err instanceof Error) return err;\n return new UnknownError(String(err), err);\n}\n\ntype ValidationErrorClass = typeof RequiresValidationError | typeof ProvidesValidationError;\n\n/**\n * Validate data against a step's requires or provides schema.\n * Returns an array of errors on failure, or null on success/no schema.\n *\n * This is the combinator variant. See pipeline.ts validateSchema for the\n * pipeline-level variant (which also returns parsed data for passthrough).\n */\nexport function validateInnerSchema(\n schema: Step['requires'] | Step['provides'],\n data: unknown,\n label: string,\n ErrorClass: ValidationErrorClass,\n): RunsheetError[] | null {\n if (!schema) return null;\n const parsed = schema.safeParse(data);\n if (parsed.success) return null;\n return parsed.error.issues.map(\n (issue) => new ErrorClass(`${label}: ${issue.path.join('.')}: ${issue.message}`),\n );\n}\n\n/**\n * Run a single inner step with requires/provides validation.\n *\n * Shared lifecycle used by parallel, choice, and map combinators:\n * validate requires → run → validate provides.\n *\n * Unlike the pipeline-level executor, inner steps do not go through\n * middleware. Middleware wraps the composite combinator step as a whole,\n * not the individual inner steps.\n */\nexport async function runInnerStep(\n step: Step,\n ctx: Readonly<StepContext>,\n): Promise<Result<StepOutput>> {\n const requiresErrors = validateInnerSchema(\n step.requires,\n ctx,\n `${step.name} requires`,\n RequiresValidationError,\n );\n if (requiresErrors) return { success: false, errors: requiresErrors };\n\n const result = await step.run(ctx);\n if (!result.success) return result;\n\n const providesErrors = validateInnerSchema(\n step.provides,\n result.data,\n `${step.name} provides`,\n ProvidesValidationError,\n );\n if (providesErrors) return { success: false, errors: providesErrors };\n\n return result;\n}\n","import type { ParserSchema, Result } from 'composable-functions';\nimport type {\n ExtractProvides,\n PipelineFailure,\n PipelineResult,\n PipelineSuccess,\n RollbackFailure,\n RollbackReport,\n Step,\n StepContext,\n StepOutput,\n UnionToIntersection,\n} from './types.js';\nimport type { StepMiddleware } from './middleware.js';\nimport {\n type RunsheetError,\n ArgsValidationError,\n PredicateError,\n RequiresValidationError,\n ProvidesValidationError,\n StrictOverlapError,\n} from './errors.js';\nimport { applyMiddleware } from './middleware.js';\nimport { isConditionalStep } from './when.js';\nimport { toError } from './internal.js';\n\n// ---------------------------------------------------------------------------\n// Pipeline configuration\n// ---------------------------------------------------------------------------\n\n/**\n * Internal configuration shape for the pipeline engine.\n *\n * Users typically don't construct this directly — use `buildPipeline()`\n * or `createPipeline()` instead.\n */\nexport type PipelineConfig = {\n /** Pipeline name, used in execution metadata and error messages. */\n readonly name: string;\n /** Steps to execute in order. */\n readonly steps: readonly Step[];\n /** Optional middleware applied to every step. First in array = outermost. */\n readonly middleware?: readonly StepMiddleware[];\n /** Optional schema that validates the pipeline's input arguments. */\n readonly argsSchema?: ParserSchema<StepContext>;\n /**\n * When `true`, throws at build time if two or more steps provide the\n * same key. Only checks steps that have a `provides` schema with an\n * inspectable `.shape` property (e.g., Zod objects). Steps without\n * provides schemas are not checked.\n */\n readonly strict?: boolean;\n};\n\n// ---------------------------------------------------------------------------\n// Strict mode — detect provides key collisions at build time\n// ---------------------------------------------------------------------------\n\nfunction checkStrictOverlap(steps: readonly Step[]): void {\n const seen = new Map<string, string>(); // key → step name\n\n for (const step of steps) {\n if (!step.provides) continue;\n\n // Extract keys from schemas that expose .shape (e.g., Zod objects)\n const shape = (step.provides as Record<string, unknown>).shape;\n if (!shape || typeof shape !== 'object') continue;\n\n for (const key of Object.keys(shape)) {\n const existing = seen.get(key);\n if (existing) {\n throw new StrictOverlapError(\n `strict mode: key \"${key}\" is provided by both \"${existing}\" and \"${step.name}\"`,\n key,\n [existing, step.name],\n );\n }\n seen.set(key, step.name);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline\n// ---------------------------------------------------------------------------\n\n/**\n * A built pipeline, ready to execute.\n *\n * Call `run(args)` to execute the pipeline. The result is a\n * {@link PipelineResult} — either a success with the fully accumulated\n * context, or a failure with error details and a rollback report.\n *\n * Pipeline objects are frozen (immutable) and can be called repeatedly.\n *\n * @typeParam Args - The input type accepted by `run()`.\n * @typeParam Ctx - The accumulated output type on success.\n */\nexport type Pipeline<Args extends StepContext, Ctx> = {\n /** The pipeline's name, as provided at build time. */\n readonly name: string;\n /**\n * Execute the pipeline.\n *\n * @param args - The initial arguments. Merged into the context before\n * the first step runs. Validated against `argsSchema` if one was\n * provided.\n * @returns A {@link PipelineResult} — discriminate on `success` to\n * access `data` (on success) or `errors`/`rollback` (on failure).\n */\n readonly run: (args: Args) => Promise<PipelineResult<Ctx>>;\n};\n\n// ---------------------------------------------------------------------------\n// Schema validation\n// ---------------------------------------------------------------------------\n// Pipeline-level validation returns parsed data for passthrough.\n// See internal.ts for the combinator variant (validateInnerSchema).\n\ntype ValidationErrorClass = new (message: string) => RunsheetError;\n\nfunction validateSchema<T>(\n schema: ParserSchema<T> | undefined,\n data: unknown,\n label: string,\n ErrorClass: ValidationErrorClass,\n): { success: true; data: T } | { success: false; errors: RunsheetError[] } {\n if (!schema) return { success: true, data: data as T };\n\n const parsed = schema.safeParse(data);\n if (parsed.success) return { success: true, data: parsed.data };\n\n const errors = parsed.error.issues.map(\n (issue) => new ErrorClass(`${label}: ${issue.path.join('.')}: ${issue.message}`),\n );\n return { success: false, errors };\n}\n\n// ---------------------------------------------------------------------------\n// Rollback\n// ---------------------------------------------------------------------------\n\nasync function executeRollback(\n executedSteps: readonly Step[],\n snapshots: readonly StepContext[],\n outputs: readonly StepOutput[],\n): Promise<RollbackReport> {\n const completed: string[] = [];\n const failed: RollbackFailure[] = [];\n\n for (let i = executedSteps.length - 1; i >= 0; i--) {\n const step = executedSteps[i];\n if (!step.rollback) continue;\n\n try {\n await step.rollback(snapshots[i], outputs[i]);\n completed.push(step.name);\n } catch (err) {\n failed.push({\n step: step.name,\n error: toError(err),\n });\n }\n }\n\n return Object.freeze({ completed, failed });\n}\n\n// ---------------------------------------------------------------------------\n// Execution state — accumulated during pipeline run\n// ---------------------------------------------------------------------------\n\ntype ExecutionState = {\n context: StepContext;\n readonly snapshots: StepContext[];\n readonly outputs: StepOutput[];\n readonly executedSteps: Step[];\n readonly stepsExecuted: string[];\n readonly stepsSkipped: string[];\n};\n\nfunction createExecutionState(args: StepContext): ExecutionState {\n return {\n context: Object.freeze({ ...args }),\n snapshots: [],\n outputs: [],\n executedSteps: [],\n stepsExecuted: [],\n stepsSkipped: [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Result constructors\n// ---------------------------------------------------------------------------\n\nfunction pipelineFailure(\n pipelineName: string,\n args: StepContext,\n state: ExecutionState,\n failedStep: string,\n errors: Error[],\n rollback: RollbackReport,\n): PipelineFailure {\n return Object.freeze({\n success: false,\n errors,\n meta: Object.freeze({\n pipeline: pipelineName,\n args,\n stepsExecuted: state.stepsExecuted,\n stepsSkipped: state.stepsSkipped,\n }),\n failedStep,\n rollback,\n });\n}\n\nfunction pipelineSuccess(\n pipelineName: string,\n args: StepContext,\n state: ExecutionState,\n): PipelineSuccess<StepContext> {\n return Object.freeze({\n success: true,\n data: state.context,\n errors: [] as [],\n meta: Object.freeze({\n pipeline: pipelineName,\n args,\n stepsExecuted: state.stepsExecuted,\n stepsSkipped: state.stepsSkipped,\n }),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Step executor — the full lifecycle (validate requires → run → validate provides)\n// ---------------------------------------------------------------------------\n\nfunction createStepExecutor(\n step: Step,\n): (ctx: Readonly<StepContext>) => Promise<Result<StepOutput>> {\n return async (ctx) => {\n // Validate requires\n const requiresCheck = validateSchema(\n step.requires,\n ctx,\n `${step.name} requires`,\n RequiresValidationError,\n );\n if (!requiresCheck.success) {\n return { success: false, errors: requiresCheck.errors };\n }\n\n // Execute step run\n const result = await step.run(ctx);\n if (!result.success) return result;\n\n // Validate provides\n const providesCheck = validateSchema(\n step.provides,\n result.data,\n `${step.name} provides`,\n ProvidesValidationError,\n );\n if (!providesCheck.success) {\n return { success: false, errors: providesCheck.errors };\n }\n\n return {\n success: true,\n data: providesCheck.data,\n errors: [],\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline execution\n// ---------------------------------------------------------------------------\n\nasync function executePipeline(\n config: PipelineConfig,\n args: StepContext,\n): Promise<PipelineResult<StepContext>> {\n // Validate pipeline args if schema provided\n if (config.argsSchema) {\n const argsCheck = validateSchema(\n config.argsSchema,\n args,\n `${config.name} args`,\n ArgsValidationError,\n );\n if (!argsCheck.success) {\n const state = createExecutionState(args);\n return pipelineFailure(\n config.name,\n args,\n state,\n config.name,\n argsCheck.errors,\n Object.freeze({ completed: [], failed: [] }),\n );\n }\n }\n\n const state = createExecutionState(args);\n const middlewares = config.middleware ?? [];\n\n for (const step of config.steps) {\n // Evaluate conditional predicate\n try {\n if (isConditionalStep(step) && !step.predicate(state.context)) {\n state.stepsSkipped.push(step.name);\n continue;\n }\n } catch (err) {\n const cause = toError(err);\n const error = new PredicateError(`${step.name} predicate: ${cause.message}`);\n error.cause = cause;\n const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);\n return pipelineFailure(config.name, args, state, step.name, [error], rollback);\n }\n\n // Snapshot pre-step context\n state.snapshots.push(state.context);\n\n // Build executor with middleware wrapping the full lifecycle\n const baseExecutor = createStepExecutor(step);\n const executor = applyMiddleware(\n middlewares,\n { name: step.name, requires: step.requires, provides: step.provides },\n baseExecutor,\n );\n\n // Execute (try/catch handles middleware throws outside the Result boundary)\n let result: Result<StepOutput>;\n try {\n result = await executor(state.context);\n } catch (err) {\n const error = toError(err);\n state.snapshots.pop();\n const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);\n return pipelineFailure(config.name, args, state, step.name, [error], rollback);\n }\n\n if (!result.success) {\n // Remove the snapshot we just pushed — the step didn't complete\n state.snapshots.pop();\n const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);\n return pipelineFailure(config.name, args, state, step.name, result.errors, rollback);\n }\n\n // Track step output and accumulate context.\n // IMPORTANT: result.data is stored by reference. The choice() and map()\n // combinators rely on this exact reference for WeakMap-based rollback\n // tracking. Do not clone or spread result.data before storing it here.\n const output = result.data;\n state.outputs.push(output);\n state.executedSteps.push(step);\n state.stepsExecuted.push(step.name);\n state.context = Object.freeze({ ...state.context, ...output });\n }\n\n return pipelineSuccess(config.name, args, state);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Build a pipeline from an array of steps.\n *\n * The result type is inferred from the steps — `pipeline.run()` returns\n * a {@link PipelineResult} whose `data` is the intersection of the\n * initial `Args` and all step output types.\n *\n * @example\n * ```ts\n * const pipeline = buildPipeline({\n * name: 'placeOrder',\n * steps: [validateOrder, chargePayment, sendConfirmation],\n * middleware: [logging, timing],\n * argsSchema: z.object({ orderId: z.string() }),\n * });\n *\n * const result = await pipeline.run({ orderId: '123' });\n * if (result.success) {\n * result.data.chargeId; // string — fully typed\n * }\n * ```\n *\n * **Execution semantics:**\n * - Steps execute sequentially in array order.\n * - Context is frozen (`Object.freeze`) at every step boundary.\n * - Conditional steps (wrapped with `when()`) are skipped when their\n * predicate returns false — no snapshot, no rollback entry.\n * - On step failure, rollback handlers for all previously completed\n * steps execute in reverse order (best-effort).\n * - Middleware wraps the full step lifecycle including schema validation.\n *\n * **Invariants:**\n * - The returned pipeline object is frozen (immutable).\n * - Errors thrown by steps, predicates, or middleware are caught and\n * returned as `PipelineFailure` — `run()` never throws.\n *\n * @typeParam Args - The pipeline's input type. Inferred from `argsSchema`\n * if provided, otherwise defaults to `StepContext`.\n * @typeParam S - The step types in the array. Inferred automatically —\n * do not specify manually.\n * @param config - Pipeline configuration.\n * @param config.name - Pipeline name, used in metadata and error messages.\n * @param config.steps - Steps to execute in order.\n * @param config.middleware - Optional middleware applied to every step.\n * First in array = outermost wrapper.\n * @param config.argsSchema - Optional schema that validates `args` before\n * any steps run. Validation failure produces a `PipelineFailure` with\n * `failedStep` set to the pipeline name.\n * @returns A frozen {@link Pipeline} whose `run()` method executes the\n * steps and returns a {@link PipelineResult}.\n */\nexport function buildPipeline<\n Args extends StepContext = StepContext,\n S extends Step = Step,\n>(config: {\n readonly name: string;\n readonly steps: readonly S[];\n readonly middleware?: readonly StepMiddleware[];\n readonly argsSchema?: ParserSchema<Args>;\n readonly strict?: boolean;\n}): Pipeline<Args, Args & UnionToIntersection<ExtractProvides<S>>> {\n if (config.strict) checkStrictOverlap(config.steps);\n\n return Object.freeze({\n name: config.name,\n run: (args: Args) => executePipeline(config as PipelineConfig, args),\n }) as Pipeline<Args, Args & UnionToIntersection<ExtractProvides<S>>>;\n}\n","import type {\n ExtractProvides,\n ExtractRequires,\n Step,\n StepContext,\n StepOutput,\n TypedStep,\n UnionToIntersection,\n} from './types.js';\nimport type { AsContext } from './internal.js';\nimport { runInnerStep, toError } from './internal.js';\nimport { PredicateError, RollbackError } from './errors.js';\nimport { isConditionalStep } from './when.js';\n\n// ---------------------------------------------------------------------------\n// Inner step execution result\n// ---------------------------------------------------------------------------\n\ntype InnerResult = {\n step: Step;\n skipped: boolean;\n output?: StepOutput;\n errors?: Error[];\n};\n\n// ---------------------------------------------------------------------------\n// Execute a single inner step (with conditional check)\n// ---------------------------------------------------------------------------\n\nasync function executeInner(step: Step, ctx: Readonly<StepContext>): Promise<InnerResult> {\n // Conditional check\n try {\n if (isConditionalStep(step) && !step.predicate(ctx)) {\n return { step, skipped: true };\n }\n } catch (err) {\n const cause = toError(err);\n const error = new PredicateError(`${step.name} predicate: ${cause.message}`);\n error.cause = cause;\n return { step, skipped: false, errors: [error] };\n }\n\n const result = await runInnerStep(step, ctx);\n if (!result.success) return { step, skipped: false, errors: [...result.errors] };\n return { step, skipped: false, output: result.data };\n}\n\n// ---------------------------------------------------------------------------\n// parallel()\n// ---------------------------------------------------------------------------\n\n/**\n * Run multiple steps concurrently and merge their outputs.\n *\n * All inner steps receive the same pre-parallel context snapshot and\n * execute via `Promise.allSettled`. On success, outputs are merged in\n * array order (deterministic). On partial failure, succeeded inner\n * steps are rolled back in reverse array order before the failure\n * propagates.\n *\n * The returned step acts as a single step from the pipeline's\n * perspective — middleware wraps the group as a whole.\n *\n * Inner steps retain their own `requires`/`provides` validation,\n * `retry`, and `timeout` behavior. Conditional steps (via `when()`)\n * are evaluated per inner step.\n *\n * @example\n * ```ts\n * const pipeline = buildPipeline({\n * name: 'checkout',\n * steps: [\n * validateOrder,\n * parallel(reserveInventory, chargePayment),\n * sendConfirmation,\n * ],\n * });\n * ```\n *\n * @param steps - Two or more steps to execute concurrently.\n * @returns A frozen {@link TypedStep} whose `Requires` is the\n * intersection of all inner steps' requires, and `Provides` is the\n * intersection of all inner steps' provides.\n */\nexport function parallel<S extends readonly TypedStep[]>(\n ...steps: [...S]\n): TypedStep<\n AsContext<UnionToIntersection<ExtractRequires<S[number]>>>,\n AsContext<UnionToIntersection<ExtractProvides<S[number]>>>\n> {\n const name = `parallel(${steps.map((s) => s.name).join(', ')})`;\n const innerSteps: readonly Step[] = steps;\n\n // ----- run: execute all inner steps concurrently ----- //\n const run: Step['run'] = async (ctx) => {\n const settled = await Promise.allSettled(innerSteps.map((step) => executeInner(step, ctx)));\n\n const succeeded: { step: Step; output: StepOutput }[] = [];\n const allErrors: Error[] = [];\n\n for (const s of settled) {\n if (s.status === 'rejected') {\n allErrors.push(toError(s.reason));\n } else {\n const r = s.value;\n if (r.skipped) continue;\n if (r.output) {\n succeeded.push({ step: r.step, output: r.output });\n } else if (r.errors) {\n allErrors.push(...r.errors);\n }\n }\n }\n\n if (allErrors.length > 0) {\n // Rollback succeeded inner steps in reverse array order (best-effort)\n for (let i = succeeded.length - 1; i >= 0; i--) {\n const { step, output } = succeeded[i];\n if (step.rollback) {\n try {\n await step.rollback(ctx, output);\n } catch {\n // Best-effort — inner rollback errors during partial failure\n // are not surfaced. The pipeline's own rollback report covers\n // the parallel step as a whole.\n }\n }\n }\n return { success: false, errors: allErrors };\n }\n\n // Merge outputs in array order (deterministic)\n const merged: StepOutput = {};\n for (const { output } of succeeded) {\n Object.assign(merged, output);\n }\n\n return { success: true, data: merged, errors: [] };\n };\n\n // ----- rollback: called when a later sequential step fails ----- //\n // The pipeline passes the merged output. Each inner step's rollback\n // receives the full merged output (a superset of what it produced).\n // This is safe because rollback handlers only read their own keys.\n const rollback: NonNullable<Step['rollback']> = async (ctx, mergedOutput) => {\n const errors: Error[] = [];\n for (let i = innerSteps.length - 1; i >= 0; i--) {\n const step = innerSteps[i];\n if (!step.rollback) continue;\n try {\n await step.rollback(ctx, mergedOutput);\n } catch (err) {\n errors.push(toError(err));\n }\n }\n if (errors.length > 0) {\n const error = new RollbackError(`${name}: ${errors.length} rollback(s) failed`);\n error.cause = errors;\n throw error;\n }\n };\n\n return Object.freeze({\n name,\n requires: undefined,\n provides: undefined,\n run,\n rollback,\n retry: undefined,\n timeout: undefined,\n }) as unknown as TypedStep<\n AsContext<UnionToIntersection<ExtractRequires<S[number]>>>,\n AsContext<UnionToIntersection<ExtractProvides<S[number]>>>\n >;\n}\n","import type {\n ExtractProvides,\n ExtractRequires,\n Step,\n StepContext,\n TypedStep,\n UnionToIntersection,\n} from './types.js';\nimport type { AsContext } from './internal.js';\nimport { runInnerStep, toError } from './internal.js';\nimport { ChoiceNoMatchError, PredicateError, RollbackError } from './errors.js';\n\n/** A [predicate, step] tuple used by {@link choice}. */\ntype BranchTuple = readonly [(ctx: Readonly<StepContext>) => boolean, TypedStep];\n\n/** Extract the Requires type from a branch tuple's step. */\ntype BranchRequires<T> = T extends readonly [unknown, infer S extends Step]\n ? ExtractRequires<S>\n : T extends Step\n ? ExtractRequires<T>\n : StepContext;\n\n/** Extract the Provides type from a branch tuple's step. */\ntype BranchProvides<T> = T extends readonly [unknown, infer S extends Step]\n ? ExtractProvides<S>\n : T extends Step\n ? ExtractProvides<T>\n : StepContext;\n\n// ---------------------------------------------------------------------------\n// Normalize args: convert trailing bare step into a [() => true, step] tuple\n// ---------------------------------------------------------------------------\n\ntype NormalizedBranch = readonly [(ctx: Readonly<StepContext>) => boolean, Step];\n\nfunction normalizeBranches(\n args: readonly (BranchTuple | TypedStep)[],\n): readonly NormalizedBranch[] {\n return args.map((arg): NormalizedBranch => {\n if (Array.isArray(arg)) return arg as unknown as NormalizedBranch;\n // Bare step → default branch\n return [() => true, arg as Step];\n });\n}\n\n// ---------------------------------------------------------------------------\n// choice()\n// ---------------------------------------------------------------------------\n\n/**\n * Execute the first branch whose predicate returns `true`.\n *\n * Similar to an AWS Step Functions Choice state — predicates are evaluated\n * in order, and the first match wins. Exactly one branch executes. If no\n * predicate matches, the step fails with a `CHOICE_NO_MATCH` error.\n *\n * A bare step (without a predicate tuple) can be passed as the last argument\n * to serve as a default branch — it is equivalent to `[() => true, step]`.\n *\n * All branches should provide the same output shape so that subsequent\n * steps can rely on a consistent context type.\n *\n * @example\n * ```ts\n * const pipeline = buildPipeline({\n * name: 'payment',\n * steps: [\n * validateOrder,\n * choice(\n * [(ctx) => ctx.method === 'card', chargeCard],\n * [(ctx) => ctx.method === 'bank', chargeBankTransfer],\n * chargeDefault, // default\n * ),\n * sendReceipt,\n * ],\n * });\n * ```\n *\n * @param branches - One or more `[predicate, step]` tuples, optionally\n * followed by a bare step as the default.\n * @returns A frozen {@link TypedStep} that executes the first matching branch.\n */\n\n// Overload: all branches are tuples (no default)\nexport function choice<B extends readonly BranchTuple[]>(\n ...branches: [...B]\n): TypedStep<\n AsContext<UnionToIntersection<BranchRequires<B[number]>>>,\n AsContext<UnionToIntersection<BranchProvides<B[number]>>>\n>;\n\n// Overload: tuples + trailing bare step as default\nexport function choice<B extends readonly BranchTuple[], D extends TypedStep>(\n ...args: [...B, D]\n): TypedStep<\n AsContext<UnionToIntersection<BranchRequires<B[number]> | ExtractRequires<D>>>,\n AsContext<UnionToIntersection<BranchProvides<B[number]> | ExtractProvides<D>>>\n>;\n\n// Implementation\nexport function choice(...args: (BranchTuple | TypedStep)[]): TypedStep<StepContext, StepContext> {\n const innerBranches = normalizeBranches(args);\n const name = `choice(${innerBranches.map(([, step]) => step.name).join(', ')})`;\n\n // Track which branch ran per execution for rollback.\n // INVARIANT: This relies on pipeline.ts storing the exact result.data\n // reference in its outputs array (pipeline.ts:344-345). If the pipeline\n // ever clones result.data, this WeakMap lookup will silently fail.\n const branchMap = new WeakMap<object, number>();\n\n const run: Step['run'] = async (ctx) => {\n for (let i = 0; i < innerBranches.length; i++) {\n const [predicate, step] = innerBranches[i];\n\n // Evaluate predicate\n let matches: boolean;\n try {\n matches = predicate(ctx);\n } catch (err) {\n const cause = toError(err);\n const error = new PredicateError(`${name} predicate: ${cause.message}`);\n error.cause = cause;\n return { success: false, errors: [error] };\n }\n\n if (!matches) continue;\n\n const result = await runInnerStep(step, ctx);\n if (!result.success) return result;\n\n // Track which branch ran for rollback\n branchMap.set(result.data, i);\n\n return { success: true, data: result.data, errors: [] };\n }\n\n // No branch matched\n return {\n success: false,\n errors: [new ChoiceNoMatchError(`${name}: no branch matched`)],\n };\n };\n\n // Rollback: only the matched branch needs rollback.\n const rollback: NonNullable<Step['rollback']> = async (ctx, output) => {\n const branchIndex = branchMap.get(output);\n if (branchIndex === undefined) return;\n const [, step] = innerBranches[branchIndex];\n if (step.rollback) {\n try {\n await step.rollback(ctx, output);\n } catch (err) {\n const error = new RollbackError(`${name}: 1 rollback(s) failed`);\n error.cause = [toError(err)];\n throw error;\n }\n }\n };\n\n return Object.freeze({\n name,\n requires: undefined,\n provides: undefined,\n run,\n rollback,\n retry: undefined,\n timeout: undefined,\n }) as unknown as TypedStep<StepContext, StepContext>;\n}\n","import type { Result } from 'composable-functions';\nimport type { ExtractProvides, Step, StepContext, StepOutput, TypedStep } from './types.js';\nimport { runInnerStep, toError } from './internal.js';\nimport { RollbackError } from './errors.js';\n\n// ---------------------------------------------------------------------------\n// Runtime step detection\n// ---------------------------------------------------------------------------\n\nfunction isStep(x: unknown): x is Step {\n return typeof x === 'object' && x !== null && 'run' in x && 'name' in x;\n}\n\n// ---------------------------------------------------------------------------\n// map()\n// ---------------------------------------------------------------------------\n\n/**\n * Iterate over a collection and run a function or step per item, concurrently.\n *\n * Similar to an AWS Step Functions Map state — extracts a collection from\n * the pipeline context, runs the callback for each item via\n * `Promise.allSettled`, and collects results into an array under the\n * given key.\n *\n * **Function form:** `(item, ctx) => result` — items can be any type.\n *\n * **Step form:** each item must be an object whose keys are spread into\n * the pipeline context before the step runs (i.e., the step receives\n * `{ ...ctx, ...item }`). The step's own `requires`/`provides`\n * validation, `retry`, and `timeout` apply per item. On partial failure,\n * succeeded items are rolled back (if the step has a rollback handler).\n *\n * @example\n * ```ts\n * // Function form\n * const pipeline = buildPipeline({\n * name: 'notify',\n * steps: [\n * map('emails', (ctx) => ctx.users, async (user) => {\n * await sendEmail(user.email);\n * return { email: user.email, sentAt: new Date() };\n * }),\n * ],\n * });\n *\n * // Step form\n * const pipeline = buildPipeline({\n * name: 'process',\n * steps: [\n * map('results', (ctx) => ctx.items, processItem),\n * ],\n * });\n * ```\n *\n * @param key - The output key under which results are collected.\n * @param collection - A selector that extracts the collection from context.\n * @param fnOrStep - A per-item function or a step to execute for each item.\n * @returns A frozen {@link TypedStep} that provides `{ [key]: Result[] }`.\n */\n\n// Overload: plain function callback\nexport function map<K extends string, Item, Result>(\n key: K,\n collection: (ctx: Readonly<StepContext>) => Item[],\n fn: (item: Item, ctx: Readonly<StepContext>) => Result | Promise<Result>,\n): TypedStep<StepContext, Record<K, Awaited<Result>[]>>;\n\n// Overload: step callback\nexport function map<K extends string, S extends TypedStep>(\n key: K,\n collection: (ctx: Readonly<StepContext>) => StepContext[],\n step: S,\n): TypedStep<StepContext, Record<K, ExtractProvides<S>[]>>;\n\n// Implementation\nexport function map(\n key: string,\n collection: (ctx: Readonly<StepContext>) => unknown[],\n fnOrStep: ((item: unknown, ctx: Readonly<StepContext>) => unknown) | Step,\n): TypedStep<StepContext, StepContext> {\n const stepMode = isStep(fnOrStep);\n const name = stepMode ? `map(${key}, ${(fnOrStep as Step).name})` : `map(${key})`;\n\n // Track per-execution data for rollback (step mode only).\n // INVARIANT: This relies on pipeline.ts storing the exact result.data\n // reference in its outputs array (pipeline.ts:344-345). If the pipeline\n // ever clones result.data, this WeakMap lookup will silently fail.\n const executionMap = new WeakMap<object, { items: unknown[]; ctx: StepContext }>();\n\n const run: Step['run'] = async (ctx) => {\n // Extract collection — selector errors are application errors, not library errors\n let items: unknown[];\n try {\n items = collection(ctx);\n } catch (err) {\n return {\n success: false,\n errors: [toError(err)],\n };\n }\n\n if (stepMode) {\n return runStepMode(fnOrStep as Step, items, ctx, name, key, executionMap);\n } else {\n return runFunctionMode(\n fnOrStep as (item: unknown, ctx: Readonly<StepContext>) => unknown,\n items,\n ctx,\n key,\n );\n }\n };\n\n // Rollback (step mode only): roll back each succeeded item in reverse.\n const rollback: Step['rollback'] = stepMode\n ? async (_ctx, output) => {\n const step = fnOrStep as Step;\n if (!step.rollback) return;\n const exec = executionMap.get(output);\n if (!exec) return;\n const results = (output as Record<string, unknown>)[key] as StepOutput[];\n const errors: Error[] = [];\n for (let i = results.length - 1; i >= 0; i--) {\n try {\n const itemCtx = { ...exec.ctx, ...(exec.items[i] as StepContext) };\n await step.rollback(itemCtx, results[i]);\n } catch (err) {\n errors.push(toError(err));\n }\n }\n if (errors.length > 0) {\n const error = new RollbackError(`${name}: ${errors.length} rollback(s) failed`);\n error.cause = errors;\n throw error;\n }\n }\n : undefined;\n\n return Object.freeze({\n name,\n requires: undefined,\n provides: undefined,\n run,\n rollback,\n retry: undefined,\n timeout: undefined,\n }) as unknown as TypedStep<StepContext, StepContext>;\n}\n\n// ---------------------------------------------------------------------------\n// Step mode: run inner step per item with validation + partial rollback\n// ---------------------------------------------------------------------------\n\nasync function runStepMode(\n step: Step,\n items: unknown[],\n ctx: Readonly<StepContext>,\n name: string,\n key: string,\n executionMap: WeakMap<object, { items: unknown[]; ctx: StepContext }>,\n): Promise<Result<StepOutput>> {\n const settled = await Promise.allSettled(\n items.map(async (item) => {\n const itemCtx = { ...ctx, ...(item as StepContext) };\n return runInnerStep(step, itemCtx);\n }),\n );\n\n const succeeded: { index: number; output: StepOutput }[] = [];\n const allErrors: Error[] = [];\n\n for (let i = 0; i < settled.length; i++) {\n const s = settled[i];\n if (s.status === 'rejected') {\n allErrors.push(toError(s.reason));\n } else if (!s.value.success) {\n allErrors.push(...s.value.errors);\n } else {\n succeeded.push({ index: i, output: s.value.data });\n }\n }\n\n if (allErrors.length > 0) {\n // Roll back succeeded items in reverse order (best-effort)\n if (step.rollback) {\n for (let i = succeeded.length - 1; i >= 0; i--) {\n try {\n const itemCtx = { ...ctx, ...(items[succeeded[i].index] as StepContext) };\n await step.rollback(itemCtx, succeeded[i].output);\n } catch {\n // Best-effort — swallowed during partial failure\n }\n }\n }\n return { success: false, errors: allErrors };\n }\n\n // Collect results in original order\n const results = succeeded.map((s) => s.output);\n const data: StepOutput = { [key]: results };\n executionMap.set(data, { items, ctx: { ...ctx } });\n return { success: true, data, errors: [] };\n}\n\n// ---------------------------------------------------------------------------\n// Function mode: run callback per item, no rollback\n// ---------------------------------------------------------------------------\n\nasync function runFunctionMode(\n fn: (item: unknown, ctx: Readonly<StepContext>) => unknown,\n items: unknown[],\n ctx: Readonly<StepContext>,\n key: string,\n): Promise<Result<StepOutput>> {\n const settled = await Promise.allSettled(items.map(async (item) => fn(item, ctx)));\n\n const results: unknown[] = [];\n const allErrors: Error[] = [];\n\n for (const s of settled) {\n if (s.status === 'rejected') {\n allErrors.push(toError(s.reason));\n } else {\n results.push(s.value);\n }\n }\n\n if (allErrors.length > 0) {\n return { success: false, errors: allErrors };\n }\n\n const data: StepOutput = { [key]: results };\n return { success: true, data, errors: [] };\n}\n","import type { Result } from 'composable-functions';\nimport type { Step, StepContext, StepOutput, TypedStep } from './types.js';\nimport { toError } from './internal.js';\n\n// ---------------------------------------------------------------------------\n// filter()\n// ---------------------------------------------------------------------------\n\n/**\n * Filter a collection from context using a predicate, concurrently.\n *\n * Extracts a collection from the pipeline context, evaluates the\n * predicate for each item via `Promise.allSettled`, and collects\n * items that pass into an array under the given key. Original order\n * is preserved.\n *\n * The predicate can be sync or async. If any predicate throws, the\n * entire step fails — no partial results are returned.\n *\n * There is no rollback (filtering is a pure operation with nothing\n * to undo).\n *\n * @example\n * ```ts\n * const pipeline = buildPipeline({\n * name: 'notify',\n * steps: [\n * filter('eligible', (ctx) => ctx.users, (user) => user.optedIn),\n * map('emails', (ctx) => ctx.eligible, sendEmail),\n * ],\n * });\n *\n * // Async predicate\n * filter('valid', (ctx) => ctx.orders, async (order) => {\n * const inventory = await checkInventory(order.sku);\n * return inventory.available >= order.quantity;\n * });\n * ```\n *\n * @param key - The output key under which filtered results are collected.\n * @param collection - A selector that extracts the collection from context.\n * @param predicate - A per-item predicate. Return `true` to keep, `false` to discard.\n * @returns A frozen {@link TypedStep} that provides `{ [key]: Item[] }`.\n */\nexport function filter<K extends string, Item>(\n key: K,\n collection: (ctx: Readonly<StepContext>) => Item[],\n predicate: (item: Item, ctx: Readonly<StepContext>) => boolean | Promise<boolean>,\n): TypedStep<StepContext, Record<K, Item[]>> {\n const name = `filter(${key})`;\n\n const run: Step['run'] = async (ctx) => {\n let items: unknown[];\n try {\n items = collection(ctx);\n } catch (err) {\n return {\n success: false,\n errors: [toError(err)],\n };\n }\n\n return runFilter(\n items,\n ctx,\n predicate as (item: unknown, ctx: Readonly<StepContext>) => boolean | Promise<boolean>,\n key,\n );\n };\n\n return Object.freeze({\n name,\n requires: undefined,\n provides: undefined,\n run,\n rollback: undefined,\n retry: undefined,\n timeout: undefined,\n }) as unknown as TypedStep<StepContext, Record<K, Item[]>>;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nasync function runFilter(\n items: unknown[],\n ctx: Readonly<StepContext>,\n predicate: (item: unknown, ctx: Readonly<StepContext>) => boolean | Promise<boolean>,\n key: string,\n): Promise<Result<StepOutput>> {\n const settled = await Promise.allSettled(items.map(async (item) => predicate(item, ctx)));\n\n const results: unknown[] = [];\n const allErrors: Error[] = [];\n\n for (let i = 0; i < settled.length; i++) {\n const s = settled[i];\n if (s.status === 'rejected') {\n allErrors.push(toError(s.reason));\n } else if (s.value) {\n results.push(items[i]);\n }\n }\n\n if (allErrors.length > 0) {\n return { success: false, errors: allErrors };\n }\n\n const data: StepOutput = { [key]: results };\n return { success: true, data, errors: [] };\n}\n","import type { Result } from 'composable-functions';\nimport type { Step, StepContext, StepOutput, TypedStep } from './types.js';\nimport { toError } from './internal.js';\n\n// ---------------------------------------------------------------------------\n// flatMap()\n// ---------------------------------------------------------------------------\n\n/**\n * Map each item in a collection to an array, then flatten one level.\n *\n * Extracts a collection from the pipeline context, runs the callback\n * for each item via `Promise.allSettled`, and flattens the per-item\n * arrays into a single array under the given key.\n *\n * The callback can be sync or async. If any callback throws, the\n * entire step fails — no partial results are returned.\n *\n * There is no rollback (pure transformation with nothing to undo).\n *\n * @example\n * ```ts\n * // Expand orders into line items\n * const pipeline = buildPipeline({\n * name: 'process',\n * steps: [\n * flatMap('lineItems', (ctx) => ctx.orders, (order) => order.items),\n * ],\n * });\n *\n * // Async callback\n * flatMap('emails', (ctx) => ctx.teams, async (team) => {\n * const members = await fetchMembers(team.id);\n * return members.map((m) => m.email);\n * });\n * ```\n *\n * @param key - The output key under which flattened results are collected.\n * @param collection - A selector that extracts the collection from context.\n * @param fn - A per-item callback that returns an array (or Promise of array).\n * @returns A frozen {@link TypedStep} that provides `{ [key]: Item[] }`.\n */\nexport function flatMap<K extends string, Item, Result>(\n key: K,\n collection: (ctx: Readonly<StepContext>) => Item[],\n fn: (item: Item, ctx: Readonly<StepContext>) => Result[] | Promise<Result[]>,\n): TypedStep<StepContext, Record<K, Result[]>> {\n const name = `flatMap(${key})`;\n\n const run: Step['run'] = async (ctx) => {\n let items: unknown[];\n try {\n items = collection(ctx);\n } catch (err) {\n return {\n success: false,\n errors: [toError(err)],\n };\n }\n\n return runFlatMap(\n items,\n ctx,\n fn as (item: unknown, ctx: Readonly<StepContext>) => unknown[] | Promise<unknown[]>,\n key,\n );\n };\n\n return Object.freeze({\n name,\n requires: undefined,\n provides: undefined,\n run,\n rollback: undefined,\n retry: undefined,\n timeout: undefined,\n }) as unknown as TypedStep<StepContext, Record<K, Result[]>>;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nasync function runFlatMap(\n items: unknown[],\n ctx: Readonly<StepContext>,\n fn: (item: unknown, ctx: Readonly<StepContext>) => unknown[] | Promise<unknown[]>,\n key: string,\n): Promise<Result<StepOutput>> {\n const settled = await Promise.allSettled(items.map(async (item) => fn(item, ctx)));\n\n const results: unknown[] = [];\n const allErrors: Error[] = [];\n\n for (const s of settled) {\n if (s.status === 'rejected') {\n allErrors.push(toError(s.reason));\n } else {\n results.push(...s.value);\n }\n }\n\n if (allErrors.length > 0) {\n return { success: false, errors: allErrors };\n }\n\n const data: StepOutput = { [key]: results };\n return { success: true, data, errors: [] };\n}\n","import type { ParserSchema } from 'composable-functions';\nimport type { Step, StepContext, TypedStep } from './types.js';\nimport type { StepMiddleware } from './middleware.js';\nimport { buildPipeline } from './pipeline.js';\nimport type { Pipeline } from './pipeline.js';\n\n// ---------------------------------------------------------------------------\n// Builder types\n// ---------------------------------------------------------------------------\n\n/**\n * A fluent pipeline builder that progressively narrows the accumulated\n * context type as steps are added.\n *\n * Each method returns a new, frozen builder — builders are immutable.\n * This means you can safely fork a builder to create variants:\n *\n * ```ts\n * const base = createPipeline('order').step(validate);\n * const withCharge = base.step(charge).build();\n * const withoutCharge = base.build(); // unaffected by the fork\n * ```\n *\n * @typeParam Args - The pipeline's initial input type.\n * @typeParam Ctx - The accumulated context type so far (grows with each `.step()`).\n */\nexport type PipelineBuilder<Args extends StepContext, Ctx extends StepContext> = {\n /**\n * Add a step to the pipeline.\n *\n * The step's `Requires` type must be satisfied by the current `Ctx`.\n * The returned builder's `Ctx` expands to include the step's `Provides`.\n *\n * @typeParam Provides - The output type of the step being added.\n * @param step - A {@link TypedStep} (from `defineStep` or `when`).\n * @returns A new builder with the expanded context type.\n */\n readonly step: <Provides extends StepContext>(\n step: TypedStep<Ctx, Provides>,\n ) => PipelineBuilder<Args, Ctx & Provides>;\n\n /**\n * Add middleware to the pipeline.\n *\n * Middleware is applied to every step. Multiple `.use()` calls\n * accumulate — earlier middleware is outermost (executes first).\n *\n * @param middleware - One or more {@link StepMiddleware} functions.\n * @returns A new builder with the middleware added.\n */\n readonly use: (...middleware: StepMiddleware[]) => PipelineBuilder<Args, Ctx>;\n\n /**\n * Build the pipeline.\n *\n * @returns A frozen {@link Pipeline} ready to execute with `run()`.\n */\n readonly build: () => Pipeline<Args, Ctx>;\n};\n\n// ---------------------------------------------------------------------------\n// Internal builder state (immutable — each method returns a new builder)\n// ---------------------------------------------------------------------------\n\ntype BuilderState = {\n readonly name: string;\n readonly steps: readonly Step[];\n readonly middleware: readonly StepMiddleware[];\n readonly argsSchema: ParserSchema<StepContext> | undefined;\n readonly strict: boolean;\n};\n\nfunction makeBuilder<Args extends StepContext, Ctx extends StepContext>(\n state: BuilderState,\n): PipelineBuilder<Args, Ctx> {\n return Object.freeze({\n step: <Provides extends StepContext>(step: TypedStep<Ctx, Provides>) =>\n makeBuilder<Args, Ctx & Provides>({\n ...state,\n steps: [...state.steps, step],\n }),\n\n use: (...middleware: StepMiddleware[]) =>\n makeBuilder<Args, Ctx>({\n ...state,\n middleware: [...state.middleware, ...middleware],\n }),\n\n build: () =>\n buildPipeline({\n name: state.name,\n steps: state.steps,\n middleware: state.middleware.length > 0 ? state.middleware : undefined,\n argsSchema: state.argsSchema as ParserSchema<Args> | undefined,\n strict: state.strict || undefined,\n }) as Pipeline<Args, Ctx>,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Start building a pipeline with the fluent builder API.\n *\n * The builder gives progressive type narrowing — each `.step()` call\n * extends the known context type, so TypeScript catches mismatches\n * at compile time.\n *\n * **Type-only args** (no runtime validation):\n * ```ts\n * createPipeline<{ orderId: string }>('placeOrder')\n * .step(validateOrder)\n * .step(chargePayment)\n * .build();\n * ```\n *\n * **Schema args** (runtime validation + type inference):\n * ```ts\n * createPipeline('placeOrder', z.object({ orderId: z.string() }))\n * .step(validateOrder)\n * .step(chargePayment)\n * .build();\n * ```\n *\n * **Strict mode** (no schema):\n * ```ts\n * createPipeline('placeOrder', { strict: true })\n * ```\n *\n * **Schema + strict mode:**\n * ```ts\n * createPipeline('placeOrder', z.object({ orderId: z.string() }), { strict: true })\n * ```\n *\n * @typeParam Args - The pipeline's input type. Inferred from `argsSchema`\n * if provided, otherwise specify via generic parameter.\n * @param name - Pipeline name, used in metadata and error messages.\n * @param schemaOrOptions - A schema for runtime args validation, or a\n * {@link PipelineOptions} object.\n * @param options - When the second argument is a schema, pass options here.\n * @returns A frozen {@link PipelineBuilder} ready for `.step()`,\n * `.use()`, and `.build()`.\n */\nexport type PipelineOptions = {\n strict?: boolean;\n};\n\n// Overload: name only\nexport function createPipeline<Args extends StepContext>(name: string): PipelineBuilder<Args, Args>;\n\n// Overload: name + schema\nexport function createPipeline<Args extends StepContext>(\n name: string,\n argsSchema: ParserSchema<Args>,\n): PipelineBuilder<Args, Args>;\n\n// Overload: name + options (no schema)\nexport function createPipeline<Args extends StepContext>(\n name: string,\n options: PipelineOptions,\n): PipelineBuilder<Args, Args>;\n\n// Overload: name + schema + options\nexport function createPipeline<Args extends StepContext>(\n name: string,\n argsSchema: ParserSchema<Args>,\n options: PipelineOptions,\n): PipelineBuilder<Args, Args>;\n\n// Implementation\nexport function createPipeline<Args extends StepContext>(\n name: string,\n schemaOrOptions?: ParserSchema<Args> | PipelineOptions,\n options?: PipelineOptions,\n): PipelineBuilder<Args, Args> {\n let argsSchema: ParserSchema<Args> | undefined;\n let strict = false;\n\n if (schemaOrOptions != null) {\n if ('safeParse' in schemaOrOptions) {\n argsSchema = schemaOrOptions;\n strict = options?.strict ?? false;\n } else {\n strict = schemaOrOptions.strict ?? false;\n }\n }\n\n return makeBuilder<Args, Args>({\n name,\n steps: [],\n middleware: [],\n argsSchema,\n strict,\n });\n}\n"],"mappings":"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","names":["state"]}
package/dist/index.d.cts CHANGED
@@ -351,7 +351,7 @@ declare function defineStep<Requires extends StepContext, Provides extends StepC
351
351
  * }
352
352
  * ```
353
353
  */
354
- type RunsheetErrorCode = 'REQUIRES_VALIDATION' | 'PROVIDES_VALIDATION' | 'ARGS_VALIDATION' | 'PREDICATE' | 'TIMEOUT' | 'RETRY_EXHAUSTED' | 'STRICT_OVERLAP';
354
+ type RunsheetErrorCode = 'REQUIRES_VALIDATION' | 'PROVIDES_VALIDATION' | 'ARGS_VALIDATION' | 'PREDICATE' | 'TIMEOUT' | 'RETRY_EXHAUSTED' | 'STRICT_OVERLAP' | 'CHOICE_NO_MATCH' | 'ROLLBACK' | 'UNKNOWN';
355
355
  /**
356
356
  * Base error class for all errors produced by the runsheet library.
357
357
  *
@@ -361,8 +361,8 @@ type RunsheetErrorCode = 'REQUIRES_VALIDATION' | 'PROVIDES_VALIDATION' | 'ARGS_V
361
361
  * library itself produced it.
362
362
  *
363
363
  * Use `instanceof RunsheetError` to distinguish library errors from
364
- * application errors, and the `code` property to identify the
365
- * specific failure.
364
+ * application errors. Use `instanceof` on a subclass (e.g.,
365
+ * `TimeoutError`) or check the `code` property for specific failures.
366
366
  */
367
367
  declare class RunsheetError extends Error {
368
368
  /** Discriminant code identifying the type of library error. */
@@ -373,6 +373,56 @@ declare class RunsheetError extends Error {
373
373
  */
374
374
  constructor(code: RunsheetErrorCode, message: string);
375
375
  }
376
+ /** Schema validation failed on the accumulated context before a step ran. */
377
+ declare class RequiresValidationError extends RunsheetError {
378
+ constructor(message: string);
379
+ }
380
+ /** Schema validation failed on a step's output after it ran. */
381
+ declare class ProvidesValidationError extends RunsheetError {
382
+ constructor(message: string);
383
+ }
384
+ /** Schema validation failed on the pipeline's input arguments. */
385
+ declare class ArgsValidationError extends RunsheetError {
386
+ constructor(message: string);
387
+ }
388
+ /** A `when()` or `choice()` predicate threw an error. */
389
+ declare class PredicateError extends RunsheetError {
390
+ constructor(message: string);
391
+ }
392
+ /** A step exceeded its configured timeout. */
393
+ declare class TimeoutError extends RunsheetError {
394
+ /** The timeout duration in milliseconds that was exceeded. */
395
+ readonly timeoutMs: number;
396
+ constructor(message: string, timeoutMs: number);
397
+ }
398
+ /** A step failed after exhausting all retry attempts. */
399
+ declare class RetryExhaustedError extends RunsheetError {
400
+ /** Total number of attempts (initial + retries). */
401
+ readonly attempts: number;
402
+ constructor(message: string, attempts: number);
403
+ }
404
+ /** Two steps provide the same key (strict mode, detected at build time). */
405
+ declare class StrictOverlapError extends RunsheetError {
406
+ /** The key that is provided by multiple steps. */
407
+ readonly key: string;
408
+ /** The names of the two steps that both provide the key. */
409
+ readonly steps: readonly [string, string];
410
+ constructor(message: string, key: string, steps: readonly [string, string]);
411
+ }
412
+ /** No branch matched in a `choice()` step. */
413
+ declare class ChoiceNoMatchError extends RunsheetError {
414
+ constructor(message: string);
415
+ }
416
+ /** A non-Error value was thrown and caught by the pipeline engine. */
417
+ declare class UnknownError extends RunsheetError {
418
+ /** The original thrown value before stringification. */
419
+ readonly originalValue: unknown;
420
+ constructor(message: string, originalValue: unknown);
421
+ }
422
+ /** One or more rollback handlers failed in a combinator. */
423
+ declare class RollbackError extends RunsheetError {
424
+ constructor(message: string);
425
+ }
376
426
 
377
427
  /**
378
428
  * Metadata about the step being executed, passed to middleware.
@@ -577,6 +627,7 @@ declare function when<Requires extends StepContext, Provides extends StepContext
577
627
 
578
628
  /** Ensure a type satisfies StepContext, falling back to StepContext. */
579
629
  type AsContext<T> = T extends StepContext ? T : StepContext;
630
+
580
631
  /**
581
632
  * Run multiple steps concurrently and merge their outputs.
582
633
  *
@@ -612,6 +663,168 @@ type AsContext<T> = T extends StepContext ? T : StepContext;
612
663
  */
613
664
  declare function parallel<S extends readonly TypedStep[]>(...steps: [...S]): TypedStep<AsContext<UnionToIntersection<ExtractRequires<S[number]>>>, AsContext<UnionToIntersection<ExtractProvides<S[number]>>>>;
614
665
 
666
+ /** A [predicate, step] tuple used by {@link choice}. */
667
+ type BranchTuple = readonly [(ctx: Readonly<StepContext>) => boolean, TypedStep];
668
+ /** Extract the Requires type from a branch tuple's step. */
669
+ type BranchRequires<T> = T extends readonly [unknown, infer S extends Step] ? ExtractRequires<S> : T extends Step ? ExtractRequires<T> : StepContext;
670
+ /** Extract the Provides type from a branch tuple's step. */
671
+ type BranchProvides<T> = T extends readonly [unknown, infer S extends Step] ? ExtractProvides<S> : T extends Step ? ExtractProvides<T> : StepContext;
672
+ /**
673
+ * Execute the first branch whose predicate returns `true`.
674
+ *
675
+ * Similar to an AWS Step Functions Choice state — predicates are evaluated
676
+ * in order, and the first match wins. Exactly one branch executes. If no
677
+ * predicate matches, the step fails with a `CHOICE_NO_MATCH` error.
678
+ *
679
+ * A bare step (without a predicate tuple) can be passed as the last argument
680
+ * to serve as a default branch — it is equivalent to `[() => true, step]`.
681
+ *
682
+ * All branches should provide the same output shape so that subsequent
683
+ * steps can rely on a consistent context type.
684
+ *
685
+ * @example
686
+ * ```ts
687
+ * const pipeline = buildPipeline({
688
+ * name: 'payment',
689
+ * steps: [
690
+ * validateOrder,
691
+ * choice(
692
+ * [(ctx) => ctx.method === 'card', chargeCard],
693
+ * [(ctx) => ctx.method === 'bank', chargeBankTransfer],
694
+ * chargeDefault, // default
695
+ * ),
696
+ * sendReceipt,
697
+ * ],
698
+ * });
699
+ * ```
700
+ *
701
+ * @param branches - One or more `[predicate, step]` tuples, optionally
702
+ * followed by a bare step as the default.
703
+ * @returns A frozen {@link TypedStep} that executes the first matching branch.
704
+ */
705
+ declare function choice<B extends readonly BranchTuple[]>(...branches: [...B]): TypedStep<AsContext<UnionToIntersection<BranchRequires<B[number]>>>, AsContext<UnionToIntersection<BranchProvides<B[number]>>>>;
706
+ declare function choice<B extends readonly BranchTuple[], D extends TypedStep>(...args: [...B, D]): TypedStep<AsContext<UnionToIntersection<BranchRequires<B[number]> | ExtractRequires<D>>>, AsContext<UnionToIntersection<BranchProvides<B[number]> | ExtractProvides<D>>>>;
707
+
708
+ /**
709
+ * Iterate over a collection and run a function or step per item, concurrently.
710
+ *
711
+ * Similar to an AWS Step Functions Map state — extracts a collection from
712
+ * the pipeline context, runs the callback for each item via
713
+ * `Promise.allSettled`, and collects results into an array under the
714
+ * given key.
715
+ *
716
+ * **Function form:** `(item, ctx) => result` — items can be any type.
717
+ *
718
+ * **Step form:** each item must be an object whose keys are spread into
719
+ * the pipeline context before the step runs (i.e., the step receives
720
+ * `{ ...ctx, ...item }`). The step's own `requires`/`provides`
721
+ * validation, `retry`, and `timeout` apply per item. On partial failure,
722
+ * succeeded items are rolled back (if the step has a rollback handler).
723
+ *
724
+ * @example
725
+ * ```ts
726
+ * // Function form
727
+ * const pipeline = buildPipeline({
728
+ * name: 'notify',
729
+ * steps: [
730
+ * map('emails', (ctx) => ctx.users, async (user) => {
731
+ * await sendEmail(user.email);
732
+ * return { email: user.email, sentAt: new Date() };
733
+ * }),
734
+ * ],
735
+ * });
736
+ *
737
+ * // Step form
738
+ * const pipeline = buildPipeline({
739
+ * name: 'process',
740
+ * steps: [
741
+ * map('results', (ctx) => ctx.items, processItem),
742
+ * ],
743
+ * });
744
+ * ```
745
+ *
746
+ * @param key - The output key under which results are collected.
747
+ * @param collection - A selector that extracts the collection from context.
748
+ * @param fnOrStep - A per-item function or a step to execute for each item.
749
+ * @returns A frozen {@link TypedStep} that provides `{ [key]: Result[] }`.
750
+ */
751
+ declare function map<K extends string, Item, Result>(key: K, collection: (ctx: Readonly<StepContext>) => Item[], fn: (item: Item, ctx: Readonly<StepContext>) => Result | Promise<Result>): TypedStep<StepContext, Record<K, Awaited<Result>[]>>;
752
+ declare function map<K extends string, S extends TypedStep>(key: K, collection: (ctx: Readonly<StepContext>) => StepContext[], step: S): TypedStep<StepContext, Record<K, ExtractProvides<S>[]>>;
753
+
754
+ /**
755
+ * Filter a collection from context using a predicate, concurrently.
756
+ *
757
+ * Extracts a collection from the pipeline context, evaluates the
758
+ * predicate for each item via `Promise.allSettled`, and collects
759
+ * items that pass into an array under the given key. Original order
760
+ * is preserved.
761
+ *
762
+ * The predicate can be sync or async. If any predicate throws, the
763
+ * entire step fails — no partial results are returned.
764
+ *
765
+ * There is no rollback (filtering is a pure operation with nothing
766
+ * to undo).
767
+ *
768
+ * @example
769
+ * ```ts
770
+ * const pipeline = buildPipeline({
771
+ * name: 'notify',
772
+ * steps: [
773
+ * filter('eligible', (ctx) => ctx.users, (user) => user.optedIn),
774
+ * map('emails', (ctx) => ctx.eligible, sendEmail),
775
+ * ],
776
+ * });
777
+ *
778
+ * // Async predicate
779
+ * filter('valid', (ctx) => ctx.orders, async (order) => {
780
+ * const inventory = await checkInventory(order.sku);
781
+ * return inventory.available >= order.quantity;
782
+ * });
783
+ * ```
784
+ *
785
+ * @param key - The output key under which filtered results are collected.
786
+ * @param collection - A selector that extracts the collection from context.
787
+ * @param predicate - A per-item predicate. Return `true` to keep, `false` to discard.
788
+ * @returns A frozen {@link TypedStep} that provides `{ [key]: Item[] }`.
789
+ */
790
+ declare function filter<K extends string, Item>(key: K, collection: (ctx: Readonly<StepContext>) => Item[], predicate: (item: Item, ctx: Readonly<StepContext>) => boolean | Promise<boolean>): TypedStep<StepContext, Record<K, Item[]>>;
791
+
792
+ /**
793
+ * Map each item in a collection to an array, then flatten one level.
794
+ *
795
+ * Extracts a collection from the pipeline context, runs the callback
796
+ * for each item via `Promise.allSettled`, and flattens the per-item
797
+ * arrays into a single array under the given key.
798
+ *
799
+ * The callback can be sync or async. If any callback throws, the
800
+ * entire step fails — no partial results are returned.
801
+ *
802
+ * There is no rollback (pure transformation with nothing to undo).
803
+ *
804
+ * @example
805
+ * ```ts
806
+ * // Expand orders into line items
807
+ * const pipeline = buildPipeline({
808
+ * name: 'process',
809
+ * steps: [
810
+ * flatMap('lineItems', (ctx) => ctx.orders, (order) => order.items),
811
+ * ],
812
+ * });
813
+ *
814
+ * // Async callback
815
+ * flatMap('emails', (ctx) => ctx.teams, async (team) => {
816
+ * const members = await fetchMembers(team.id);
817
+ * return members.map((m) => m.email);
818
+ * });
819
+ * ```
820
+ *
821
+ * @param key - The output key under which flattened results are collected.
822
+ * @param collection - A selector that extracts the collection from context.
823
+ * @param fn - A per-item callback that returns an array (or Promise of array).
824
+ * @returns A frozen {@link TypedStep} that provides `{ [key]: Item[] }`.
825
+ */
826
+ declare function flatMap<K extends string, Item, Result>(key: K, collection: (ctx: Readonly<StepContext>) => Item[], fn: (item: Item, ctx: Readonly<StepContext>) => Result[] | Promise<Result[]>): TypedStep<StepContext, Record<K, Result[]>>;
827
+
615
828
  /**
616
829
  * A fluent pipeline builder that progressively narrows the accumulated
617
830
  * context type as steps are added.
@@ -707,4 +920,4 @@ declare function createPipeline<Args extends StepContext>(name: string, argsSche
707
920
  declare function createPipeline<Args extends StepContext>(name: string, options: PipelineOptions): PipelineBuilder<Args, Args>;
708
921
  declare function createPipeline<Args extends StepContext>(name: string, argsSchema: ParserSchema<Args>, options: PipelineOptions): PipelineBuilder<Args, Args>;
709
922
 
710
- export { type ConditionalStep, type ExtractProvides, type ExtractRequires, type Pipeline, type PipelineBuilder, type PipelineConfig, type PipelineExecutionMeta, type PipelineFailure, type PipelineResult, type PipelineSuccess, type RetryPolicy, type RollbackFailure, type RollbackReport, RunsheetError, type RunsheetErrorCode, type Step, type StepConfig, type StepContext, type StepExecutor, type StepInfo, type StepMiddleware, type StepOutput, type TypedStep, buildPipeline, createPipeline, defineStep, parallel, when };
923
+ export { ArgsValidationError, ChoiceNoMatchError, type ConditionalStep, type ExtractProvides, type ExtractRequires, type Pipeline, type PipelineBuilder, type PipelineConfig, type PipelineExecutionMeta, type PipelineFailure, type PipelineResult, type PipelineSuccess, PredicateError, ProvidesValidationError, RequiresValidationError, RetryExhaustedError, type RetryPolicy, RollbackError, type RollbackFailure, type RollbackReport, RunsheetError, type RunsheetErrorCode, type Step, type StepConfig, type StepContext, type StepExecutor, type StepInfo, type StepMiddleware, type StepOutput, StrictOverlapError, TimeoutError, type TypedStep, UnknownError, buildPipeline, choice, createPipeline, defineStep, filter, flatMap, map, parallel, when };