@jagreehal/workflow 1.4.0 → 1.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.
package/dist/index.d.cts CHANGED
@@ -1,2 +1,3266 @@
1
- export { AsyncResult, BackoffStrategy, CauseOf, EmptyInputError, ErrorOf, Errors, ExtractCause, ExtractError, ExtractValue, MaybeAsyncResult, PromiseRejectedError, PromiseRejectionCause, Result, RetryOptions, RunOptions, RunOptionsWithCatch, RunOptionsWithoutCatch, RunStep, STEP_TIMEOUT_MARKER, ScopeType, SettledError, StepOptions, StepTimeoutError, StepTimeoutMarkerMeta, TimeoutOptions, UnexpectedCause, UnexpectedError, UnexpectedStepFailureCause, UnwrapError, WorkflowEvent, all, allAsync, allSettled, allSettledAsync, andThen, any, anyAsync, err, from, fromNullable, fromPromise, getStepTimeoutMeta, isErr, isOk, isStepTimeoutError, isUnexpectedError, map, mapError, mapErrorTry, mapTry, match, ok, partition, run, tap, tapError, tryAsync, unwrap, unwrapOr, unwrapOrElse } from './core.cjs';
2
- export { AnyResultFn, ApprovalRejected, ApprovalStepOptions, CausesOfDeps, ErrorsOfDeps, PendingApproval, ResumeState, ResumeStateEntry, StepCache, Workflow, WorkflowOptions, WorkflowOptionsStrict, WorkflowStrict, clearStep, createApprovalStep, createHITLCollector, createStepCollector, createWorkflow, getPendingApprovals, hasPendingApproval, injectApproval, isApprovalRejected, isPendingApproval, isStepComplete, pendingApproval } from './workflow.cjs';
1
+ import { WorkflowEvent, Result, AsyncResult, UnexpectedError, StepOptions, RetryOptions, TimeoutOptions, StepFailureMeta } from './core.cjs';
2
+ export { BackoffStrategy, CauseOf, EmptyInputError, ErrorOf, Errors, ExtractCause, ExtractError, ExtractValue, MaybeAsyncResult, PromiseRejectedError, PromiseRejectionCause, RunOptions, RunOptionsWithCatch, RunOptionsWithoutCatch, RunStep, STEP_TIMEOUT_MARKER, ScopeType, SettledError, StepTimeoutError, StepTimeoutMarkerMeta, UnexpectedCause, UnexpectedStepFailureCause, UnwrapError, all, allAsync, allSettled, allSettledAsync, andThen, any, anyAsync, err, from, fromNullable, fromPromise, getStepTimeoutMeta, isErr, isOk, isStepTimeoutError, isUnexpectedError, map, mapError, mapErrorTry, mapTry, match, ok, partition, run, tap, tapError, tryAsync, unwrap, unwrapOr, unwrapOrElse } from './core.cjs';
3
+ import { ResumeState, ResumeStateEntry, Workflow, WorkflowStrict, StepCache, AnyResultFn as AnyResultFn$1, ErrorsOfDeps as ErrorsOfDeps$1 } from './workflow.cjs';
4
+ export { ApprovalRejected, ApprovalStepOptions, CausesOfDeps, PendingApproval, WorkflowOptions, WorkflowOptionsStrict, clearStep, createApprovalStep, createHITLCollector, createStepCollector, createWorkflow, getPendingApprovals, hasPendingApproval, injectApproval, isApprovalRejected, isPendingApproval, isStepComplete, pendingApproval } from './workflow.cjs';
5
+ import { CollectableEvent, VisualizerOptions, DecisionStartEvent, DecisionBranchEvent, DecisionEndEvent, OutputFormat } from './visualize.cjs';
6
+
7
+ /**
8
+ * @jagreehal/workflow/conditional
9
+ *
10
+ * Conditional step execution helpers for workflows.
11
+ * These helpers allow you to conditionally execute steps based on runtime conditions,
12
+ * with proper event emission for skipped steps.
13
+ */
14
+
15
+ /**
16
+ * Options for conditional execution.
17
+ */
18
+ type ConditionalOptions = {
19
+ /**
20
+ * Human-readable name for the conditional step.
21
+ * Used in step_skipped events for debugging and visualization.
22
+ */
23
+ name?: string;
24
+ /**
25
+ * Stable identity key for the conditional step.
26
+ * Used in step_skipped events for tracking and visualization.
27
+ */
28
+ key?: string;
29
+ /**
30
+ * Optional reason explaining why the step was skipped.
31
+ * Included in step_skipped events.
32
+ */
33
+ reason?: string;
34
+ };
35
+ /**
36
+ * Context for conditional execution, used to emit events.
37
+ */
38
+ type ConditionalContext = {
39
+ /**
40
+ * The workflow ID for event emission.
41
+ */
42
+ workflowId: string;
43
+ /**
44
+ * Event emitter function.
45
+ */
46
+ onEvent?: (event: WorkflowEvent<unknown>) => void;
47
+ };
48
+ /**
49
+ * Type for operations that can be either sync or async.
50
+ */
51
+ type MaybeAsync<T> = T | Promise<T>;
52
+ /**
53
+ * Type for the operation function passed to conditional helpers.
54
+ */
55
+ type Operation<T> = () => MaybeAsync<T>;
56
+ /**
57
+ * Run a step only if condition is true, return undefined if skipped.
58
+ *
59
+ * Use this when you want to conditionally execute a step and handle
60
+ * the undefined case yourself. For a version with a default value,
61
+ * use `whenOr`.
62
+ *
63
+ * @param condition - Boolean condition to evaluate
64
+ * @param operation - Function that performs the step (only called if condition is true)
65
+ * @param options - Optional configuration for the conditional step
66
+ * @param ctx - Optional context for event emission
67
+ * @returns The result of the operation if condition is true, undefined otherwise
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * const result = await workflow(async (step) => {
72
+ * const user = await step(fetchUser(id));
73
+ *
74
+ * // Only runs if user is premium
75
+ * const premium = await when(
76
+ * user.isPremium,
77
+ * () => step(() => fetchPremiumData(user.id), { name: 'premium-data' }),
78
+ * { name: 'check-premium', reason: 'User is not premium' }
79
+ * );
80
+ *
81
+ * return { user, premium };
82
+ * });
83
+ * ```
84
+ */
85
+ declare function when<T>(condition: boolean, operation: Operation<T>, options?: ConditionalOptions, ctx?: ConditionalContext): Promise<T | undefined>;
86
+ /**
87
+ * Synchronous overload for when the operation returns a non-Promise value.
88
+ */
89
+ declare function when<T>(condition: boolean, operation: () => T, options?: ConditionalOptions, ctx?: ConditionalContext): T | undefined | Promise<T | undefined>;
90
+ /**
91
+ * Run a step only if condition is false, return undefined if skipped.
92
+ *
93
+ * Use this when you want to conditionally execute a step when a condition
94
+ * is NOT met. For a version with a default value, use `unlessOr`.
95
+ *
96
+ * @param condition - Boolean condition to evaluate
97
+ * @param operation - Function that performs the step (only called if condition is false)
98
+ * @param options - Optional configuration for the conditional step
99
+ * @param ctx - Optional context for event emission
100
+ * @returns The result of the operation if condition is false, undefined otherwise
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * const result = await workflow(async (step) => {
105
+ * const user = await step(fetchUser(id));
106
+ *
107
+ * // Only runs if user is NOT verified
108
+ * const verification = await unless(
109
+ * user.isVerified,
110
+ * () => step(() => sendVerificationEmail(user.email), { name: 'send-verification' }),
111
+ * { name: 'check-verification', reason: 'User is already verified' }
112
+ * );
113
+ *
114
+ * return { user, verification };
115
+ * });
116
+ * ```
117
+ */
118
+ declare function unless<T>(condition: boolean, operation: Operation<T>, options?: ConditionalOptions, ctx?: ConditionalContext): Promise<T | undefined>;
119
+ /**
120
+ * Synchronous overload for unless when the operation returns a non-Promise value.
121
+ */
122
+ declare function unless<T>(condition: boolean, operation: () => T, options?: ConditionalOptions, ctx?: ConditionalContext): T | undefined | Promise<T | undefined>;
123
+ /**
124
+ * Run a step only if condition is true, return default value if skipped.
125
+ *
126
+ * Use this when you want to conditionally execute a step and provide
127
+ * a fallback value when the condition is not met.
128
+ *
129
+ * @param condition - Boolean condition to evaluate
130
+ * @param operation - Function that performs the step (only called if condition is true)
131
+ * @param defaultValue - Value to return if condition is false
132
+ * @param options - Optional configuration for the conditional step
133
+ * @param ctx - Optional context for event emission
134
+ * @returns The result of the operation if condition is true, defaultValue otherwise
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * const result = await workflow(async (step) => {
139
+ * const user = await step(fetchUser(id));
140
+ *
141
+ * // Get premium limits or use default for non-premium users
142
+ * const limits = await whenOr(
143
+ * user.isPremium,
144
+ * () => step(() => fetchPremiumLimits(user.id), { name: 'premium-limits' }),
145
+ * { maxRequests: 100, maxStorage: 1000 }, // default for non-premium
146
+ * { name: 'check-premium-limits', reason: 'Using default limits for non-premium user' }
147
+ * );
148
+ *
149
+ * return { user, limits };
150
+ * });
151
+ * ```
152
+ */
153
+ declare function whenOr<T, D>(condition: boolean, operation: Operation<T>, defaultValue: D, options?: ConditionalOptions, ctx?: ConditionalContext): Promise<T | D>;
154
+ /**
155
+ * Synchronous overload for whenOr when the operation returns a non-Promise value.
156
+ */
157
+ declare function whenOr<T, D>(condition: boolean, operation: () => T, defaultValue: D, options?: ConditionalOptions, ctx?: ConditionalContext): T | D | Promise<T | D>;
158
+ /**
159
+ * Run a step only if condition is false, return default value if skipped.
160
+ *
161
+ * Use this when you want to conditionally execute a step when a condition
162
+ * is NOT met, with a fallback value for when the condition is true.
163
+ *
164
+ * @param condition - Boolean condition to evaluate
165
+ * @param operation - Function that performs the step (only called if condition is false)
166
+ * @param defaultValue - Value to return if condition is true
167
+ * @param options - Optional configuration for the conditional step
168
+ * @param ctx - Optional context for event emission
169
+ * @returns The result of the operation if condition is false, defaultValue otherwise
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * const result = await workflow(async (step) => {
174
+ * const user = await step(fetchUser(id));
175
+ *
176
+ * // Generate new token if user is NOT authenticated, otherwise use existing
177
+ * const token = await unlessOr(
178
+ * user.isAuthenticated,
179
+ * () => step(() => generateNewToken(user.id), { name: 'generate-token' }),
180
+ * user.existingToken, // use existing token if authenticated
181
+ * { name: 'check-auth-for-token', reason: 'Using existing token for authenticated user' }
182
+ * );
183
+ *
184
+ * return { user, token };
185
+ * });
186
+ * ```
187
+ */
188
+ declare function unlessOr<T, D>(condition: boolean, operation: Operation<T>, defaultValue: D, options?: ConditionalOptions, ctx?: ConditionalContext): Promise<T | D>;
189
+ /**
190
+ * Synchronous overload for unlessOr when the operation returns a non-Promise value.
191
+ */
192
+ declare function unlessOr<T, D>(condition: boolean, operation: () => T, defaultValue: D, options?: ConditionalOptions, ctx?: ConditionalContext): T | D | Promise<T | D>;
193
+ /**
194
+ * Create a set of conditional helpers bound to a workflow context.
195
+ *
196
+ * Use this factory when you want to automatically emit step_skipped events
197
+ * to the workflow's event stream without passing context manually.
198
+ *
199
+ * @param ctx - The workflow context containing workflowId and onEvent
200
+ * @returns Object with bound when, unless, whenOr, and unlessOr functions
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * const result = await run(async (step) => {
205
+ * const ctx = { workflowId, onEvent };
206
+ * const { when, whenOr } = createConditionalHelpers(ctx);
207
+ *
208
+ * const user = await step(fetchUser(id));
209
+ *
210
+ * const premium = await when(
211
+ * user.isPremium,
212
+ * () => step(() => fetchPremiumData(user.id)),
213
+ * { name: 'premium-data' }
214
+ * );
215
+ *
216
+ * return { user, premium };
217
+ * }, { onEvent, workflowId });
218
+ * ```
219
+ */
220
+ declare function createConditionalHelpers(ctx: ConditionalContext): {
221
+ /**
222
+ * Run a step only if condition is true, return undefined if skipped.
223
+ */
224
+ when: <T>(condition: boolean, operation: Operation<T>, options?: ConditionalOptions) => MaybeAsync<T | undefined>;
225
+ /**
226
+ * Run a step only if condition is false, return undefined if skipped.
227
+ */
228
+ unless: <T>(condition: boolean, operation: Operation<T>, options?: ConditionalOptions) => MaybeAsync<T | undefined>;
229
+ /**
230
+ * Run a step only if condition is true, return default value if skipped.
231
+ */
232
+ whenOr: <T, D>(condition: boolean, operation: Operation<T>, defaultValue: D, options?: ConditionalOptions) => MaybeAsync<T | D>;
233
+ /**
234
+ * Run a step only if condition is false, return default value if skipped.
235
+ */
236
+ unlessOr: <T, D>(condition: boolean, operation: Operation<T>, defaultValue: D, options?: ConditionalOptions) => MaybeAsync<T | D>;
237
+ };
238
+
239
+ /**
240
+ * Circuit Breaker for Steps
241
+ *
242
+ * Prevents cascading failures by tracking step failure rates and
243
+ * short-circuiting calls when a threshold is exceeded.
244
+ *
245
+ * Uses the circuit breaker pattern with three states:
246
+ * - CLOSED: Normal operation (steps executing)
247
+ * - OPEN: Fast-fail mode (steps blocked)
248
+ * - HALF_OPEN: Testing if service recovered
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * import { createCircuitBreaker } from '@jagreehal/workflow';
253
+ *
254
+ * const breaker = createCircuitBreaker({
255
+ * failureThreshold: 5,
256
+ * resetTimeout: 30000,
257
+ * halfOpenMax: 3,
258
+ * });
259
+ *
260
+ * const result = await workflow(async (step) => {
261
+ * const data = await breaker.execute(
262
+ * () => step(() => callExternalApi()),
263
+ * { name: 'external-api' }
264
+ * );
265
+ * return data;
266
+ * });
267
+ * ```
268
+ */
269
+
270
+ /**
271
+ * Circuit breaker state.
272
+ */
273
+ type CircuitState = "CLOSED" | "OPEN" | "HALF_OPEN";
274
+ /**
275
+ * Configuration for circuit breaker behavior.
276
+ */
277
+ interface CircuitBreakerConfig {
278
+ /**
279
+ * Number of failures within the window before opening the circuit.
280
+ * @default 5
281
+ */
282
+ failureThreshold: number;
283
+ /**
284
+ * Time in ms to wait before transitioning from OPEN to HALF_OPEN.
285
+ * @default 30000 (30 seconds)
286
+ */
287
+ resetTimeout: number;
288
+ /**
289
+ * Time window in ms for counting failures.
290
+ * Failures older than this are discarded.
291
+ * @default 60000 (1 minute)
292
+ */
293
+ windowSize: number;
294
+ /**
295
+ * Maximum number of test requests allowed in HALF_OPEN state.
296
+ * If all succeed, circuit closes. If any fail, circuit reopens.
297
+ * @default 3
298
+ */
299
+ halfOpenMax: number;
300
+ /**
301
+ * Optional callback when circuit state changes.
302
+ */
303
+ onStateChange?: (from: CircuitState, to: CircuitState, name?: string) => void;
304
+ }
305
+ /**
306
+ * Error thrown when the circuit is open and calls are blocked.
307
+ */
308
+ declare class CircuitOpenError extends Error {
309
+ readonly type: "CIRCUIT_OPEN";
310
+ readonly circuitName: string;
311
+ readonly state: CircuitState;
312
+ readonly retryAfterMs: number;
313
+ constructor(options: {
314
+ circuitName: string;
315
+ state: CircuitState;
316
+ retryAfterMs: number;
317
+ message?: string;
318
+ });
319
+ }
320
+ /**
321
+ * Type guard for CircuitOpenError.
322
+ */
323
+ declare function isCircuitOpenError(error: unknown): error is CircuitOpenError;
324
+ /**
325
+ * Circuit breaker statistics.
326
+ */
327
+ interface CircuitBreakerStats {
328
+ state: CircuitState;
329
+ failureCount: number;
330
+ successCount: number;
331
+ lastFailureTime: number | null;
332
+ lastSuccessTime: number | null;
333
+ halfOpenSuccesses: number;
334
+ }
335
+ /**
336
+ * Circuit breaker instance for protecting external calls.
337
+ */
338
+ interface CircuitBreaker {
339
+ /**
340
+ * Execute an operation with circuit breaker protection.
341
+ * Throws CircuitOpenError if the circuit is open.
342
+ *
343
+ * @param operation - The operation to execute
344
+ * @param options - Optional name for logging/metrics
345
+ * @returns The operation result
346
+ * @throws CircuitOpenError if circuit is open
347
+ */
348
+ execute<T>(operation: () => T | Promise<T>, options?: {
349
+ name?: string;
350
+ }): Promise<T>;
351
+ /**
352
+ * Execute a Result-returning operation with circuit breaker protection.
353
+ * Returns a CircuitOpenError result instead of throwing.
354
+ *
355
+ * @param operation - The operation returning a Result
356
+ * @param options - Optional name for logging/metrics
357
+ * @returns Result with the value or CircuitOpenError
358
+ */
359
+ executeResult<T, E>(operation: () => Result<T, E> | AsyncResult<T, E>, options?: {
360
+ name?: string;
361
+ }): AsyncResult<T, E | CircuitOpenError>;
362
+ /**
363
+ * Get current circuit state.
364
+ */
365
+ getState(): CircuitState;
366
+ /**
367
+ * Get circuit breaker statistics.
368
+ */
369
+ getStats(): CircuitBreakerStats;
370
+ /**
371
+ * Manually reset the circuit breaker to CLOSED state.
372
+ */
373
+ reset(): void;
374
+ /**
375
+ * Manually open the circuit (for testing or manual intervention).
376
+ */
377
+ forceOpen(): void;
378
+ /**
379
+ * Record a manual success (useful for health checks).
380
+ */
381
+ recordSuccess(): void;
382
+ /**
383
+ * Record a manual failure (useful for health checks).
384
+ */
385
+ recordFailure(error?: unknown): void;
386
+ }
387
+ /**
388
+ * Create a circuit breaker instance.
389
+ *
390
+ * @param name - Name for this circuit breaker (used in errors and logging)
391
+ * @param config - Configuration options
392
+ * @returns A CircuitBreaker instance
393
+ *
394
+ * @example
395
+ * ```typescript
396
+ * const apiBreaker = createCircuitBreaker('external-api', {
397
+ * failureThreshold: 5,
398
+ * resetTimeout: 30000,
399
+ * });
400
+ *
401
+ * // In workflow
402
+ * const data = await apiBreaker.execute(() =>
403
+ * step(() => fetchFromApi(id))
404
+ * );
405
+ * ```
406
+ */
407
+ declare function createCircuitBreaker(name: string, config?: Partial<CircuitBreakerConfig>): CircuitBreaker;
408
+ /**
409
+ * Preset configurations for common use cases.
410
+ */
411
+ declare const circuitBreakerPresets: {
412
+ /**
413
+ * Aggressive circuit breaker for critical paths.
414
+ * Opens quickly (3 failures) and recovers slowly (60s).
415
+ */
416
+ readonly critical: {
417
+ failureThreshold: number;
418
+ resetTimeout: number;
419
+ windowSize: number;
420
+ halfOpenMax: number;
421
+ };
422
+ /**
423
+ * Standard circuit breaker for typical API calls.
424
+ * Balanced between stability and availability.
425
+ */
426
+ readonly standard: {
427
+ failureThreshold: number;
428
+ resetTimeout: number;
429
+ windowSize: number;
430
+ halfOpenMax: number;
431
+ };
432
+ /**
433
+ * Lenient circuit breaker for non-critical operations.
434
+ * Opens slowly (10 failures) and recovers quickly (15s).
435
+ */
436
+ readonly lenient: {
437
+ failureThreshold: number;
438
+ resetTimeout: number;
439
+ windowSize: number;
440
+ halfOpenMax: number;
441
+ };
442
+ };
443
+
444
+ /**
445
+ * Saga / Compensation Pattern
446
+ *
447
+ * Define compensating actions for steps that need rollback on downstream failures.
448
+ * When a workflow fails after some steps have completed, compensations run in
449
+ * reverse order automatically.
450
+ *
451
+ * @example
452
+ * ```typescript
453
+ * import { createSagaWorkflow, ok, err } from '@jagreehal/workflow';
454
+ *
455
+ * const checkout = createSagaWorkflow({ reserveInventory, chargeCard, sendEmail });
456
+ *
457
+ * const result = await checkout(async (saga) => {
458
+ * const reservation = await saga.step(
459
+ * () => reserveInventory(items),
460
+ * { compensate: (res) => releaseInventory(res.reservationId) }
461
+ * );
462
+ *
463
+ * const payment = await saga.step(
464
+ * () => chargeCard(amount),
465
+ * { compensate: (p) => refundPayment(p.txId) }
466
+ * );
467
+ *
468
+ * await saga.step(() => sendEmail(userId)); // No compensation needed
469
+ *
470
+ * return { reservation, payment };
471
+ * });
472
+ * // On failure: compensations run in reverse order automatically
473
+ * ```
474
+ */
475
+
476
+ /**
477
+ * A compensation action to run on rollback.
478
+ */
479
+ type CompensationAction<T> = (value: T) => void | Promise<void>;
480
+ /**
481
+ * Options for a saga step.
482
+ */
483
+ interface SagaStepOptions<T> {
484
+ /**
485
+ * Name for the step (used in logging and events).
486
+ */
487
+ name?: string;
488
+ /**
489
+ * Compensation action to run if a later step fails.
490
+ * Receives the value returned by this step.
491
+ */
492
+ compensate?: CompensationAction<T>;
493
+ }
494
+ /**
495
+ * Error returned when compensation actions fail.
496
+ */
497
+ interface SagaCompensationError {
498
+ type: "SAGA_COMPENSATION_ERROR";
499
+ /** The original error that triggered the saga rollback */
500
+ originalError: unknown;
501
+ /** Errors from failed compensation actions */
502
+ compensationErrors: Array<{
503
+ stepName?: string;
504
+ error: unknown;
505
+ }>;
506
+ }
507
+ /**
508
+ * Type guard for SagaCompensationError.
509
+ */
510
+ declare function isSagaCompensationError(error: unknown): error is SagaCompensationError;
511
+ /**
512
+ * Saga execution context provided to the workflow callback.
513
+ */
514
+ interface SagaContext<E = unknown> {
515
+ /**
516
+ * Execute a step with optional compensation.
517
+ *
518
+ * @param operation - The operation to execute (returns Result)
519
+ * @param options - Step options including compensation action
520
+ * @returns The unwrapped success value
521
+ */
522
+ step: <T, StepE extends E, StepC = unknown>(operation: () => Result<T, StepE, StepC> | AsyncResult<T, StepE, StepC>, options?: SagaStepOptions<T>) => Promise<T>;
523
+ /**
524
+ * Execute a throwing operation with optional compensation.
525
+ *
526
+ * @param operation - The operation to execute (may throw)
527
+ * @param options - Step options including error mapping and compensation
528
+ * @returns The success value
529
+ */
530
+ tryStep: <T, Err extends E>(operation: () => T | Promise<T>, options: {
531
+ error: Err;
532
+ name?: string;
533
+ compensate?: CompensationAction<T>;
534
+ } | {
535
+ onError: (cause: unknown) => Err;
536
+ name?: string;
537
+ compensate?: CompensationAction<T>;
538
+ }) => Promise<T>;
539
+ /**
540
+ * Get all recorded compensations (for debugging/testing).
541
+ */
542
+ getCompensations: () => Array<{
543
+ name?: string;
544
+ hasValue: boolean;
545
+ }>;
546
+ }
547
+ /**
548
+ * Saga event types for observability.
549
+ */
550
+ type SagaEvent = {
551
+ type: "saga_start";
552
+ sagaId: string;
553
+ ts: number;
554
+ } | {
555
+ type: "saga_success";
556
+ sagaId: string;
557
+ ts: number;
558
+ durationMs: number;
559
+ } | {
560
+ type: "saga_error";
561
+ sagaId: string;
562
+ ts: number;
563
+ durationMs: number;
564
+ error: unknown;
565
+ } | {
566
+ type: "saga_compensation_start";
567
+ sagaId: string;
568
+ ts: number;
569
+ stepCount: number;
570
+ } | {
571
+ type: "saga_compensation_step";
572
+ sagaId: string;
573
+ stepName?: string;
574
+ ts: number;
575
+ success: boolean;
576
+ error?: unknown;
577
+ } | {
578
+ type: "saga_compensation_end";
579
+ sagaId: string;
580
+ ts: number;
581
+ durationMs: number;
582
+ success: boolean;
583
+ failedCount: number;
584
+ };
585
+ /**
586
+ * Options for createSagaWorkflow.
587
+ */
588
+ interface SagaWorkflowOptions<E> {
589
+ /**
590
+ * Called when errors occur.
591
+ */
592
+ onError?: (error: E | UnexpectedError | SagaCompensationError, stepName?: string) => void;
593
+ /**
594
+ * Event stream for saga lifecycle events.
595
+ */
596
+ onEvent?: (event: SagaEvent | WorkflowEvent<E | UnexpectedError>) => void;
597
+ /**
598
+ * Whether to throw if compensation actions fail.
599
+ * If false, original error is returned with compensation errors attached.
600
+ * @default false
601
+ */
602
+ throwOnCompensationFailure?: boolean;
603
+ }
604
+ /**
605
+ * Result type for saga workflow.
606
+ */
607
+ type SagaResult<T, E> = Result<T, E | UnexpectedError | SagaCompensationError, unknown>;
608
+ /**
609
+ * Helper type for Result-returning functions.
610
+ */
611
+ type AnyResultFn = (...args: any[]) => Result<any, any, any> | Promise<Result<any, any, any>>;
612
+ /**
613
+ * Extract union of error types from a deps object.
614
+ */
615
+ type ErrorsOfDeps<Deps extends Record<string, AnyResultFn>> = {
616
+ [K in keyof Deps]: Deps[K] extends (...args: never[]) => infer R ? R extends Promise<infer PR> ? PR extends {
617
+ ok: false;
618
+ error: infer E;
619
+ } ? E : never : R extends {
620
+ ok: false;
621
+ error: infer E;
622
+ } ? E : never : never;
623
+ }[keyof Deps];
624
+ /**
625
+ * Create a saga workflow with automatic compensation on failure.
626
+ *
627
+ * @param deps - Object mapping names to Result-returning functions
628
+ * @param options - Saga workflow options
629
+ * @returns A saga executor function
630
+ *
631
+ * @example
632
+ * ```typescript
633
+ * const saga = createSagaWorkflow({ reserveInventory, chargeCard });
634
+ *
635
+ * const result = await saga(async (ctx) => {
636
+ * const reservation = await ctx.step(
637
+ * () => reserveInventory(items),
638
+ * { compensate: (res) => releaseInventory(res.id) }
639
+ * );
640
+ *
641
+ * const payment = await ctx.step(
642
+ * () => chargeCard(amount),
643
+ * { compensate: (p) => refundPayment(p.txId) }
644
+ * );
645
+ *
646
+ * return { reservation, payment };
647
+ * });
648
+ * ```
649
+ */
650
+ declare function createSagaWorkflow<const Deps extends Readonly<Record<string, AnyResultFn>>>(deps: Deps, options?: SagaWorkflowOptions<ErrorsOfDeps<Deps>>): <T>(fn: (saga: SagaContext<ErrorsOfDeps<Deps>>, deps: Deps) => Promise<T>) => Promise<SagaResult<T, ErrorsOfDeps<Deps>>>;
651
+ /**
652
+ * Run a saga with explicit compensation registration.
653
+ *
654
+ * Lower-level API for when you don't want automatic error inference
655
+ * from a deps object.
656
+ *
657
+ * @example
658
+ * ```typescript
659
+ * const result = await runSaga<CheckoutResult, CheckoutError>(async (saga) => {
660
+ * const reservation = await saga.step(
661
+ * () => reserveInventory(items),
662
+ * { compensate: (res) => releaseInventory(res.id) }
663
+ * );
664
+ * return { reservation };
665
+ * });
666
+ * ```
667
+ */
668
+ declare function runSaga<T, E>(fn: (saga: SagaContext<E>) => Promise<T>, options?: Omit<SagaWorkflowOptions<E>, "onEvent"> & {
669
+ onEvent?: (event: SagaEvent) => void;
670
+ }): Promise<SagaResult<T, E>>;
671
+
672
+ /**
673
+ * Rate Limiting / Concurrency Control
674
+ *
675
+ * Control throughput for steps that hit rate-limited APIs or shared resources.
676
+ *
677
+ * @example
678
+ * ```typescript
679
+ * import { createRateLimiter, createConcurrencyLimiter } from '@jagreehal/workflow';
680
+ *
681
+ * // Rate limiting (requests per second)
682
+ * const rateLimiter = createRateLimiter({ maxPerSecond: 10 });
683
+ *
684
+ * // Concurrency limiting (max concurrent)
685
+ * const concurrencyLimiter = createConcurrencyLimiter({ maxConcurrent: 5 });
686
+ *
687
+ * const result = await workflow(async (step) => {
688
+ * // Wrap operations with rate limiting
689
+ * const data = await rateLimiter.execute(() =>
690
+ * step(() => callRateLimitedApi())
691
+ * );
692
+ *
693
+ * // Wrap batch operations with concurrency control
694
+ * const results = await concurrencyLimiter.executeAll(
695
+ * ids.map(id => () => step(() => fetchItem(id)))
696
+ * );
697
+ *
698
+ * return { data, results };
699
+ * });
700
+ * ```
701
+ */
702
+
703
+ /**
704
+ * Configuration for rate limiter.
705
+ */
706
+ interface RateLimiterConfig {
707
+ /**
708
+ * Maximum operations per second.
709
+ */
710
+ maxPerSecond: number;
711
+ /**
712
+ * Burst capacity - allows brief spikes above the rate.
713
+ * @default maxPerSecond * 2
714
+ */
715
+ burstCapacity?: number;
716
+ /**
717
+ * Strategy when rate limit is exceeded.
718
+ * - 'wait': Wait until a slot is available (default)
719
+ * - 'reject': Reject immediately with error
720
+ * @default 'wait'
721
+ */
722
+ strategy?: "wait" | "reject";
723
+ }
724
+ /**
725
+ * Configuration for concurrency limiter.
726
+ */
727
+ interface ConcurrencyLimiterConfig {
728
+ /**
729
+ * Maximum concurrent operations.
730
+ */
731
+ maxConcurrent: number;
732
+ /**
733
+ * Strategy when limit is reached.
734
+ * - 'queue': Queue and wait (default)
735
+ * - 'reject': Reject immediately
736
+ * @default 'queue'
737
+ */
738
+ strategy?: "queue" | "reject";
739
+ /**
740
+ * Maximum queue size (only for 'queue' strategy).
741
+ * @default Infinity
742
+ */
743
+ maxQueueSize?: number;
744
+ }
745
+ /**
746
+ * Error when rate/concurrency limit is exceeded and strategy is 'reject'.
747
+ */
748
+ interface RateLimitExceededError {
749
+ type: "RATE_LIMIT_EXCEEDED";
750
+ limiterName: string;
751
+ retryAfterMs?: number;
752
+ }
753
+ /**
754
+ * Error when concurrency limit queue is full.
755
+ */
756
+ interface QueueFullError {
757
+ type: "QUEUE_FULL";
758
+ limiterName: string;
759
+ queueSize: number;
760
+ maxQueueSize: number;
761
+ }
762
+ /**
763
+ * Type guard for RateLimitExceededError.
764
+ */
765
+ declare function isRateLimitExceededError(error: unknown): error is RateLimitExceededError;
766
+ /**
767
+ * Type guard for QueueFullError.
768
+ */
769
+ declare function isQueueFullError(error: unknown): error is QueueFullError;
770
+ /**
771
+ * Statistics for rate limiter.
772
+ */
773
+ interface RateLimiterStats {
774
+ availableTokens: number;
775
+ maxTokens: number;
776
+ tokensPerSecond: number;
777
+ waitingCount: number;
778
+ }
779
+ /**
780
+ * Statistics for concurrency limiter.
781
+ */
782
+ interface ConcurrencyLimiterStats {
783
+ activeCount: number;
784
+ maxConcurrent: number;
785
+ queueSize: number;
786
+ maxQueueSize: number;
787
+ }
788
+ /**
789
+ * Rate limiter interface.
790
+ */
791
+ interface RateLimiter {
792
+ /**
793
+ * Execute an operation with rate limiting.
794
+ * @param operation - The operation to execute
795
+ * @returns The operation result
796
+ */
797
+ execute<T>(operation: () => T | Promise<T>): Promise<T>;
798
+ /**
799
+ * Execute a Result-returning operation with rate limiting.
800
+ */
801
+ executeResult<T, E>(operation: () => Result<T, E> | AsyncResult<T, E>): AsyncResult<T, E | RateLimitExceededError>;
802
+ /**
803
+ * Get current statistics.
804
+ */
805
+ getStats(): RateLimiterStats;
806
+ /**
807
+ * Reset the rate limiter.
808
+ */
809
+ reset(): void;
810
+ }
811
+ /**
812
+ * Create a token bucket rate limiter.
813
+ *
814
+ * @param name - Name for the limiter (used in errors)
815
+ * @param config - Rate limiter configuration
816
+ * @returns A RateLimiter instance
817
+ *
818
+ * @example
819
+ * ```typescript
820
+ * const limiter = createRateLimiter('api-calls', {
821
+ * maxPerSecond: 10,
822
+ * burstCapacity: 20,
823
+ * });
824
+ *
825
+ * // In workflow
826
+ * const data = await limiter.execute(() =>
827
+ * step(() => callApi())
828
+ * );
829
+ * ```
830
+ */
831
+ declare function createRateLimiter(name: string, config: RateLimiterConfig): RateLimiter;
832
+ /**
833
+ * Concurrency limiter interface.
834
+ */
835
+ interface ConcurrencyLimiter {
836
+ /**
837
+ * Execute an operation with concurrency limiting.
838
+ * @param operation - The operation to execute
839
+ * @returns The operation result
840
+ */
841
+ execute<T>(operation: () => T | Promise<T>): Promise<T>;
842
+ /**
843
+ * Execute multiple operations with concurrency control.
844
+ * @param operations - Array of operation factories
845
+ * @returns Array of results (in order)
846
+ */
847
+ executeAll<T>(operations: Array<() => T | Promise<T>>): Promise<T[]>;
848
+ /**
849
+ * Execute a Result-returning operation with concurrency limiting.
850
+ */
851
+ executeResult<T, E>(operation: () => Result<T, E> | AsyncResult<T, E>): AsyncResult<T, E | QueueFullError>;
852
+ /**
853
+ * Get current statistics.
854
+ */
855
+ getStats(): ConcurrencyLimiterStats;
856
+ /**
857
+ * Reset the concurrency limiter.
858
+ */
859
+ reset(): void;
860
+ }
861
+ /**
862
+ * Create a concurrency limiter.
863
+ *
864
+ * @param name - Name for the limiter (used in errors)
865
+ * @param config - Concurrency limiter configuration
866
+ * @returns A ConcurrencyLimiter instance
867
+ *
868
+ * @example
869
+ * ```typescript
870
+ * const limiter = createConcurrencyLimiter('db-pool', {
871
+ * maxConcurrent: 10,
872
+ * });
873
+ *
874
+ * // Execute with concurrency control
875
+ * const results = await limiter.executeAll(
876
+ * ids.map(id => () => fetchItem(id))
877
+ * );
878
+ * ```
879
+ */
880
+ declare function createConcurrencyLimiter(name: string, config: ConcurrencyLimiterConfig): ConcurrencyLimiter;
881
+ /**
882
+ * Configuration for combined rate + concurrency limiter.
883
+ */
884
+ interface CombinedLimiterConfig {
885
+ /**
886
+ * Rate limiting configuration.
887
+ */
888
+ rate?: RateLimiterConfig;
889
+ /**
890
+ * Concurrency limiting configuration.
891
+ */
892
+ concurrency?: ConcurrencyLimiterConfig;
893
+ }
894
+ /**
895
+ * Create a combined rate + concurrency limiter.
896
+ *
897
+ * Operations are first rate-limited, then concurrency-limited.
898
+ *
899
+ * @param name - Name for the limiter
900
+ * @param config - Combined limiter configuration
901
+ * @returns An object with both limiters and a combined execute function
902
+ *
903
+ * @example
904
+ * ```typescript
905
+ * const limiter = createCombinedLimiter('api', {
906
+ * rate: { maxPerSecond: 10 },
907
+ * concurrency: { maxConcurrent: 5 },
908
+ * });
909
+ *
910
+ * const result = await limiter.execute(() => callApi());
911
+ * ```
912
+ */
913
+ declare function createCombinedLimiter(name: string, config: CombinedLimiterConfig): {
914
+ rate?: RateLimiter;
915
+ concurrency?: ConcurrencyLimiter;
916
+ execute: <T>(operation: () => T | Promise<T>) => Promise<T>;
917
+ };
918
+ /**
919
+ * Preset configurations for common use cases.
920
+ */
921
+ declare const rateLimiterPresets: {
922
+ /**
923
+ * Typical API rate limit (10 req/s).
924
+ */
925
+ readonly api: {
926
+ maxPerSecond: number;
927
+ burstCapacity: number;
928
+ strategy: "wait";
929
+ };
930
+ /**
931
+ * Database pool limit (concurrent connections).
932
+ */
933
+ readonly database: {
934
+ maxConcurrent: number;
935
+ strategy: "queue";
936
+ maxQueueSize: number;
937
+ };
938
+ /**
939
+ * Aggressive rate limit for external APIs (5 req/s).
940
+ */
941
+ readonly external: {
942
+ maxPerSecond: number;
943
+ burstCapacity: number;
944
+ strategy: "wait";
945
+ };
946
+ };
947
+
948
+ /**
949
+ * Workflow Versioning and Migration
950
+ *
951
+ * Handle schema changes when resuming workflows that were persisted
952
+ * with older step shapes.
953
+ *
954
+ * @example
955
+ * ```typescript
956
+ * import { createVersionedWorkflow } from '@jagreehal/workflow';
957
+ *
958
+ * const workflow = createVersionedWorkflow(
959
+ * { fetchUser, chargeCard },
960
+ * {
961
+ * version: 2,
962
+ * migrations: {
963
+ * 1: (state) => migrateV1ToV2(state),
964
+ * },
965
+ * resumeState: loadState(runId),
966
+ * }
967
+ * );
968
+ * ```
969
+ */
970
+
971
+ /**
972
+ * Version number type.
973
+ */
974
+ type Version = number;
975
+ /**
976
+ * Migration function that transforms state from one version to the next.
977
+ */
978
+ type MigrationFn = (state: ResumeState) => ResumeState | Promise<ResumeState>;
979
+ /**
980
+ * Map of migrations keyed by the source version.
981
+ * Migration at key N transforms state from version N to version N+1.
982
+ */
983
+ type Migrations = Record<Version, MigrationFn>;
984
+ /**
985
+ * Versioned state includes the version number.
986
+ */
987
+ interface VersionedState {
988
+ version: Version;
989
+ state: ResumeState;
990
+ }
991
+ /**
992
+ * Configuration for versioned workflow.
993
+ */
994
+ interface VersionedWorkflowConfig {
995
+ /**
996
+ * Current workflow version.
997
+ */
998
+ version: Version;
999
+ /**
1000
+ * Migrations for upgrading old states.
1001
+ * Key is the source version, value transforms to next version.
1002
+ */
1003
+ migrations?: Migrations;
1004
+ /**
1005
+ * Strict mode - fail if state version is higher than current.
1006
+ * @default true
1007
+ */
1008
+ strictVersioning?: boolean;
1009
+ }
1010
+ /**
1011
+ * Error when version migration fails.
1012
+ */
1013
+ interface MigrationError {
1014
+ type: "MIGRATION_ERROR";
1015
+ fromVersion: Version;
1016
+ toVersion: Version;
1017
+ cause: unknown;
1018
+ }
1019
+ /**
1020
+ * Error when state version is incompatible.
1021
+ */
1022
+ interface VersionIncompatibleError {
1023
+ type: "VERSION_INCOMPATIBLE";
1024
+ stateVersion: Version;
1025
+ currentVersion: Version;
1026
+ reason: string;
1027
+ }
1028
+ /**
1029
+ * Type guard for MigrationError.
1030
+ */
1031
+ declare function isMigrationError(error: unknown): error is MigrationError;
1032
+ /**
1033
+ * Type guard for VersionIncompatibleError.
1034
+ */
1035
+ declare function isVersionIncompatibleError(error: unknown): error is VersionIncompatibleError;
1036
+ /**
1037
+ * Migrate state from one version to another.
1038
+ *
1039
+ * @param state - The versioned state to migrate
1040
+ * @param targetVersion - The target version
1041
+ * @param migrations - Migration functions
1042
+ * @returns The migrated state or an error
1043
+ *
1044
+ * @example
1045
+ * ```typescript
1046
+ * const migrated = await migrateState(
1047
+ * { version: 1, state: oldState },
1048
+ * 3,
1049
+ * {
1050
+ * 1: (s) => transformV1ToV2(s),
1051
+ * 2: (s) => transformV2ToV3(s),
1052
+ * }
1053
+ * );
1054
+ * ```
1055
+ */
1056
+ declare function migrateState(state: VersionedState, targetVersion: Version, migrations: Migrations): Promise<Result<VersionedState, MigrationError | VersionIncompatibleError>>;
1057
+ /**
1058
+ * Create a versioned resume state loader.
1059
+ *
1060
+ * This wraps a state loader to automatically apply migrations
1061
+ * when loading older state versions.
1062
+ *
1063
+ * @param config - Versioning configuration
1064
+ * @returns A function that loads and migrates state
1065
+ *
1066
+ * @example
1067
+ * ```typescript
1068
+ * const loadVersionedState = createVersionedStateLoader({
1069
+ * version: 3,
1070
+ * migrations: {
1071
+ * 1: migrateV1ToV2,
1072
+ * 2: migrateV2ToV3,
1073
+ * },
1074
+ * });
1075
+ *
1076
+ * // In workflow
1077
+ * const workflow = createWorkflow(deps, {
1078
+ * resumeState: () => loadVersionedState(savedState),
1079
+ * });
1080
+ * ```
1081
+ */
1082
+ declare function createVersionedStateLoader(config: VersionedWorkflowConfig): (versionedState: VersionedState | null | undefined) => Promise<Result<ResumeState | undefined, MigrationError | VersionIncompatibleError>>;
1083
+ /**
1084
+ * Create versioned state from current resume state.
1085
+ *
1086
+ * Use this when saving state to storage.
1087
+ *
1088
+ * @param state - The current resume state
1089
+ * @param version - The current workflow version
1090
+ * @returns A versioned state object
1091
+ *
1092
+ * @example
1093
+ * ```typescript
1094
+ * const collector = createStepCollector();
1095
+ * // ... run workflow ...
1096
+ *
1097
+ * const versionedState = createVersionedState(collector.getState(), 2);
1098
+ * await db.saveWorkflowState(workflowId, versionedState);
1099
+ * ```
1100
+ */
1101
+ declare function createVersionedState(state: ResumeState, version: Version): VersionedState;
1102
+ /**
1103
+ * Parse versioned state from JSON.
1104
+ *
1105
+ * Handles the serialization/deserialization of ResumeState with Map.
1106
+ *
1107
+ * @param json - The JSON string or parsed object
1108
+ * @returns The versioned state or null if invalid
1109
+ *
1110
+ * @example
1111
+ * ```typescript
1112
+ * const json = await db.loadWorkflowState(workflowId);
1113
+ * const versionedState = parseVersionedState(json);
1114
+ * if (versionedState) {
1115
+ * const loader = createVersionedStateLoader(config);
1116
+ * const state = await loader(versionedState);
1117
+ * }
1118
+ * ```
1119
+ */
1120
+ interface SerializedVersionedState {
1121
+ version: number;
1122
+ state: {
1123
+ steps: Array<[string, ResumeStateEntry]>;
1124
+ };
1125
+ }
1126
+ declare function parseVersionedState(json: string | SerializedVersionedState | null | undefined): VersionedState | null;
1127
+ /**
1128
+ * Serialize versioned state to JSON.
1129
+ *
1130
+ * Converts the Map to an array for JSON serialization.
1131
+ *
1132
+ * @param state - The versioned state
1133
+ * @returns JSON string
1134
+ *
1135
+ * @example
1136
+ * ```typescript
1137
+ * const json = stringifyVersionedState(versionedState);
1138
+ * await db.saveWorkflowState(workflowId, json);
1139
+ * ```
1140
+ */
1141
+ declare function stringifyVersionedState(state: VersionedState): string;
1142
+ /**
1143
+ * Create a migration that renames step keys.
1144
+ *
1145
+ * @param renames - Map of old key to new key
1146
+ * @returns A migration function
1147
+ *
1148
+ * @example
1149
+ * ```typescript
1150
+ * const migrations = {
1151
+ * 1: createKeyRenameMigration({
1152
+ * 'user:fetch': 'user:load',
1153
+ * 'order:create': 'order:submit',
1154
+ * }),
1155
+ * };
1156
+ * ```
1157
+ */
1158
+ declare function createKeyRenameMigration(renames: Record<string, string>): MigrationFn;
1159
+ /**
1160
+ * Create a migration that removes specific step keys.
1161
+ *
1162
+ * @param keysToRemove - Array of keys to remove
1163
+ * @returns A migration function
1164
+ *
1165
+ * @example
1166
+ * ```typescript
1167
+ * const migrations = {
1168
+ * 1: createKeyRemoveMigration(['deprecated:step', 'old:cache']),
1169
+ * };
1170
+ * ```
1171
+ */
1172
+ declare function createKeyRemoveMigration(keysToRemove: string[]): MigrationFn;
1173
+ /**
1174
+ * Create a migration that transforms step values.
1175
+ *
1176
+ * @param transforms - Map of key to transform function
1177
+ * @returns A migration function
1178
+ *
1179
+ * @example
1180
+ * ```typescript
1181
+ * const migrations = {
1182
+ * 1: createValueTransformMigration({
1183
+ * 'user:fetch': (entry) => ({
1184
+ * ...entry,
1185
+ * result: entry.result.ok
1186
+ * ? ok({ ...entry.result.value, newField: 'default' })
1187
+ * : entry.result,
1188
+ * }),
1189
+ * }),
1190
+ * };
1191
+ * ```
1192
+ */
1193
+ declare function createValueTransformMigration(transforms: Record<string, (entry: ResumeStateEntry) => ResumeStateEntry>): MigrationFn;
1194
+ /**
1195
+ * Compose multiple migrations into a single migration.
1196
+ *
1197
+ * @param migrations - Array of migration functions
1198
+ * @returns A single migration function that applies all migrations in order
1199
+ *
1200
+ * @example
1201
+ * ```typescript
1202
+ * const migrations = {
1203
+ * 1: composeMigrations([
1204
+ * createKeyRenameMigration({ 'old': 'new' }),
1205
+ * createKeyRemoveMigration(['deprecated']),
1206
+ * ]),
1207
+ * };
1208
+ * ```
1209
+ */
1210
+ declare function composeMigrations(migrations: MigrationFn[]): MigrationFn;
1211
+
1212
+ /**
1213
+ * Autotel Integration
1214
+ *
1215
+ * First-class OpenTelemetry spans and metrics from the workflow event stream
1216
+ * using the autotel library.
1217
+ *
1218
+ * @example
1219
+ * ```typescript
1220
+ * import { createAutotelAdapter } from '@jagreehal/workflow/autotel';
1221
+ * import { init } from 'autotel';
1222
+ *
1223
+ * // Initialize autotel
1224
+ * init({ service: 'checkout-api' });
1225
+ *
1226
+ * // Create adapter
1227
+ * const otel = createAutotelAdapter({ serviceName: 'checkout' });
1228
+ *
1229
+ * // Use with workflow
1230
+ * const workflow = createWorkflow(deps, { onEvent: otel.handleEvent });
1231
+ *
1232
+ * // Automatic spans: workflow > step > retry attempts
1233
+ * // Automatic metrics: step_duration, retry_count, error_rate
1234
+ * ```
1235
+ */
1236
+
1237
+ /**
1238
+ * Configuration for the autotel adapter.
1239
+ */
1240
+ interface AutotelAdapterConfig {
1241
+ /**
1242
+ * Service name for spans (used as prefix).
1243
+ */
1244
+ serviceName?: string;
1245
+ /**
1246
+ * Whether to create spans for each step.
1247
+ * @default true
1248
+ */
1249
+ createStepSpans?: boolean;
1250
+ /**
1251
+ * Whether to record metrics for steps.
1252
+ * @default true
1253
+ */
1254
+ recordMetrics?: boolean;
1255
+ /**
1256
+ * Custom attributes to add to all spans.
1257
+ */
1258
+ defaultAttributes?: Record<string, string | number | boolean>;
1259
+ /**
1260
+ * Whether to record retry events as span events.
1261
+ * @default true
1262
+ */
1263
+ recordRetryEvents?: boolean;
1264
+ /**
1265
+ * Whether to mark workflow errors as span errors.
1266
+ * @default true
1267
+ */
1268
+ markErrorsOnSpan?: boolean;
1269
+ }
1270
+ /**
1271
+ * Metrics collected by the adapter.
1272
+ */
1273
+ interface AutotelMetrics {
1274
+ stepDurations: Array<{
1275
+ name: string;
1276
+ durationMs: number;
1277
+ success: boolean;
1278
+ attributes?: Record<string, string | number | boolean>;
1279
+ }>;
1280
+ retryCount: number;
1281
+ errorCount: number;
1282
+ cacheHits: number;
1283
+ cacheMisses: number;
1284
+ /** Default attributes applied to all metrics */
1285
+ defaultAttributes: Record<string, string | number | boolean>;
1286
+ }
1287
+ /**
1288
+ * Autotel adapter interface.
1289
+ */
1290
+ interface AutotelAdapter {
1291
+ /**
1292
+ * Handle workflow events (pass to onEvent option).
1293
+ */
1294
+ handleEvent: (event: WorkflowEvent<unknown>) => void;
1295
+ /**
1296
+ * Get current active spans count (for debugging).
1297
+ */
1298
+ getActiveSpansCount: () => {
1299
+ workflows: number;
1300
+ steps: number;
1301
+ };
1302
+ /**
1303
+ * Get collected metrics.
1304
+ */
1305
+ getMetrics: () => AutotelMetrics;
1306
+ /**
1307
+ * Reset adapter state.
1308
+ */
1309
+ reset: () => void;
1310
+ }
1311
+ /**
1312
+ * Create an autotel adapter for workflow observability.
1313
+ *
1314
+ * This adapter translates workflow events into metrics and tracking data.
1315
+ * When autotel is installed and initialized, it will also create OpenTelemetry spans.
1316
+ *
1317
+ * @param config - Adapter configuration
1318
+ * @returns An AutotelAdapter instance
1319
+ *
1320
+ * @example
1321
+ * ```typescript
1322
+ * import { createAutotelAdapter } from '@jagreehal/workflow/autotel';
1323
+ *
1324
+ * const otel = createAutotelAdapter({ serviceName: 'checkout' });
1325
+ *
1326
+ * const workflow = createWorkflow(deps, {
1327
+ * onEvent: otel.handleEvent,
1328
+ * });
1329
+ * ```
1330
+ */
1331
+ declare function createAutotelAdapter(config?: AutotelAdapterConfig): AutotelAdapter;
1332
+ /**
1333
+ * Create an event handler that integrates with autotel's trace() function.
1334
+ *
1335
+ * This is a higher-level integration that creates actual OpenTelemetry spans
1336
+ * for each workflow and step when autotel is initialized.
1337
+ *
1338
+ * @param options - Configuration options
1339
+ * @returns An event handler function
1340
+ *
1341
+ * @example
1342
+ * ```typescript
1343
+ * import { createAutotelEventHandler } from '@jagreehal/workflow/autotel';
1344
+ * import { init } from 'autotel';
1345
+ *
1346
+ * init({ service: 'checkout-api' });
1347
+ *
1348
+ * const handler = createAutotelEventHandler({ serviceName: 'checkout' });
1349
+ *
1350
+ * const workflow = createWorkflow(deps, {
1351
+ * onEvent: handler,
1352
+ * });
1353
+ * ```
1354
+ */
1355
+ declare function createAutotelEventHandler(options?: {
1356
+ serviceName?: string;
1357
+ includeStepDetails?: boolean;
1358
+ }): (event: WorkflowEvent<unknown>) => void;
1359
+ /**
1360
+ * Wrapper type for autotel trace function.
1361
+ * Use this when you want to explicitly type the autotel integration.
1362
+ */
1363
+ type AutotelTraceFn = <T>(name: string, fn: (ctx: {
1364
+ setAttribute: (key: string, value: unknown) => void;
1365
+ }) => T | Promise<T>) => T | Promise<T>;
1366
+ /**
1367
+ * Create a workflow wrapper that adds autotel tracing.
1368
+ *
1369
+ * This creates a higher-order function that wraps workflow execution
1370
+ * in an autotel trace span.
1371
+ *
1372
+ * @param traceFn - The autotel trace function
1373
+ * @param options - Wrapper options
1374
+ * @returns A workflow wrapper function
1375
+ *
1376
+ * @example
1377
+ * ```typescript
1378
+ * import { trace } from 'autotel';
1379
+ * import { withAutotelTracing } from '@jagreehal/workflow/autotel';
1380
+ *
1381
+ * const traced = withAutotelTracing(trace, { serviceName: 'checkout' });
1382
+ *
1383
+ * const result = await traced('process-order', async () => {
1384
+ * return workflow(async (step) => {
1385
+ * // ... workflow logic
1386
+ * });
1387
+ * });
1388
+ * ```
1389
+ */
1390
+ declare function withAutotelTracing(traceFn: AutotelTraceFn, options?: {
1391
+ serviceName?: string;
1392
+ }): <T>(name: string, fn: () => T | Promise<T>, attributes?: Record<string, string | number | boolean>) => Promise<T>;
1393
+
1394
+ /**
1395
+ * @jagreehal/workflow/webhook
1396
+ *
1397
+ * Webhook and event trigger adapters for exposing workflows as HTTP endpoints.
1398
+ * Framework-agnostic handlers that work with Express, Hono, Fastify, etc.
1399
+ */
1400
+
1401
+ /**
1402
+ * Generic HTTP request representation.
1403
+ * Abstracts away framework-specific request objects.
1404
+ */
1405
+ interface WebhookRequest<Body = unknown> {
1406
+ /** HTTP method (GET, POST, PUT, DELETE, etc.) */
1407
+ method: string;
1408
+ /** Request path (e.g., "/api/checkout") */
1409
+ path: string;
1410
+ /** Request headers */
1411
+ headers: Record<string, string | string[] | undefined>;
1412
+ /** Parsed request body (JSON) */
1413
+ body: Body;
1414
+ /** Query parameters */
1415
+ query: Record<string, string | string[] | undefined>;
1416
+ /** Path parameters (e.g., { id: "123" }) */
1417
+ params: Record<string, string>;
1418
+ /** Raw request object from framework (for advanced use cases) */
1419
+ raw?: unknown;
1420
+ }
1421
+ /**
1422
+ * Generic HTTP response representation.
1423
+ */
1424
+ interface WebhookResponse<T = unknown> {
1425
+ /** HTTP status code */
1426
+ status: number;
1427
+ /** Response headers */
1428
+ headers?: Record<string, string>;
1429
+ /** Response body (will be JSON serialized) */
1430
+ body: T;
1431
+ }
1432
+ /**
1433
+ * Error response body structure.
1434
+ */
1435
+ interface ErrorResponseBody {
1436
+ error: {
1437
+ type: string;
1438
+ message?: string;
1439
+ details?: unknown;
1440
+ };
1441
+ }
1442
+ /**
1443
+ * Input validation result.
1444
+ */
1445
+ type ValidationResult<T, E = string> = Result<T, E>;
1446
+ /**
1447
+ * Standard validation error type.
1448
+ */
1449
+ interface ValidationError {
1450
+ type: "VALIDATION_ERROR";
1451
+ message: string;
1452
+ field?: string;
1453
+ details?: unknown;
1454
+ }
1455
+ /**
1456
+ * Type guard for ValidationError.
1457
+ */
1458
+ declare function isValidationError(e: unknown): e is ValidationError;
1459
+ /**
1460
+ * Configuration for creating a webhook handler.
1461
+ *
1462
+ * @template TInput - The validated input type
1463
+ * @template TOutput - The workflow output type
1464
+ * @template TError - The workflow error type
1465
+ * @template TBody - The raw request body type
1466
+ */
1467
+ interface WebhookHandlerConfig<TInput, TOutput, TError, TBody = unknown> {
1468
+ /**
1469
+ * Validate and transform the incoming request.
1470
+ * Return ok(input) to proceed, or err(validationError) to reject.
1471
+ *
1472
+ * @param req - The incoming request
1473
+ * @returns Validated input or validation error
1474
+ */
1475
+ validateInput: (req: WebhookRequest<TBody>) => ValidationResult<TInput, ValidationError> | Promise<ValidationResult<TInput, ValidationError>>;
1476
+ /**
1477
+ * Map workflow result to HTTP response.
1478
+ * Called for both success and error cases.
1479
+ *
1480
+ * @param result - The workflow result
1481
+ * @param req - The original request (for context)
1482
+ * @returns HTTP response
1483
+ */
1484
+ mapResult: (result: Result<TOutput, TError | UnexpectedError>, req: WebhookRequest<TBody>) => WebhookResponse;
1485
+ /**
1486
+ * Optional: Map validation errors to HTTP response.
1487
+ * Defaults to 400 Bad Request with error details.
1488
+ *
1489
+ * @param error - The validation error
1490
+ * @param req - The original request
1491
+ * @returns HTTP response
1492
+ */
1493
+ mapValidationError?: (error: ValidationError, req: WebhookRequest<TBody>) => WebhookResponse<ErrorResponseBody>;
1494
+ /**
1495
+ * Optional: Handle unexpected errors during request processing.
1496
+ * Defaults to 500 Internal Server Error.
1497
+ *
1498
+ * @param error - The unexpected error
1499
+ * @param req - The original request
1500
+ * @returns HTTP response
1501
+ */
1502
+ mapUnexpectedError?: (error: unknown, req: WebhookRequest<TBody>) => WebhookResponse<ErrorResponseBody>;
1503
+ /**
1504
+ * Optional: Request middleware.
1505
+ * Transform or enrich the request before validation.
1506
+ *
1507
+ * @param req - The incoming request
1508
+ * @returns Transformed request
1509
+ */
1510
+ beforeValidation?: (req: WebhookRequest<TBody>) => WebhookRequest<TBody> | Promise<WebhookRequest<TBody>>;
1511
+ /**
1512
+ * Optional: Response middleware.
1513
+ * Transform the response before sending.
1514
+ *
1515
+ * @param response - The response to send
1516
+ * @param req - The original request
1517
+ * @returns Transformed response
1518
+ */
1519
+ afterResponse?: (response: WebhookResponse, req: WebhookRequest<TBody>) => WebhookResponse | Promise<WebhookResponse>;
1520
+ }
1521
+ /**
1522
+ * A webhook handler function that processes requests.
1523
+ */
1524
+ type WebhookHandler<TBody = unknown> = (req: WebhookRequest<TBody>) => Promise<WebhookResponse>;
1525
+ /**
1526
+ * Default validation error mapper.
1527
+ * Returns 400 Bad Request with error details.
1528
+ */
1529
+ declare function defaultValidationErrorMapper(error: ValidationError): WebhookResponse<ErrorResponseBody>;
1530
+ /**
1531
+ * Default unexpected error mapper.
1532
+ * Returns 500 Internal Server Error.
1533
+ */
1534
+ declare function defaultUnexpectedErrorMapper(_error: unknown): WebhookResponse<ErrorResponseBody>;
1535
+ /**
1536
+ * Create a webhook handler for a workflow.
1537
+ *
1538
+ * This factory creates an HTTP handler function that:
1539
+ * 1. Validates the incoming request
1540
+ * 2. Executes the workflow with the validated input
1541
+ * 3. Maps the result to an HTTP response
1542
+ *
1543
+ * The handler is framework-agnostic and returns a standard response object.
1544
+ * Use framework adapters (createExpressHandler, createHonoHandler, etc.) to
1545
+ * integrate with specific frameworks.
1546
+ *
1547
+ * @template TInput - The validated input type passed to the workflow
1548
+ * @template TOutput - The workflow output type
1549
+ * @template TError - The workflow error type
1550
+ * @template TBody - The raw request body type
1551
+ * @template TDeps - The workflow dependencies type
1552
+ *
1553
+ * @param workflow - The workflow function to execute
1554
+ * @param workflowFn - The workflow body function (step, deps, input) => output
1555
+ * @param config - Handler configuration
1556
+ * @returns A webhook handler function
1557
+ *
1558
+ * @example
1559
+ * ```typescript
1560
+ * const checkoutWorkflow = createWorkflow({ chargeCard, sendEmail });
1561
+ *
1562
+ * const handler = createWebhookHandler(
1563
+ * checkoutWorkflow,
1564
+ * async (step, deps, input: CheckoutInput) => {
1565
+ * const charge = await step(() => deps.chargeCard(input.amount));
1566
+ * await step(() => deps.sendEmail(input.email, charge.receiptUrl));
1567
+ * return { chargeId: charge.id };
1568
+ * },
1569
+ * {
1570
+ * validateInput: (req) => {
1571
+ * const { amount, email } = req.body;
1572
+ * if (!amount || !email) {
1573
+ * return err({ type: 'VALIDATION_ERROR', message: 'Missing required fields' });
1574
+ * }
1575
+ * return ok({ amount, email });
1576
+ * },
1577
+ * mapResult: (result) => {
1578
+ * if (result.ok) {
1579
+ * return { status: 200, body: result.value };
1580
+ * }
1581
+ * if (result.error === 'CARD_DECLINED') {
1582
+ * return { status: 402, body: { error: { type: 'CARD_DECLINED' } } };
1583
+ * }
1584
+ * return { status: 500, body: { error: { type: 'UNKNOWN' } } };
1585
+ * },
1586
+ * }
1587
+ * );
1588
+ *
1589
+ * // Use with Express
1590
+ * app.post('/checkout', async (req, res) => {
1591
+ * const response = await handler(toWebhookRequest(req));
1592
+ * res.status(response.status).json(response.body);
1593
+ * });
1594
+ * ```
1595
+ */
1596
+ declare function createWebhookHandler<TInput, TOutput, TError, TBody = unknown, TDeps = unknown>(workflow: Workflow<TError, TDeps> | WorkflowStrict<TError, unknown, TDeps>, workflowFn: (step: Parameters<Parameters<Workflow<TError, TDeps>>[1]>[0], deps: TDeps, input: TInput) => TOutput | Promise<TOutput>, config: WebhookHandlerConfig<TInput, TOutput, TError, TBody>): WebhookHandler<TBody>;
1597
+ /**
1598
+ * Configuration for a simple webhook handler without workflow.
1599
+ */
1600
+ interface SimpleHandlerConfig<TInput, TOutput, TError, TBody = unknown> {
1601
+ validateInput: (req: WebhookRequest<TBody>) => ValidationResult<TInput, ValidationError> | Promise<ValidationResult<TInput, ValidationError>>;
1602
+ handler: (input: TInput, req: WebhookRequest<TBody>) => AsyncResult<TOutput, TError>;
1603
+ mapResult: (result: Result<TOutput, TError>, req: WebhookRequest<TBody>) => WebhookResponse;
1604
+ mapValidationError?: (error: ValidationError, req: WebhookRequest<TBody>) => WebhookResponse<ErrorResponseBody>;
1605
+ mapUnexpectedError?: (error: unknown, req: WebhookRequest<TBody>) => WebhookResponse<ErrorResponseBody>;
1606
+ }
1607
+ /**
1608
+ * Create a simple webhook handler without workflow orchestration.
1609
+ * Useful for simple endpoints that don't need step-based error handling.
1610
+ *
1611
+ * @example
1612
+ * ```typescript
1613
+ * const handler = createSimpleHandler({
1614
+ * validateInput: (req) => {
1615
+ * const { id } = req.params;
1616
+ * if (!id) return err({ type: 'VALIDATION_ERROR', message: 'Missing id' });
1617
+ * return ok({ id });
1618
+ * },
1619
+ * handler: async ({ id }) => {
1620
+ * const user = await db.findUser(id);
1621
+ * return user ? ok(user) : err('NOT_FOUND' as const);
1622
+ * },
1623
+ * mapResult: (result) => {
1624
+ * if (result.ok) return { status: 200, body: result.value };
1625
+ * return { status: 404, body: { error: { type: 'NOT_FOUND' } } };
1626
+ * },
1627
+ * });
1628
+ * ```
1629
+ */
1630
+ declare function createSimpleHandler<TInput, TOutput, TError, TBody = unknown>(config: SimpleHandlerConfig<TInput, TOutput, TError, TBody>): WebhookHandler<TBody>;
1631
+ /**
1632
+ * Standard error mapping configuration.
1633
+ */
1634
+ interface ErrorMapping<TError> {
1635
+ /** The error value to match */
1636
+ error: TError;
1637
+ /** HTTP status code for this error */
1638
+ status: number;
1639
+ /** Optional custom message */
1640
+ message?: string;
1641
+ }
1642
+ /**
1643
+ * Create a result mapper from error mappings.
1644
+ * Provides a declarative way to map workflow errors to HTTP responses.
1645
+ *
1646
+ * @param mappings - Array of error mappings
1647
+ * @param defaultStatus - Default status for unmapped errors (default: 500)
1648
+ * @returns A mapResult function for use in handler config
1649
+ *
1650
+ * @example
1651
+ * ```typescript
1652
+ * const mapResult = createResultMapper<CheckoutOutput, CheckoutError>([
1653
+ * { error: 'NOT_FOUND', status: 404, message: 'Resource not found' },
1654
+ * { error: 'CARD_DECLINED', status: 402, message: 'Payment failed' },
1655
+ * { error: 'RATE_LIMITED', status: 429, message: 'Too many requests' },
1656
+ * ]);
1657
+ *
1658
+ * const handler = createWebhookHandler(workflow, workflowFn, {
1659
+ * validateInput,
1660
+ * mapResult,
1661
+ * });
1662
+ * ```
1663
+ */
1664
+ declare function createResultMapper<TOutput, TError>(mappings: ErrorMapping<TError>[], options?: {
1665
+ defaultStatus?: number;
1666
+ successStatus?: number;
1667
+ }): (result: Result<TOutput, TError | UnexpectedError>) => WebhookResponse;
1668
+ /**
1669
+ * Express-style request object (minimal interface).
1670
+ */
1671
+ interface ExpressLikeRequest {
1672
+ method: string;
1673
+ path: string;
1674
+ headers: Record<string, string | string[] | undefined>;
1675
+ body: unknown;
1676
+ query: Record<string, string | string[] | undefined>;
1677
+ params: Record<string, string>;
1678
+ }
1679
+ /**
1680
+ * Express-style response object (minimal interface).
1681
+ */
1682
+ interface ExpressLikeResponse {
1683
+ status(code: number): ExpressLikeResponse;
1684
+ set(headers: Record<string, string>): ExpressLikeResponse;
1685
+ json(body: unknown): void;
1686
+ }
1687
+ /**
1688
+ * Convert an Express-like request to WebhookRequest.
1689
+ *
1690
+ * @param req - Express-like request object
1691
+ * @returns WebhookRequest
1692
+ *
1693
+ * @example
1694
+ * ```typescript
1695
+ * app.post('/checkout', async (req, res) => {
1696
+ * const webhookReq = toWebhookRequest(req);
1697
+ * const response = await handler(webhookReq);
1698
+ * res.status(response.status).json(response.body);
1699
+ * });
1700
+ * ```
1701
+ */
1702
+ declare function toWebhookRequest<TBody = unknown>(req: ExpressLikeRequest): WebhookRequest<TBody>;
1703
+ /**
1704
+ * Send a WebhookResponse using an Express-like response object.
1705
+ *
1706
+ * @param res - Express-like response object
1707
+ * @param response - WebhookResponse to send
1708
+ *
1709
+ * @example
1710
+ * ```typescript
1711
+ * app.post('/checkout', async (req, res) => {
1712
+ * const response = await handler(toWebhookRequest(req));
1713
+ * sendWebhookResponse(res, response);
1714
+ * });
1715
+ * ```
1716
+ */
1717
+ declare function sendWebhookResponse(res: ExpressLikeResponse, response: WebhookResponse): void;
1718
+ /**
1719
+ * Create an Express-compatible middleware from a webhook handler.
1720
+ *
1721
+ * @param handler - Webhook handler function
1722
+ * @returns Express middleware function
1723
+ *
1724
+ * @example
1725
+ * ```typescript
1726
+ * const handler = createWebhookHandler(workflow, workflowFn, config);
1727
+ * const middleware = createExpressHandler(handler);
1728
+ * app.post('/checkout', middleware);
1729
+ * ```
1730
+ */
1731
+ declare function createExpressHandler<TBody = unknown>(handler: WebhookHandler<TBody>): (req: ExpressLikeRequest, res: ExpressLikeResponse) => Promise<void>;
1732
+ /**
1733
+ * Create a validation error.
1734
+ *
1735
+ * @param message - Error message
1736
+ * @param field - Optional field name
1737
+ * @param details - Optional additional details
1738
+ * @returns ValidationError
1739
+ */
1740
+ declare function validationError(message: string, field?: string, details?: unknown): ValidationError;
1741
+ /**
1742
+ * Create a required field validator.
1743
+ *
1744
+ * @param fields - Field names to validate
1745
+ * @returns Validation function
1746
+ *
1747
+ * @example
1748
+ * ```typescript
1749
+ * const validateRequired = requireFields(['email', 'password']);
1750
+ *
1751
+ * const validateInput = (req) => {
1752
+ * const result = validateRequired(req.body);
1753
+ * if (!result.ok) return result;
1754
+ * return ok(req.body as LoginInput);
1755
+ * };
1756
+ * ```
1757
+ */
1758
+ declare function requireFields(fields: string[]): (body: Record<string, unknown>) => ValidationResult<void, ValidationError>;
1759
+ /**
1760
+ * Compose multiple validators into a single validator.
1761
+ *
1762
+ * @param validators - Validators to compose
1763
+ * @returns Combined validator function
1764
+ *
1765
+ * @example
1766
+ * ```typescript
1767
+ * const validate = composeValidators(
1768
+ * requireFields(['email', 'password']),
1769
+ * validateEmailFormat,
1770
+ * validatePasswordStrength
1771
+ * );
1772
+ * ```
1773
+ */
1774
+ declare function composeValidators<T>(...validators: Array<(input: T) => ValidationResult<void, ValidationError>>): (input: T) => ValidationResult<void, ValidationError>;
1775
+ /**
1776
+ * Generic event message for queue-based triggers.
1777
+ */
1778
+ interface EventMessage<T = unknown> {
1779
+ /** Unique message ID */
1780
+ id: string;
1781
+ /** Event type/name */
1782
+ type: string;
1783
+ /** Event payload */
1784
+ payload: T;
1785
+ /** Event metadata */
1786
+ metadata?: {
1787
+ timestamp?: number;
1788
+ source?: string;
1789
+ correlationId?: string;
1790
+ [key: string]: unknown;
1791
+ };
1792
+ }
1793
+ /**
1794
+ * Result of processing an event.
1795
+ */
1796
+ interface EventProcessingResult {
1797
+ /** Whether the event was processed successfully */
1798
+ success: boolean;
1799
+ /** Should the message be acknowledged (removed from queue)? */
1800
+ ack: boolean;
1801
+ /** Optional error details */
1802
+ error?: {
1803
+ type: string;
1804
+ message?: string;
1805
+ retryable?: boolean;
1806
+ };
1807
+ }
1808
+ /**
1809
+ * Configuration for event trigger handlers.
1810
+ */
1811
+ interface EventTriggerConfig<TPayload, TOutput, TError> {
1812
+ /** Validate the event payload */
1813
+ validatePayload: (event: EventMessage<TPayload>) => ValidationResult<TPayload, ValidationError>;
1814
+ /** Map workflow result to processing result */
1815
+ mapResult: (result: Result<TOutput, TError | UnexpectedError>, event: EventMessage<TPayload>) => EventProcessingResult;
1816
+ /** Optional: Determine if error is retryable */
1817
+ isRetryable?: (error: TError | UnexpectedError) => boolean;
1818
+ }
1819
+ /**
1820
+ * Event handler function type.
1821
+ */
1822
+ type EventHandler<TPayload = unknown> = (event: EventMessage<TPayload>) => Promise<EventProcessingResult>;
1823
+ /**
1824
+ * Create an event handler for queue-based triggers.
1825
+ *
1826
+ * @example
1827
+ * ```typescript
1828
+ * const handler = createEventHandler(
1829
+ * checkoutWorkflow,
1830
+ * async (step, deps, payload: CheckoutPayload) => {
1831
+ * const charge = await step(() => deps.chargeCard(payload.amount));
1832
+ * return { chargeId: charge.id };
1833
+ * },
1834
+ * {
1835
+ * validatePayload: (event) => {
1836
+ * if (!event.payload.amount) {
1837
+ * return err({ type: 'VALIDATION_ERROR', message: 'Missing amount' });
1838
+ * }
1839
+ * return ok(event.payload);
1840
+ * },
1841
+ * mapResult: (result) => ({
1842
+ * success: result.ok,
1843
+ * ack: result.ok || !isRetryableError(result.error),
1844
+ * error: result.ok ? undefined : { type: String(result.error) },
1845
+ * }),
1846
+ * }
1847
+ * );
1848
+ *
1849
+ * // Use with SQS, RabbitMQ, etc.
1850
+ * queue.consume(async (message) => {
1851
+ * const result = await handler(message);
1852
+ * if (result.ack) await message.ack();
1853
+ * else await message.nack();
1854
+ * });
1855
+ * ```
1856
+ */
1857
+ declare function createEventHandler<TPayload, TOutput, TError, TDeps = unknown>(workflow: Workflow<TError, TDeps> | WorkflowStrict<TError, unknown, TDeps>, workflowFn: (step: Parameters<Parameters<Workflow<TError, TDeps>>[1]>[0], deps: TDeps, payload: TPayload) => TOutput | Promise<TOutput>, config: EventTriggerConfig<TPayload, TOutput, TError>): EventHandler<TPayload>;
1858
+
1859
+ /**
1860
+ * @jagreehal/workflow/policies
1861
+ *
1862
+ * Policy-Driven Step Middleware - Reusable bundles of StepOptions
1863
+ * that can be composed and applied per-workflow or per-step.
1864
+ */
1865
+
1866
+ /**
1867
+ * A policy is a partial StepOptions that can be merged with other policies.
1868
+ */
1869
+ type Policy = Partial<StepOptions>;
1870
+ /**
1871
+ * A policy factory that creates policies based on context.
1872
+ */
1873
+ type PolicyFactory<T = void> = T extends void ? () => Policy : (context: T) => Policy;
1874
+ /**
1875
+ * Named policy with metadata.
1876
+ */
1877
+ interface NamedPolicy {
1878
+ name: string;
1879
+ policy: Policy;
1880
+ description?: string;
1881
+ }
1882
+ /**
1883
+ * Merge multiple policies into a single StepOptions object.
1884
+ * Later policies override earlier ones for conflicting properties.
1885
+ * Retry and timeout options are deep-merged.
1886
+ *
1887
+ * @param policies - Policies to merge (in order of precedence)
1888
+ * @returns Merged StepOptions
1889
+ *
1890
+ * @example
1891
+ * ```typescript
1892
+ * const merged = mergePolicies(
1893
+ * timeoutPolicies.api, // timeout: 5000ms
1894
+ * retryPolicies.transient, // retry: 3 attempts
1895
+ * { name: 'fetch-user' } // name override
1896
+ * );
1897
+ * ```
1898
+ */
1899
+ declare function mergePolicies(...policies: Policy[]): StepOptions;
1900
+ /**
1901
+ * Create a policy applier that merges base policies with step-specific options.
1902
+ *
1903
+ * @param basePolicies - Base policies to apply to all steps
1904
+ * @returns A function that applies policies to step options
1905
+ *
1906
+ * @example
1907
+ * ```typescript
1908
+ * const applyPolicy = createPolicyApplier(
1909
+ * timeoutPolicies.api,
1910
+ * retryPolicies.transient
1911
+ * );
1912
+ *
1913
+ * // In workflow
1914
+ * const user = await step(
1915
+ * () => fetchUser(id),
1916
+ * applyPolicy({ name: 'fetch-user', key: 'user:' + id })
1917
+ * );
1918
+ * ```
1919
+ */
1920
+ declare function createPolicyApplier(...basePolicies: Policy[]): (stepOptions?: StepOptions | string) => StepOptions;
1921
+ /**
1922
+ * Create a named policy bundle for reuse across workflows.
1923
+ *
1924
+ * @param name - Policy bundle name
1925
+ * @param policies - Policies to include in the bundle
1926
+ * @returns Named policy object
1927
+ */
1928
+ declare function createPolicyBundle(name: string, ...policies: Policy[]): NamedPolicy;
1929
+ /**
1930
+ * Create a retry policy with the given options.
1931
+ */
1932
+ declare function retryPolicy(options: RetryOptions): Policy;
1933
+ /**
1934
+ * Pre-built retry policies for common scenarios.
1935
+ */
1936
+ declare const retryPolicies: {
1937
+ /**
1938
+ * No retry - fail immediately on error.
1939
+ */
1940
+ readonly none: Partial<StepOptions>;
1941
+ /**
1942
+ * Quick retry for transient errors (3 attempts, fast backoff).
1943
+ */
1944
+ readonly transient: Partial<StepOptions>;
1945
+ /**
1946
+ * Standard retry for API calls (3 attempts, moderate backoff).
1947
+ */
1948
+ readonly standard: Partial<StepOptions>;
1949
+ /**
1950
+ * Aggressive retry for critical operations (5 attempts, longer backoff).
1951
+ */
1952
+ readonly aggressive: Partial<StepOptions>;
1953
+ /**
1954
+ * Fixed interval retry (useful for polling).
1955
+ */
1956
+ readonly fixed: (attempts: number, delayMs: number) => Policy;
1957
+ /**
1958
+ * Linear backoff retry.
1959
+ */
1960
+ readonly linear: (attempts: number, initialDelay: number) => Policy;
1961
+ /**
1962
+ * Custom retry policy builder.
1963
+ */
1964
+ readonly custom: (options: Partial<RetryOptions> & {
1965
+ attempts: number;
1966
+ }) => Policy;
1967
+ };
1968
+ /**
1969
+ * Create a timeout policy with the given options.
1970
+ */
1971
+ declare function timeoutPolicy(options: TimeoutOptions): Policy;
1972
+ /**
1973
+ * Pre-built timeout policies for common scenarios.
1974
+ */
1975
+ declare const timeoutPolicies: {
1976
+ /**
1977
+ * No timeout.
1978
+ */
1979
+ readonly none: Policy;
1980
+ /**
1981
+ * Fast timeout for quick operations (1 second).
1982
+ */
1983
+ readonly fast: Partial<StepOptions>;
1984
+ /**
1985
+ * Standard API timeout (5 seconds).
1986
+ */
1987
+ readonly api: Partial<StepOptions>;
1988
+ /**
1989
+ * Extended timeout for slower operations (30 seconds).
1990
+ */
1991
+ readonly extended: Partial<StepOptions>;
1992
+ /**
1993
+ * Long timeout for batch operations (2 minutes).
1994
+ */
1995
+ readonly long: Partial<StepOptions>;
1996
+ /**
1997
+ * Custom timeout in milliseconds.
1998
+ */
1999
+ readonly ms: (ms: number) => Policy;
2000
+ /**
2001
+ * Custom timeout in seconds.
2002
+ */
2003
+ readonly seconds: (seconds: number) => Policy;
2004
+ /**
2005
+ * Timeout with custom error.
2006
+ */
2007
+ readonly withError: <E>(ms: number, error: E) => Policy;
2008
+ /**
2009
+ * Timeout with AbortSignal support.
2010
+ */
2011
+ readonly withSignal: (ms: number) => Policy;
2012
+ };
2013
+ /**
2014
+ * Pre-built combined policies for common service patterns.
2015
+ */
2016
+ declare const servicePolicies: {
2017
+ /**
2018
+ * Policy for external HTTP APIs.
2019
+ * - 5 second timeout
2020
+ * - 3 retries with exponential backoff
2021
+ */
2022
+ readonly httpApi: StepOptions;
2023
+ /**
2024
+ * Policy for database operations.
2025
+ * - 30 second timeout
2026
+ * - 2 retries for transient errors
2027
+ */
2028
+ readonly database: StepOptions;
2029
+ /**
2030
+ * Policy for cache operations.
2031
+ * - 1 second timeout
2032
+ * - No retry (cache misses are not errors)
2033
+ */
2034
+ readonly cache: StepOptions;
2035
+ /**
2036
+ * Policy for message queue operations.
2037
+ * - 30 second timeout
2038
+ * - 5 retries with longer backoff
2039
+ */
2040
+ readonly messageQueue: StepOptions;
2041
+ /**
2042
+ * Policy for file operations.
2043
+ * - 2 minute timeout
2044
+ * - 3 retries
2045
+ */
2046
+ readonly fileSystem: StepOptions;
2047
+ /**
2048
+ * Policy for third-party services with rate limits.
2049
+ * - 10 second timeout
2050
+ * - 5 retries with linear backoff
2051
+ */
2052
+ readonly rateLimited: StepOptions;
2053
+ };
2054
+ /**
2055
+ * Options for withPolicies workflow wrapper.
2056
+ */
2057
+ interface WithPoliciesOptions {
2058
+ /**
2059
+ * Base policies applied to all steps.
2060
+ */
2061
+ policies: Policy[];
2062
+ /**
2063
+ * Step-specific policy overrides by name or key pattern.
2064
+ */
2065
+ overrides?: Record<string, Policy>;
2066
+ }
2067
+ /**
2068
+ * Create step options with policies applied.
2069
+ * This is a helper for applying policies inline.
2070
+ *
2071
+ * @param policies - Policies to apply
2072
+ * @param stepOptions - Step-specific options
2073
+ * @returns Merged StepOptions
2074
+ *
2075
+ * @example
2076
+ * ```typescript
2077
+ * const user = await step(
2078
+ * () => fetchUser(id),
2079
+ * withPolicy(servicePolicies.httpApi, { name: 'fetch-user' })
2080
+ * );
2081
+ * ```
2082
+ */
2083
+ declare function withPolicy(policy: Policy, stepOptions?: StepOptions | string): StepOptions;
2084
+ /**
2085
+ * Create step options with multiple policies applied.
2086
+ *
2087
+ * @param policies - Policies to apply (in order)
2088
+ * @param stepOptions - Step-specific options
2089
+ * @returns Merged StepOptions
2090
+ *
2091
+ * @example
2092
+ * ```typescript
2093
+ * const user = await step(
2094
+ * () => fetchUser(id),
2095
+ * withPolicies([timeoutPolicies.api, retryPolicies.standard], { name: 'fetch-user' })
2096
+ * );
2097
+ * ```
2098
+ */
2099
+ declare function withPolicies(policies: Policy[], stepOptions?: StepOptions | string): StepOptions;
2100
+ /**
2101
+ * Create a policy that applies conditionally.
2102
+ *
2103
+ * @param condition - Condition to check
2104
+ * @param policy - Policy to apply if condition is true
2105
+ * @param elsePolicy - Policy to apply if condition is false (optional)
2106
+ * @returns The selected policy
2107
+ *
2108
+ * @example
2109
+ * ```typescript
2110
+ * const policy = conditionalPolicy(
2111
+ * isProduction,
2112
+ * servicePolicies.httpApi, // Use in production
2113
+ * retryPolicies.none // Skip in development
2114
+ * );
2115
+ * ```
2116
+ */
2117
+ declare function conditionalPolicy(condition: boolean, policy: Policy, elsePolicy?: Policy): Policy;
2118
+ /**
2119
+ * Create a policy based on environment.
2120
+ *
2121
+ * @param envPolicies - Map of environment names to policies
2122
+ * @param currentEnv - Current environment (defaults to NODE_ENV)
2123
+ * @param defaultPolicy - Default policy if environment not found
2124
+ * @returns The selected policy
2125
+ *
2126
+ * @example
2127
+ * ```typescript
2128
+ * const policy = envPolicy({
2129
+ * production: servicePolicies.httpApi,
2130
+ * development: retryPolicies.none,
2131
+ * test: retryPolicies.none,
2132
+ * });
2133
+ * ```
2134
+ */
2135
+ declare function envPolicy(envPolicies: Record<string, Policy>, currentEnv?: string, defaultPolicy?: Policy): Policy;
2136
+ /**
2137
+ * A registry for managing named policies.
2138
+ */
2139
+ interface PolicyRegistry {
2140
+ /**
2141
+ * Register a named policy.
2142
+ */
2143
+ register(name: string, policy: Policy): void;
2144
+ /**
2145
+ * Get a policy by name.
2146
+ */
2147
+ get(name: string): Policy | undefined;
2148
+ /**
2149
+ * Check if a policy exists.
2150
+ */
2151
+ has(name: string): boolean;
2152
+ /**
2153
+ * Get all registered policy names.
2154
+ */
2155
+ names(): string[];
2156
+ /**
2157
+ * Create step options using a registered policy.
2158
+ */
2159
+ apply(policyName: string, stepOptions?: StepOptions | string): StepOptions;
2160
+ }
2161
+ /**
2162
+ * Create a policy registry for managing named policies.
2163
+ *
2164
+ * @returns PolicyRegistry instance
2165
+ *
2166
+ * @example
2167
+ * ```typescript
2168
+ * const registry = createPolicyRegistry();
2169
+ *
2170
+ * // Register policies
2171
+ * registry.register('api', servicePolicies.httpApi);
2172
+ * registry.register('db', servicePolicies.database);
2173
+ *
2174
+ * // Use in workflow
2175
+ * const user = await step(
2176
+ * () => fetchUser(id),
2177
+ * registry.apply('api', { name: 'fetch-user' })
2178
+ * );
2179
+ * ```
2180
+ */
2181
+ declare function createPolicyRegistry(): PolicyRegistry;
2182
+ /**
2183
+ * Fluent builder for constructing step options.
2184
+ */
2185
+ interface StepOptionsBuilder {
2186
+ /**
2187
+ * Set step name.
2188
+ */
2189
+ name(name: string): StepOptionsBuilder;
2190
+ /**
2191
+ * Set step key for caching.
2192
+ */
2193
+ key(key: string): StepOptionsBuilder;
2194
+ /**
2195
+ * Apply a policy.
2196
+ */
2197
+ policy(policy: Policy): StepOptionsBuilder;
2198
+ /**
2199
+ * Set timeout in milliseconds.
2200
+ */
2201
+ timeout(ms: number): StepOptionsBuilder;
2202
+ /**
2203
+ * Set retry options.
2204
+ */
2205
+ retry(options: RetryOptions): StepOptionsBuilder;
2206
+ /**
2207
+ * Set retry attempts (with default exponential backoff).
2208
+ */
2209
+ retries(attempts: number): StepOptionsBuilder;
2210
+ /**
2211
+ * Build the final StepOptions.
2212
+ */
2213
+ build(): StepOptions;
2214
+ }
2215
+ /**
2216
+ * Create a fluent builder for step options.
2217
+ *
2218
+ * @returns StepOptionsBuilder instance
2219
+ *
2220
+ * @example
2221
+ * ```typescript
2222
+ * const options = stepOptions()
2223
+ * .name('fetch-user')
2224
+ * .key('user:123')
2225
+ * .timeout(5000)
2226
+ * .retries(3)
2227
+ * .build();
2228
+ *
2229
+ * const user = await step(() => fetchUser(id), options);
2230
+ * ```
2231
+ */
2232
+ declare function stepOptions(): StepOptionsBuilder;
2233
+
2234
+ /**
2235
+ * @jagreehal/workflow/persistence
2236
+ *
2237
+ * Pluggable Persistence Adapters for StepCache and ResumeState.
2238
+ * Provides adapters for Redis, file system, and in-memory storage,
2239
+ * plus helpers for JSON-safe serialization of causes.
2240
+ */
2241
+
2242
+ /**
2243
+ * JSON-safe representation of a Result.
2244
+ */
2245
+ interface SerializedResult {
2246
+ ok: boolean;
2247
+ value?: unknown;
2248
+ error?: unknown;
2249
+ cause?: SerializedCause;
2250
+ }
2251
+ /**
2252
+ * JSON-safe representation of a cause value.
2253
+ * Handles Error objects and other non-JSON-safe types.
2254
+ */
2255
+ interface SerializedCause {
2256
+ type: "error" | "value" | "undefined";
2257
+ errorName?: string;
2258
+ errorMessage?: string;
2259
+ errorStack?: string;
2260
+ value?: unknown;
2261
+ }
2262
+ /**
2263
+ * JSON-safe representation of StepFailureMeta.
2264
+ */
2265
+ interface SerializedMeta {
2266
+ origin: "result" | "throw";
2267
+ resultCause?: SerializedCause;
2268
+ thrown?: SerializedCause;
2269
+ }
2270
+ /**
2271
+ * JSON-safe representation of a ResumeStateEntry.
2272
+ */
2273
+ interface SerializedEntry {
2274
+ result: SerializedResult;
2275
+ meta?: SerializedMeta;
2276
+ }
2277
+ /**
2278
+ * JSON-safe representation of ResumeState.
2279
+ */
2280
+ interface SerializedState {
2281
+ version: number;
2282
+ entries: Record<string, SerializedEntry>;
2283
+ metadata?: Record<string, unknown>;
2284
+ }
2285
+ /**
2286
+ * Serialize a cause value to a JSON-safe format.
2287
+ */
2288
+ declare function serializeCause(cause: unknown): SerializedCause;
2289
+ /**
2290
+ * Deserialize a cause value from JSON-safe format.
2291
+ */
2292
+ declare function deserializeCause(serialized: SerializedCause): unknown;
2293
+ /**
2294
+ * Serialize a Result to a JSON-safe format.
2295
+ */
2296
+ declare function serializeResult(result: Result<unknown, unknown, unknown>): SerializedResult;
2297
+ /**
2298
+ * Deserialize a Result from JSON-safe format.
2299
+ */
2300
+ declare function deserializeResult(serialized: SerializedResult): Result<unknown, unknown, unknown>;
2301
+ /**
2302
+ * Serialize StepFailureMeta to a JSON-safe format.
2303
+ */
2304
+ declare function serializeMeta(meta: StepFailureMeta): SerializedMeta;
2305
+ /**
2306
+ * Deserialize StepFailureMeta from JSON-safe format.
2307
+ */
2308
+ declare function deserializeMeta(serialized: SerializedMeta): StepFailureMeta;
2309
+ /**
2310
+ * Serialize a ResumeStateEntry to a JSON-safe format.
2311
+ */
2312
+ declare function serializeEntry(entry: ResumeStateEntry): SerializedEntry;
2313
+ /**
2314
+ * Deserialize a ResumeStateEntry from JSON-safe format.
2315
+ */
2316
+ declare function deserializeEntry(serialized: SerializedEntry): ResumeStateEntry;
2317
+ /**
2318
+ * Serialize ResumeState to a JSON-safe format.
2319
+ */
2320
+ declare function serializeState(state: ResumeState, metadata?: Record<string, unknown>): SerializedState;
2321
+ /**
2322
+ * Deserialize ResumeState from JSON-safe format.
2323
+ */
2324
+ declare function deserializeState(serialized: SerializedState): ResumeState;
2325
+ /**
2326
+ * Convert ResumeState to a JSON string.
2327
+ */
2328
+ declare function stringifyState(state: ResumeState, metadata?: Record<string, unknown>): string;
2329
+ /**
2330
+ * Parse ResumeState from a JSON string.
2331
+ */
2332
+ declare function parseState(json: string): ResumeState;
2333
+ /**
2334
+ * Options for the in-memory cache adapter.
2335
+ */
2336
+ interface MemoryCacheOptions {
2337
+ /**
2338
+ * Maximum number of entries to store.
2339
+ * Oldest entries are evicted when limit is reached.
2340
+ */
2341
+ maxSize?: number;
2342
+ /**
2343
+ * Time-to-live in milliseconds.
2344
+ * Entries are automatically removed after this duration.
2345
+ */
2346
+ ttl?: number;
2347
+ }
2348
+ /**
2349
+ * Create an in-memory StepCache with optional LRU eviction and TTL.
2350
+ *
2351
+ * @param options - Cache options
2352
+ * @returns StepCache implementation
2353
+ *
2354
+ * @example
2355
+ * ```typescript
2356
+ * const cache = createMemoryCache({ maxSize: 1000, ttl: 60000 });
2357
+ * const workflow = createWorkflow(deps, { cache });
2358
+ * ```
2359
+ */
2360
+ declare function createMemoryCache(options?: MemoryCacheOptions): StepCache;
2361
+ /**
2362
+ * Options for the file system cache adapter.
2363
+ */
2364
+ interface FileCacheOptions {
2365
+ /**
2366
+ * Directory to store cache files.
2367
+ */
2368
+ directory: string;
2369
+ /**
2370
+ * File extension for cache files.
2371
+ * @default '.json'
2372
+ */
2373
+ extension?: string;
2374
+ /**
2375
+ * Custom file system interface (for testing or custom implementations).
2376
+ */
2377
+ fs?: FileSystemInterface;
2378
+ }
2379
+ /**
2380
+ * Minimal file system interface for cache operations.
2381
+ */
2382
+ interface FileSystemInterface {
2383
+ readFile(path: string): Promise<string>;
2384
+ writeFile(path: string, data: string): Promise<void>;
2385
+ unlink(path: string): Promise<void>;
2386
+ exists(path: string): Promise<boolean>;
2387
+ readdir(path: string): Promise<string[]>;
2388
+ mkdir(path: string, options?: {
2389
+ recursive?: boolean;
2390
+ }): Promise<void>;
2391
+ }
2392
+ /**
2393
+ * Create a file system-based StepCache.
2394
+ * Each step result is stored as a separate JSON file.
2395
+ *
2396
+ * @param options - Cache options
2397
+ * @returns StepCache implementation (async operations wrapped in sync interface)
2398
+ *
2399
+ * @example
2400
+ * ```typescript
2401
+ * import * as fs from 'fs/promises';
2402
+ *
2403
+ * const cache = createFileCache({
2404
+ * directory: './workflow-cache',
2405
+ * fs: {
2406
+ * readFile: (path) => fs.readFile(path, 'utf-8'),
2407
+ * writeFile: (path, data) => fs.writeFile(path, data, 'utf-8'),
2408
+ * unlink: fs.unlink,
2409
+ * exists: async (path) => fs.access(path).then(() => true).catch(() => false),
2410
+ * readdir: fs.readdir,
2411
+ * mkdir: fs.mkdir,
2412
+ * },
2413
+ * });
2414
+ * ```
2415
+ */
2416
+ declare function createFileCache(options: FileCacheOptions): StepCache & {
2417
+ /** Initialize the cache directory. Call before using the cache. */
2418
+ init(): Promise<void>;
2419
+ /** Get a result asynchronously. */
2420
+ getAsync(key: string): Promise<Result<unknown, unknown, unknown> | undefined>;
2421
+ /** Set a result asynchronously. */
2422
+ setAsync(key: string, result: Result<unknown, unknown, unknown>): Promise<void>;
2423
+ /** Delete a result asynchronously. */
2424
+ deleteAsync(key: string): Promise<boolean>;
2425
+ /** Clear all results asynchronously. */
2426
+ clearAsync(): Promise<void>;
2427
+ };
2428
+ /**
2429
+ * Generic key-value store interface.
2430
+ * Implement this for Redis, DynamoDB, etc.
2431
+ */
2432
+ interface KeyValueStore {
2433
+ get(key: string): Promise<string | null>;
2434
+ set(key: string, value: string, options?: {
2435
+ ttl?: number;
2436
+ }): Promise<void>;
2437
+ delete(key: string): Promise<boolean>;
2438
+ exists(key: string): Promise<boolean>;
2439
+ keys(pattern: string): Promise<string[]>;
2440
+ }
2441
+ /**
2442
+ * Options for key-value store cache adapter.
2443
+ */
2444
+ interface KVCacheOptions {
2445
+ /**
2446
+ * Key-value store implementation.
2447
+ */
2448
+ store: KeyValueStore;
2449
+ /**
2450
+ * Key prefix for all cache entries.
2451
+ * @default 'workflow:'
2452
+ */
2453
+ prefix?: string;
2454
+ /**
2455
+ * Time-to-live in seconds for cache entries.
2456
+ */
2457
+ ttl?: number;
2458
+ }
2459
+ /**
2460
+ * Create a StepCache backed by a key-value store (Redis, DynamoDB, etc.).
2461
+ *
2462
+ * @param options - Cache options
2463
+ * @returns StepCache implementation with async methods
2464
+ *
2465
+ * @example
2466
+ * ```typescript
2467
+ * // With Redis
2468
+ * import { createClient } from 'redis';
2469
+ *
2470
+ * const redis = createClient();
2471
+ * await redis.connect();
2472
+ *
2473
+ * const cache = createKVCache({
2474
+ * store: {
2475
+ * get: (key) => redis.get(key),
2476
+ * set: (key, value, opts) => redis.set(key, value, { EX: opts?.ttl }),
2477
+ * delete: (key) => redis.del(key).then(n => n > 0),
2478
+ * exists: (key) => redis.exists(key).then(n => n > 0),
2479
+ * keys: (pattern) => redis.keys(pattern),
2480
+ * },
2481
+ * prefix: 'myapp:workflow:',
2482
+ * ttl: 3600, // 1 hour
2483
+ * });
2484
+ * ```
2485
+ */
2486
+ declare function createKVCache(options: KVCacheOptions): StepCache & {
2487
+ /** Get a result asynchronously. */
2488
+ getAsync(key: string): Promise<Result<unknown, unknown, unknown> | undefined>;
2489
+ /** Set a result asynchronously. */
2490
+ setAsync(key: string, result: Result<unknown, unknown, unknown>): Promise<void>;
2491
+ /** Check if key exists asynchronously. */
2492
+ hasAsync(key: string): Promise<boolean>;
2493
+ /** Delete a result asynchronously. */
2494
+ deleteAsync(key: string): Promise<boolean>;
2495
+ /** Clear all results asynchronously. */
2496
+ clearAsync(): Promise<void>;
2497
+ };
2498
+ /**
2499
+ * Interface for persisting workflow state.
2500
+ */
2501
+ interface StatePersistence {
2502
+ /**
2503
+ * Save workflow state.
2504
+ */
2505
+ save(runId: string, state: ResumeState, metadata?: Record<string, unknown>): Promise<void>;
2506
+ /**
2507
+ * Load workflow state.
2508
+ */
2509
+ load(runId: string): Promise<ResumeState | undefined>;
2510
+ /**
2511
+ * Delete workflow state.
2512
+ */
2513
+ delete(runId: string): Promise<boolean>;
2514
+ /**
2515
+ * List all saved workflow IDs.
2516
+ */
2517
+ list(): Promise<string[]>;
2518
+ }
2519
+ /**
2520
+ * Create a state persistence adapter using a key-value store.
2521
+ *
2522
+ * @param store - Key-value store implementation
2523
+ * @param prefix - Key prefix for state entries
2524
+ * @returns StatePersistence implementation
2525
+ */
2526
+ declare function createStatePersistence(store: KeyValueStore, prefix?: string): StatePersistence;
2527
+ /**
2528
+ * Create a cache that hydrates from persistent storage on first access.
2529
+ *
2530
+ * @param memoryCache - In-memory cache for fast access
2531
+ * @param persistence - Persistent storage for durability
2532
+ * @returns Hydrating cache implementation
2533
+ */
2534
+ declare function createHydratingCache(memoryCache: StepCache, persistence: StatePersistence, runId: string): StepCache & {
2535
+ hydrate(): Promise<void>;
2536
+ };
2537
+
2538
+ /**
2539
+ * @jagreehal/workflow/devtools
2540
+ *
2541
+ * Developer tools for workflow debugging, visualization, and analysis.
2542
+ * Provides timeline rendering, run diffing, and live visualization.
2543
+ */
2544
+
2545
+ /**
2546
+ * A recorded workflow run with events and metadata.
2547
+ */
2548
+ interface WorkflowRun {
2549
+ /** Unique identifier for this run */
2550
+ id: string;
2551
+ /** Workflow name */
2552
+ name?: string;
2553
+ /** Start timestamp */
2554
+ startTime: number;
2555
+ /** End timestamp (undefined if still running) */
2556
+ endTime?: number;
2557
+ /** Duration in milliseconds */
2558
+ durationMs?: number;
2559
+ /** Whether the workflow succeeded */
2560
+ success?: boolean;
2561
+ /** Error if the workflow failed */
2562
+ error?: unknown;
2563
+ /** All events from this run */
2564
+ events: CollectableEvent[];
2565
+ /** Custom metadata */
2566
+ metadata?: Record<string, unknown>;
2567
+ }
2568
+ /**
2569
+ * Difference between two workflow runs.
2570
+ */
2571
+ interface RunDiff {
2572
+ /** Steps that were added in the new run */
2573
+ added: StepDiff[];
2574
+ /** Steps that were removed from the new run */
2575
+ removed: StepDiff[];
2576
+ /** Steps that changed between runs */
2577
+ changed: StepDiff[];
2578
+ /** Steps that are identical */
2579
+ unchanged: string[];
2580
+ /** Overall status change */
2581
+ statusChange?: {
2582
+ from: "success" | "error" | "running";
2583
+ to: "success" | "error" | "running";
2584
+ };
2585
+ /** Duration change in milliseconds */
2586
+ durationChange?: number;
2587
+ }
2588
+ /**
2589
+ * Information about a step difference.
2590
+ */
2591
+ interface StepDiff {
2592
+ /** Step name or key */
2593
+ step: string;
2594
+ /** Type of change */
2595
+ type: "added" | "removed" | "status" | "duration" | "error";
2596
+ /** Previous value (for changes) */
2597
+ from?: unknown;
2598
+ /** New value (for changes) */
2599
+ to?: unknown;
2600
+ }
2601
+ /**
2602
+ * Timeline entry for a step.
2603
+ */
2604
+ interface TimelineEntry {
2605
+ /** Step name */
2606
+ name: string;
2607
+ /** Step key (if any) */
2608
+ key?: string;
2609
+ /** Start time (relative to workflow start) */
2610
+ startMs: number;
2611
+ /** End time (relative to workflow start) */
2612
+ endMs?: number;
2613
+ /** Duration in milliseconds */
2614
+ durationMs?: number;
2615
+ /** Step status */
2616
+ status: "pending" | "running" | "success" | "error" | "skipped" | "cached";
2617
+ /** Error if failed */
2618
+ error?: unknown;
2619
+ /** Parent scope (for nested steps) */
2620
+ parent?: string;
2621
+ /** Retry attempt number */
2622
+ attempt?: number;
2623
+ }
2624
+ /**
2625
+ * Devtools configuration options.
2626
+ */
2627
+ interface DevtoolsOptions extends VisualizerOptions {
2628
+ /** Enable console logging of events */
2629
+ logEvents?: boolean;
2630
+ /** Maximum number of runs to keep in history */
2631
+ maxHistory?: number;
2632
+ /** Custom logger function */
2633
+ logger?: (message: string) => void;
2634
+ }
2635
+ /**
2636
+ * Devtools instance for workflow debugging.
2637
+ */
2638
+ interface Devtools {
2639
+ /** Handle a workflow event */
2640
+ handleEvent: (event: WorkflowEvent<unknown>) => void;
2641
+ /** Handle a decision event */
2642
+ handleDecisionEvent: (event: DecisionStartEvent | DecisionBranchEvent | DecisionEndEvent) => void;
2643
+ /** Get the current run */
2644
+ getCurrentRun: () => WorkflowRun | undefined;
2645
+ /** Get run history */
2646
+ getHistory: () => WorkflowRun[];
2647
+ /** Get a specific run by ID */
2648
+ getRun: (id: string) => WorkflowRun | undefined;
2649
+ /** Compare two runs */
2650
+ diff: (runId1: string, runId2: string) => RunDiff | undefined;
2651
+ /** Compare current run with a previous run */
2652
+ diffWithPrevious: () => RunDiff | undefined;
2653
+ /** Render current state */
2654
+ render: () => string;
2655
+ /** Render to a specific format */
2656
+ renderAs: (format: OutputFormat) => string;
2657
+ /** Render as Mermaid diagram */
2658
+ renderMermaid: () => string;
2659
+ /** Render as ASCII timeline */
2660
+ renderTimeline: () => string;
2661
+ /** Get timeline data for current run */
2662
+ getTimeline: () => TimelineEntry[];
2663
+ /** Clear all history */
2664
+ clearHistory: () => void;
2665
+ /** Reset current run */
2666
+ reset: () => void;
2667
+ /** Export run data as JSON */
2668
+ exportRun: (runId?: string) => string;
2669
+ /** Import run data from JSON */
2670
+ importRun: (json: string) => WorkflowRun;
2671
+ }
2672
+ /**
2673
+ * Create a devtools instance for workflow debugging.
2674
+ *
2675
+ * @example
2676
+ * ```typescript
2677
+ * const devtools = createDevtools({ workflowName: 'checkout' });
2678
+ *
2679
+ * const workflow = createWorkflow(deps, {
2680
+ * onEvent: devtools.handleEvent,
2681
+ * });
2682
+ *
2683
+ * await workflow(async (step) => { ... });
2684
+ *
2685
+ * // Visualize
2686
+ * console.log(devtools.render());
2687
+ * console.log(devtools.renderMermaid());
2688
+ *
2689
+ * // Compare with previous run
2690
+ * const diff = devtools.diffWithPrevious();
2691
+ * ```
2692
+ */
2693
+ declare function createDevtools(options?: DevtoolsOptions): Devtools;
2694
+ /**
2695
+ * Render a run diff as a string.
2696
+ */
2697
+ declare function renderDiff(diff: RunDiff): string;
2698
+ /**
2699
+ * Quick visualization helper for a single workflow run.
2700
+ */
2701
+ declare function quickVisualize(workflowFn: (handleEvent: (event: WorkflowEvent<unknown>) => void) => Promise<unknown>, options?: DevtoolsOptions): Promise<string>;
2702
+ /**
2703
+ * Create an event handler that logs to console with pretty formatting.
2704
+ */
2705
+ declare function createConsoleLogger(options?: {
2706
+ prefix?: string;
2707
+ colors?: boolean;
2708
+ }): (event: WorkflowEvent<unknown>) => void;
2709
+
2710
+ /**
2711
+ * @jagreehal/workflow/hitl
2712
+ *
2713
+ * Human-in-the-Loop Orchestration Helpers.
2714
+ * Provides pollers, webhook handlers, and resume injectors
2715
+ * for production-ready approval workflows.
2716
+ */
2717
+
2718
+ /**
2719
+ * Options passed to the workflow factory by the HITL orchestrator.
2720
+ */
2721
+ interface HITLWorkflowFactoryOptions {
2722
+ /** Resume state for replaying completed steps */
2723
+ resumeState?: ResumeState;
2724
+ /** Event handler for tracking workflow events (required for HITL) */
2725
+ onEvent: (event: WorkflowEvent<unknown>) => void;
2726
+ }
2727
+ /**
2728
+ * Approval status returned from the approval store.
2729
+ */
2730
+ type ApprovalStatus<T = unknown> = {
2731
+ status: "pending";
2732
+ } | {
2733
+ status: "approved";
2734
+ value: T;
2735
+ approvedBy?: string;
2736
+ approvedAt?: number;
2737
+ } | {
2738
+ status: "rejected";
2739
+ reason: string;
2740
+ rejectedBy?: string;
2741
+ rejectedAt?: number;
2742
+ } | {
2743
+ status: "expired";
2744
+ expiredAt: number;
2745
+ };
2746
+ /**
2747
+ * Interface for approval storage backends.
2748
+ */
2749
+ interface ApprovalStore {
2750
+ /**
2751
+ * Get the status of an approval.
2752
+ */
2753
+ getApproval(key: string): Promise<ApprovalStatus>;
2754
+ /**
2755
+ * Create or update a pending approval request.
2756
+ */
2757
+ createApproval(key: string, options?: {
2758
+ metadata?: Record<string, unknown>;
2759
+ expiresAt?: number;
2760
+ requestedBy?: string;
2761
+ }): Promise<void>;
2762
+ /**
2763
+ * Grant an approval.
2764
+ */
2765
+ grantApproval<T>(key: string, value: T, options?: {
2766
+ approvedBy?: string;
2767
+ }): Promise<void>;
2768
+ /**
2769
+ * Reject an approval.
2770
+ */
2771
+ rejectApproval(key: string, reason: string, options?: {
2772
+ rejectedBy?: string;
2773
+ }): Promise<void>;
2774
+ /**
2775
+ * Cancel a pending approval.
2776
+ */
2777
+ cancelApproval(key: string): Promise<void>;
2778
+ /**
2779
+ * List all pending approvals.
2780
+ */
2781
+ listPending(options?: {
2782
+ prefix?: string;
2783
+ }): Promise<string[]>;
2784
+ }
2785
+ /**
2786
+ * Saved workflow state for resumption.
2787
+ */
2788
+ interface SavedWorkflowState {
2789
+ /** Unique identifier for this workflow run */
2790
+ runId: string;
2791
+ /** Workflow name/type */
2792
+ workflowName: string;
2793
+ /** Resume state with step results */
2794
+ resumeState: ResumeState;
2795
+ /** Pending approval keys */
2796
+ pendingApprovals: string[];
2797
+ /** Input that was passed to the workflow */
2798
+ input?: unknown;
2799
+ /** Custom metadata */
2800
+ metadata?: Record<string, unknown>;
2801
+ /** When the workflow was started */
2802
+ startedAt: number;
2803
+ /** When the state was last updated */
2804
+ updatedAt: number;
2805
+ }
2806
+ /**
2807
+ * Interface for workflow state storage.
2808
+ */
2809
+ interface WorkflowStateStore {
2810
+ /**
2811
+ * Save workflow state.
2812
+ */
2813
+ save(state: SavedWorkflowState): Promise<void>;
2814
+ /**
2815
+ * Load workflow state by run ID.
2816
+ */
2817
+ load(runId: string): Promise<SavedWorkflowState | undefined>;
2818
+ /**
2819
+ * Delete workflow state.
2820
+ */
2821
+ delete(runId: string): Promise<void>;
2822
+ /**
2823
+ * List all saved workflow states.
2824
+ */
2825
+ list(options?: {
2826
+ workflowName?: string;
2827
+ hasPendingApprovals?: boolean;
2828
+ }): Promise<string[]>;
2829
+ /**
2830
+ * Find workflows waiting for a specific approval.
2831
+ */
2832
+ findByPendingApproval(approvalKey: string): Promise<string[]>;
2833
+ }
2834
+ /**
2835
+ * Options for the HITL orchestrator.
2836
+ */
2837
+ interface HITLOrchestratorOptions {
2838
+ /** Approval store for managing approval states */
2839
+ approvalStore: ApprovalStore;
2840
+ /** Workflow state store for persisting workflow state */
2841
+ workflowStateStore: WorkflowStateStore;
2842
+ /** Default expiration time for approvals (in milliseconds) */
2843
+ defaultExpirationMs?: number;
2844
+ /** Logger function */
2845
+ logger?: (message: string) => void;
2846
+ }
2847
+ /**
2848
+ * Result of executing a workflow that may pause for approval.
2849
+ * Uses unknown for error type since workflows add UnexpectedError to the union.
2850
+ */
2851
+ type HITLExecutionResult<T, E> = {
2852
+ status: "completed";
2853
+ result: Result<T, E | unknown>;
2854
+ } | {
2855
+ status: "paused";
2856
+ runId: string;
2857
+ pendingApprovals: string[];
2858
+ reason?: string;
2859
+ } | {
2860
+ status: "resumed";
2861
+ runId: string;
2862
+ result: Result<T, E | unknown>;
2863
+ };
2864
+ /**
2865
+ * Poller configuration.
2866
+ */
2867
+ interface PollerOptions {
2868
+ /** Polling interval in milliseconds */
2869
+ intervalMs: number;
2870
+ /** Maximum number of polls (undefined = unlimited) */
2871
+ maxPolls?: number;
2872
+ /** Timeout for the entire polling operation */
2873
+ timeoutMs?: number;
2874
+ /** Callback when polling starts */
2875
+ onPollStart?: () => void;
2876
+ /** Callback when a poll completes */
2877
+ onPollComplete?: (result: ApprovalStatus) => void;
2878
+ }
2879
+ /**
2880
+ * Create an in-memory approval store for development/testing.
2881
+ */
2882
+ declare function createMemoryApprovalStore(): ApprovalStore;
2883
+ /**
2884
+ * Create an in-memory workflow state store for development/testing.
2885
+ */
2886
+ declare function createMemoryWorkflowStateStore(): WorkflowStateStore;
2887
+ /**
2888
+ * HITL orchestrator interface.
2889
+ */
2890
+ interface HITLOrchestrator {
2891
+ /**
2892
+ * Execute a workflow that may pause for approvals.
2893
+ * If the workflow pauses, state is automatically saved.
2894
+ *
2895
+ * The workflowFactory receives options including onEvent handler which MUST be
2896
+ * passed to createWorkflow for HITL tracking to work.
2897
+ */
2898
+ execute<T, E, TInput>(workflowName: string, workflowFactory: (options: HITLWorkflowFactoryOptions) => Workflow<E, unknown>, workflowFn: (step: unknown, deps: unknown, input: TInput) => Promise<T>, input: TInput, options?: {
2899
+ runId?: string;
2900
+ metadata?: Record<string, unknown>;
2901
+ }): Promise<HITLExecutionResult<T, E>>;
2902
+ /**
2903
+ * Resume a paused workflow after approvals have been granted.
2904
+ */
2905
+ resume<T, E, TInput>(runId: string, workflowFactory: (options: HITLWorkflowFactoryOptions) => Workflow<E, unknown>, workflowFn: (step: unknown, deps: unknown, input: TInput) => Promise<T>): Promise<HITLExecutionResult<T, E>>;
2906
+ /**
2907
+ * Grant an approval and automatically resume any waiting workflows.
2908
+ */
2909
+ grantApproval<T>(approvalKey: string, value: T, options?: {
2910
+ approvedBy?: string;
2911
+ autoResume?: boolean;
2912
+ }): Promise<{
2913
+ grantedAt: number;
2914
+ resumedWorkflows: string[];
2915
+ }>;
2916
+ /**
2917
+ * Reject an approval.
2918
+ */
2919
+ rejectApproval(approvalKey: string, reason: string, options?: {
2920
+ rejectedBy?: string;
2921
+ }): Promise<void>;
2922
+ /**
2923
+ * Poll for an approval to be granted.
2924
+ */
2925
+ pollApproval<T>(approvalKey: string, options?: PollerOptions): Promise<ApprovalStatus<T>>;
2926
+ /**
2927
+ * Get the status of a workflow run.
2928
+ */
2929
+ getWorkflowStatus(runId: string): Promise<SavedWorkflowState | undefined>;
2930
+ /**
2931
+ * List all pending workflows.
2932
+ */
2933
+ listPendingWorkflows(workflowName?: string): Promise<string[]>;
2934
+ /**
2935
+ * Clean up completed workflows older than the specified age.
2936
+ */
2937
+ cleanup(maxAgeMs: number): Promise<number>;
2938
+ }
2939
+ /**
2940
+ * Create a HITL orchestrator for managing approval workflows.
2941
+ *
2942
+ * @example
2943
+ * ```typescript
2944
+ * const orchestrator = createHITLOrchestrator({
2945
+ * approvalStore: createMemoryApprovalStore(),
2946
+ * workflowStateStore: createMemoryWorkflowStateStore(),
2947
+ * });
2948
+ *
2949
+ * // Execute workflow - IMPORTANT: pass onEvent to createWorkflow!
2950
+ * const result = await orchestrator.execute(
2951
+ * 'order-approval',
2952
+ * ({ resumeState, onEvent }) => createWorkflow(deps, { resumeState, onEvent }),
2953
+ * async (step, deps, input) => {
2954
+ * const order = await step(() => deps.createOrder(input));
2955
+ * const approval = await step(() => deps.requireApproval(order.id), { key: `approval:${order.id}` });
2956
+ * await step(() => deps.processOrder(order.id));
2957
+ * return { orderId: order.id, approvedBy: approval.approvedBy };
2958
+ * },
2959
+ * { items: [...], total: 100 }
2960
+ * );
2961
+ *
2962
+ * if (result.status === 'paused') {
2963
+ * console.log(`Workflow paused, waiting for: ${result.pendingApprovals}`);
2964
+ * }
2965
+ *
2966
+ * // Later, grant approval
2967
+ * await orchestrator.grantApproval(
2968
+ * `approval:${orderId}`,
2969
+ * { approvedBy: 'manager@example.com' },
2970
+ * { autoResume: true }
2971
+ * );
2972
+ * ```
2973
+ */
2974
+ declare function createHITLOrchestrator(options: HITLOrchestratorOptions): HITLOrchestrator;
2975
+ /**
2976
+ * Approval webhook request body.
2977
+ */
2978
+ interface ApprovalWebhookRequest {
2979
+ /** Approval key */
2980
+ key: string;
2981
+ /** Action: approve, reject, or cancel */
2982
+ action: "approve" | "reject" | "cancel";
2983
+ /** Value to inject (for approve) */
2984
+ value?: unknown;
2985
+ /** Reason (for reject) */
2986
+ reason?: string;
2987
+ /** Who performed this action */
2988
+ actorId?: string;
2989
+ }
2990
+ /**
2991
+ * Approval webhook response.
2992
+ */
2993
+ interface ApprovalWebhookResponse {
2994
+ success: boolean;
2995
+ message: string;
2996
+ data?: {
2997
+ key: string;
2998
+ action: string;
2999
+ timestamp: number;
3000
+ };
3001
+ }
3002
+ /**
3003
+ * Create a webhook handler for approval actions.
3004
+ *
3005
+ * @example
3006
+ * ```typescript
3007
+ * const handleApproval = createApprovalWebhookHandler(approvalStore);
3008
+ *
3009
+ * // Express
3010
+ * app.post('/api/approvals', async (req, res) => {
3011
+ * const result = await handleApproval(req.body);
3012
+ * res.json(result);
3013
+ * });
3014
+ * ```
3015
+ */
3016
+ declare function createApprovalWebhookHandler(store: ApprovalStore): (request: ApprovalWebhookRequest) => Promise<ApprovalWebhookResponse>;
3017
+ /**
3018
+ * Create an approval checker function for use in approval steps.
3019
+ * This wraps the approval store with the standard checkApproval interface.
3020
+ *
3021
+ * @example
3022
+ * ```typescript
3023
+ * const checkApproval = createApprovalChecker(approvalStore);
3024
+ *
3025
+ * const requireManagerApproval = createApprovalStep<{ approvedBy: string }>({
3026
+ * key: 'manager-approval',
3027
+ * checkApproval: checkApproval('manager-approval'),
3028
+ * pendingReason: 'Waiting for manager approval',
3029
+ * });
3030
+ * ```
3031
+ */
3032
+ declare function createApprovalChecker<T>(store: ApprovalStore): (key: string) => () => Promise<{
3033
+ status: "pending";
3034
+ } | {
3035
+ status: "approved";
3036
+ value: T;
3037
+ } | {
3038
+ status: "rejected";
3039
+ reason: string;
3040
+ }>;
3041
+
3042
+ /**
3043
+ * @jagreehal/workflow/testing
3044
+ *
3045
+ * Deterministic Workflow Testing Harness.
3046
+ * Provides tools for scripting step outcomes and asserting workflow behavior.
3047
+ */
3048
+
3049
+ /**
3050
+ * A scripted outcome for a step.
3051
+ */
3052
+ type ScriptedOutcome<T = unknown, E = unknown> = {
3053
+ type: "ok";
3054
+ value: T;
3055
+ } | {
3056
+ type: "err";
3057
+ error: E;
3058
+ } | {
3059
+ type: "throw";
3060
+ error: unknown;
3061
+ };
3062
+ /**
3063
+ * Step invocation record.
3064
+ */
3065
+ interface StepInvocation {
3066
+ /** Step name */
3067
+ name?: string;
3068
+ /** Step key */
3069
+ key?: string;
3070
+ /** Invocation order (0-indexed) */
3071
+ order: number;
3072
+ /** Timestamp when step was invoked */
3073
+ timestamp: number;
3074
+ /** Duration in milliseconds */
3075
+ durationMs?: number;
3076
+ /** Result of the step */
3077
+ result?: Result<unknown, unknown>;
3078
+ /** Whether the step was from cache */
3079
+ cached?: boolean;
3080
+ }
3081
+ /**
3082
+ * Assertion result.
3083
+ */
3084
+ interface AssertionResult {
3085
+ passed: boolean;
3086
+ message: string;
3087
+ expected?: unknown;
3088
+ actual?: unknown;
3089
+ }
3090
+ /**
3091
+ * Test harness options.
3092
+ */
3093
+ interface TestHarnessOptions {
3094
+ /** Whether to record step invocations */
3095
+ recordInvocations?: boolean;
3096
+ /** Custom clock for deterministic timing */
3097
+ clock?: () => number;
3098
+ }
3099
+ /**
3100
+ * Mock step function that returns scripted outcomes.
3101
+ */
3102
+ type MockStep<E> = {
3103
+ /** Execute with a Result-returning operation */
3104
+ <T, StepE extends E>(operation: () => Result<T, StepE> | AsyncResult<T, StepE>, options?: StepOptions | string): Promise<T>;
3105
+ /** Execute with a direct Result */
3106
+ <T, StepE extends E>(result: Result<T, StepE> | AsyncResult<T, StepE>, options?: StepOptions | string): Promise<T>;
3107
+ /** step.try for catching throws */
3108
+ try: <T, Err extends E>(operation: () => T | Promise<T>, options: {
3109
+ error: Err;
3110
+ name?: string;
3111
+ key?: string;
3112
+ } | {
3113
+ onError: (cause: unknown) => Err;
3114
+ name?: string;
3115
+ key?: string;
3116
+ }) => Promise<T>;
3117
+ };
3118
+ /**
3119
+ * Workflow test harness interface.
3120
+ */
3121
+ interface WorkflowHarness<E, Deps> {
3122
+ /**
3123
+ * Script step outcomes in order.
3124
+ * Each outcome will be returned for the corresponding step invocation.
3125
+ */
3126
+ script(outcomes: ScriptedOutcome[]): void;
3127
+ /**
3128
+ * Script a specific step outcome by name or key.
3129
+ */
3130
+ scriptStep(nameOrKey: string, outcome: ScriptedOutcome): void;
3131
+ /**
3132
+ * Run the workflow with scripted outcomes.
3133
+ */
3134
+ run<T>(fn: (step: MockStep<E>, deps: Deps) => Promise<T>): Promise<Result<T, E | unknown>>;
3135
+ /**
3136
+ * Run the workflow with input.
3137
+ */
3138
+ runWithInput<T, TInput>(input: TInput, fn: (step: MockStep<E>, deps: Deps, input: TInput) => Promise<T>): Promise<Result<T, E | unknown>>;
3139
+ /**
3140
+ * Get recorded step invocations.
3141
+ */
3142
+ getInvocations(): StepInvocation[];
3143
+ /**
3144
+ * Assert that steps were invoked in order.
3145
+ */
3146
+ assertSteps(expectedNames: string[]): AssertionResult;
3147
+ /**
3148
+ * Assert that a step was invoked with specific options.
3149
+ */
3150
+ assertStepCalled(nameOrKey: string): AssertionResult;
3151
+ /**
3152
+ * Assert that a step was NOT invoked.
3153
+ */
3154
+ assertStepNotCalled(nameOrKey: string): AssertionResult;
3155
+ /**
3156
+ * Assert the workflow result.
3157
+ */
3158
+ assertResult<T>(result: Result<T, unknown>, expected: Result<T, unknown>): AssertionResult;
3159
+ /**
3160
+ * Clear all state for a new test.
3161
+ */
3162
+ reset(): void;
3163
+ }
3164
+ /**
3165
+ * Create a test harness for a workflow.
3166
+ *
3167
+ * @example
3168
+ * ```typescript
3169
+ * const harness = createWorkflowHarness({ fetchUser, chargeCard });
3170
+ *
3171
+ * // Script step outcomes
3172
+ * harness.script([
3173
+ * { type: 'ok', value: { id: '1', name: 'Alice' } },
3174
+ * { type: 'ok', value: { transactionId: 'tx_123' } },
3175
+ * ]);
3176
+ *
3177
+ * // Run the workflow
3178
+ * const result = await harness.run(async (step, { fetchUser, chargeCard }) => {
3179
+ * const user = await step(() => fetchUser('1'), 'fetch-user');
3180
+ * const charge = await step(() => chargeCard(100), 'charge-card');
3181
+ * return { user, charge };
3182
+ * });
3183
+ *
3184
+ * // Assert
3185
+ * expect(result.ok).toBe(true);
3186
+ * harness.assertSteps(['fetch-user', 'charge-card']);
3187
+ * ```
3188
+ */
3189
+ declare function createWorkflowHarness<Deps extends Record<string, AnyResultFn$1>>(deps: Deps, options?: TestHarnessOptions): WorkflowHarness<ErrorsOfDeps$1<Deps>, Deps>;
3190
+ /**
3191
+ * Create a mock Result-returning function.
3192
+ *
3193
+ * @example
3194
+ * ```typescript
3195
+ * const fetchUser = createMockFn<User, 'NOT_FOUND'>();
3196
+ *
3197
+ * fetchUser.returns(ok({ id: '1', name: 'Alice' }));
3198
+ * // or
3199
+ * fetchUser.returnsOnce(ok({ id: '1', name: 'Alice' }));
3200
+ * fetchUser.returnsOnce(err('NOT_FOUND'));
3201
+ * ```
3202
+ */
3203
+ declare function createMockFn<T, E>(): MockFunction<T, E>;
3204
+ /**
3205
+ * Mock function interface.
3206
+ */
3207
+ interface MockFunction<T, E> {
3208
+ (...args: unknown[]): AsyncResult<T, E>;
3209
+ /** Set the default return value */
3210
+ returns(result: Result<T, E>): MockFunction<T, E>;
3211
+ /** Queue a return value for the next call */
3212
+ returnsOnce(result: Result<T, E>): MockFunction<T, E>;
3213
+ /** Get all call arguments */
3214
+ getCalls(): unknown[][];
3215
+ /** Get the number of times the function was called */
3216
+ getCallCount(): number;
3217
+ /** Reset the mock */
3218
+ reset(): void;
3219
+ }
3220
+ /**
3221
+ * Workflow snapshot for comparison.
3222
+ */
3223
+ interface WorkflowSnapshot {
3224
+ /** Step invocations */
3225
+ invocations: StepInvocation[];
3226
+ /** Final result */
3227
+ result: Result<unknown, unknown>;
3228
+ /** Events emitted */
3229
+ events?: WorkflowEvent<unknown>[];
3230
+ /** Total duration */
3231
+ durationMs?: number;
3232
+ }
3233
+ /**
3234
+ * Create a snapshot of a workflow execution.
3235
+ */
3236
+ declare function createSnapshot(invocations: StepInvocation[], result: Result<unknown, unknown>, events?: WorkflowEvent<unknown>[]): WorkflowSnapshot;
3237
+ /**
3238
+ * Compare two workflow snapshots.
3239
+ */
3240
+ declare function compareSnapshots(snapshot1: WorkflowSnapshot, snapshot2: WorkflowSnapshot): {
3241
+ equal: boolean;
3242
+ differences: string[];
3243
+ };
3244
+ /**
3245
+ * Create a deterministic clock for testing.
3246
+ */
3247
+ declare function createTestClock(startTime?: number): {
3248
+ now: () => number;
3249
+ advance: (ms: number) => void;
3250
+ set: (time: number) => void;
3251
+ reset: () => void;
3252
+ };
3253
+ /**
3254
+ * Helper to create ok outcomes.
3255
+ */
3256
+ declare function okOutcome<T>(value: T): ScriptedOutcome<T, never>;
3257
+ /**
3258
+ * Helper to create err outcomes.
3259
+ */
3260
+ declare function errOutcome<E>(error: E): ScriptedOutcome<never, E>;
3261
+ /**
3262
+ * Helper to create throw outcomes.
3263
+ */
3264
+ declare function throwOutcome(error: unknown): ScriptedOutcome<never, never>;
3265
+
3266
+ export { AnyResultFn$1 as AnyResultFn, type ApprovalStatus, type ApprovalStore, type ApprovalWebhookRequest, type ApprovalWebhookResponse, type AssertionResult, AsyncResult, type AutotelAdapter, type AutotelAdapterConfig, type AutotelMetrics, type AutotelTraceFn, type CircuitBreaker, type CircuitBreakerConfig, type CircuitBreakerStats, CircuitOpenError, type CircuitState, type CombinedLimiterConfig, type CompensationAction, type ConcurrencyLimiter, type ConcurrencyLimiterConfig, type ConcurrencyLimiterStats, type ConditionalContext, type ConditionalOptions, type Devtools, type DevtoolsOptions, type ErrorMapping, type ErrorResponseBody, ErrorsOfDeps$1 as ErrorsOfDeps, type EventHandler, type EventMessage, type EventProcessingResult, type EventTriggerConfig, type ExpressLikeRequest, type ExpressLikeResponse, type FileCacheOptions, type FileSystemInterface, type HITLExecutionResult, type HITLOrchestrator, type HITLOrchestratorOptions, type HITLWorkflowFactoryOptions, type KVCacheOptions, type KeyValueStore, type MemoryCacheOptions, type MigrationError, type MigrationFn, type Migrations, type MockFunction, type MockStep, type NamedPolicy, type Policy, type PolicyFactory, type PolicyRegistry, type PollerOptions, type QueueFullError, type RateLimitExceededError, type RateLimiter, type RateLimiterConfig, type RateLimiterStats, Result, ResumeState, ResumeStateEntry, RetryOptions, type RunDiff, type SagaCompensationError, type SagaContext, type SagaEvent, type SagaResult, type SagaStepOptions, type SagaWorkflowOptions, type SavedWorkflowState, type ScriptedOutcome, type SerializedCause, type SerializedEntry, type SerializedMeta, type SerializedResult, type SerializedState, type SimpleHandlerConfig, type StatePersistence, StepCache, type StepDiff, type StepInvocation, StepOptions, type StepOptionsBuilder, type TestHarnessOptions, type TimelineEntry, TimeoutOptions, UnexpectedError, type ValidationError, type ValidationResult, type Version, type VersionIncompatibleError, type VersionedState, type VersionedWorkflowConfig, type WebhookHandler, type WebhookHandlerConfig, type WebhookRequest, type WebhookResponse, type WithPoliciesOptions, Workflow, WorkflowEvent, type WorkflowHarness, type WorkflowRun, type WorkflowSnapshot, type WorkflowStateStore, WorkflowStrict, circuitBreakerPresets, compareSnapshots, composeMigrations, composeValidators, conditionalPolicy, createApprovalChecker, createApprovalWebhookHandler, createAutotelAdapter, createAutotelEventHandler, createCircuitBreaker, createCombinedLimiter, createConcurrencyLimiter, createConditionalHelpers, createConsoleLogger, createDevtools, createEventHandler, createExpressHandler, createFileCache, createHITLOrchestrator, createHydratingCache, createKVCache, createKeyRemoveMigration, createKeyRenameMigration, createMemoryApprovalStore, createMemoryCache, createMemoryWorkflowStateStore, createMockFn, createPolicyApplier, createPolicyBundle, createPolicyRegistry, createRateLimiter, createResultMapper, createSagaWorkflow, createSimpleHandler, createSnapshot, createStatePersistence, createTestClock, createValueTransformMigration, createVersionedState, createVersionedStateLoader, createWebhookHandler, createWorkflowHarness, defaultUnexpectedErrorMapper, defaultValidationErrorMapper, deserializeCause, deserializeEntry, deserializeMeta, deserializeResult, deserializeState, envPolicy, errOutcome, isCircuitOpenError, isMigrationError, isQueueFullError, isRateLimitExceededError, isSagaCompensationError, isValidationError, isVersionIncompatibleError, mergePolicies, migrateState, okOutcome, parseState, parseVersionedState, quickVisualize, rateLimiterPresets, renderDiff, requireFields, retryPolicies, retryPolicy, runSaga, sendWebhookResponse, serializeCause, serializeEntry, serializeMeta, serializeResult, serializeState, servicePolicies, stepOptions, stringifyState, stringifyVersionedState, throwOutcome, timeoutPolicies, timeoutPolicy, toWebhookRequest, unless, unlessOr, validationError, when, whenOr, withAutotelTracing, withPolicies, withPolicy };