awaitly 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/di-Biw_plBn.d.cts +15 -0
  2. package/dist/di-DxyH2N3i.d.ts +15 -0
  3. package/dist/durable.cjs +4670 -0
  4. package/dist/durable.cjs.map +1 -0
  5. package/dist/durable.d.cts +9 -0
  6. package/dist/durable.d.ts +9 -0
  7. package/dist/durable.js +4610 -0
  8. package/dist/durable.js.map +1 -0
  9. package/dist/{di-BbFFfO8y.d.ts → duration-BjVn3QpB.d.cts} +1 -15
  10. package/dist/{di-BDlT7InM.d.cts → duration-BjVn3QpB.d.ts} +1 -15
  11. package/dist/engine.cjs +4501 -0
  12. package/dist/engine.cjs.map +1 -0
  13. package/dist/engine.d.cts +113 -0
  14. package/dist/engine.d.ts +113 -0
  15. package/dist/engine.js +4474 -0
  16. package/dist/engine.js.map +1 -0
  17. package/dist/{errors-DtXvrCiO.d.cts → errors-sDSeaZBO.d.cts} +1 -1
  18. package/dist/{errors-DtXvrCiO.d.ts → errors-sDSeaZBO.d.ts} +1 -1
  19. package/dist/guards-CwQZro1F.d.ts +72 -0
  20. package/dist/guards-DhhOJZda.d.cts +72 -0
  21. package/dist/hitl-BnnnLzh2.d.cts +468 -0
  22. package/dist/hitl-pYfkQMv_.d.ts +468 -0
  23. package/dist/hitl.cjs +646 -0
  24. package/dist/hitl.cjs.map +1 -0
  25. package/dist/hitl.d.cts +440 -0
  26. package/dist/hitl.d.ts +440 -0
  27. package/dist/hitl.js +605 -0
  28. package/dist/hitl.js.map +1 -0
  29. package/dist/index-Bew12SZ8.d.ts +417 -0
  30. package/dist/index-Bwt-iz50.d.cts +417 -0
  31. package/dist/{types-B8NfNRGX.d.ts → index-Cv4K_3sZ.d.ts} +2 -1162
  32. package/dist/{types-BZ2f4MRR.d.cts → index-ICmfQi08.d.cts} +2 -1162
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +11 -1527
  35. package/dist/index.d.ts +11 -1527
  36. package/dist/persistence.cjs +552 -0
  37. package/dist/persistence.cjs.map +1 -0
  38. package/dist/persistence.d.cts +254 -0
  39. package/dist/persistence.d.ts +254 -0
  40. package/dist/persistence.js +499 -0
  41. package/dist/persistence.js.map +1 -0
  42. package/dist/reliability.cjs +1651 -0
  43. package/dist/reliability.cjs.map +1 -0
  44. package/dist/reliability.d.cts +1442 -0
  45. package/dist/reliability.d.ts +1442 -0
  46. package/dist/reliability.js +1589 -0
  47. package/dist/reliability.js.map +1 -0
  48. package/dist/result.d.cts +1 -1
  49. package/dist/result.d.ts +1 -1
  50. package/dist/run.cjs +2283 -0
  51. package/dist/run.cjs.map +1 -0
  52. package/dist/run.d.cts +90 -0
  53. package/dist/run.d.ts +90 -0
  54. package/dist/run.js +2247 -0
  55. package/dist/run.js.map +1 -0
  56. package/dist/saga.cjs +3699 -0
  57. package/dist/saga.cjs.map +1 -0
  58. package/dist/saga.d.cts +162 -0
  59. package/dist/saga.d.ts +162 -0
  60. package/dist/saga.js +3670 -0
  61. package/dist/saga.js.map +1 -0
  62. package/dist/store-contract-BI98VYmX.d.ts +66 -0
  63. package/dist/store-contract-C2HyR_o6.d.cts +66 -0
  64. package/dist/streaming.cjs +895 -0
  65. package/dist/streaming.cjs.map +1 -0
  66. package/dist/streaming.d.cts +594 -0
  67. package/dist/streaming.d.ts +594 -0
  68. package/dist/streaming.js +832 -0
  69. package/dist/streaming.js.map +1 -0
  70. package/dist/testing.d.cts +4 -2
  71. package/dist/testing.d.ts +4 -2
  72. package/dist/types-BDTxgKKc.d.ts +845 -0
  73. package/dist/types-C9Y71dua.d.cts +845 -0
  74. package/dist/types-d8q8iQlk.d.cts +323 -0
  75. package/dist/types-sObbY4mX.d.ts +323 -0
  76. package/dist/webhook.cjs +461 -0
  77. package/dist/webhook.cjs.map +1 -0
  78. package/dist/webhook.d.cts +497 -0
  79. package/dist/webhook.d.ts +497 -0
  80. package/dist/webhook.js +422 -0
  81. package/dist/webhook.js.map +1 -0
  82. package/dist/workflow.cjs +2 -2614
  83. package/dist/workflow.cjs.map +1 -1
  84. package/dist/workflow.d.cts +12 -3023
  85. package/dist/workflow.d.ts +12 -3023
  86. package/dist/workflow.js +2 -2535
  87. package/dist/workflow.js.map +1 -1
  88. package/package.json +46 -1
package/dist/workflow.js CHANGED
@@ -385,7 +385,6 @@ function err(error, options) {
385
385
  const cause = options?.cause;
386
386
  return { ok: false, error, ...cause !== void 0 ? { cause } : {} };
387
387
  }
388
- var isOk = (r) => r.ok;
389
388
  var isUnexpectedError = (e) => e instanceof UnexpectedError || typeof e === "object" && e !== null && "_tag" in e && e._tag === "UnexpectedError";
390
389
  function extractErrorTag(error) {
391
390
  if (error == null) return "unknown";
@@ -2504,69 +2503,6 @@ function deserializeCauseNew(serialized) {
2504
2503
  }
2505
2504
  return serialized.value !== void 0 ? serialized.value : serialized.stringRepresentation;
2506
2505
  }
2507
- function createMemoryCache(options = {}) {
2508
- const { maxSize, ttl } = options;
2509
- const cache = /* @__PURE__ */ new Map();
2510
- const isExpired = (entry) => {
2511
- const effectiveTtl = entry.entryTtl ?? ttl;
2512
- if (!effectiveTtl) return false;
2513
- return Date.now() - entry.timestamp > effectiveTtl;
2514
- };
2515
- const evictExpired = () => {
2516
- for (const [key, entry] of cache) {
2517
- if (isExpired(entry)) {
2518
- cache.delete(key);
2519
- }
2520
- }
2521
- };
2522
- const evictOldest = () => {
2523
- if (!maxSize || cache.size < maxSize) return;
2524
- let oldestKey;
2525
- let oldestTime = Infinity;
2526
- for (const [key, entry] of cache) {
2527
- if (entry.timestamp < oldestTime) {
2528
- oldestTime = entry.timestamp;
2529
- oldestKey = key;
2530
- }
2531
- }
2532
- if (oldestKey) {
2533
- cache.delete(oldestKey);
2534
- }
2535
- };
2536
- return {
2537
- get(key) {
2538
- evictExpired();
2539
- const entry = cache.get(key);
2540
- if (!entry) return void 0;
2541
- if (isExpired(entry)) {
2542
- cache.delete(key);
2543
- return void 0;
2544
- }
2545
- return entry.result;
2546
- },
2547
- set(key, result, options2) {
2548
- evictExpired();
2549
- evictOldest();
2550
- cache.set(key, { result, timestamp: Date.now(), entryTtl: options2?.ttl });
2551
- },
2552
- has(key) {
2553
- evictExpired();
2554
- const entry = cache.get(key);
2555
- if (!entry) return false;
2556
- if (isExpired(entry)) {
2557
- cache.delete(key);
2558
- return false;
2559
- }
2560
- return true;
2561
- },
2562
- delete(key) {
2563
- return cache.delete(key);
2564
- },
2565
- clear() {
2566
- cache.clear();
2567
- }
2568
- };
2569
- }
2570
2506
 
2571
2507
  // src/workflow/cache-encoding.ts
2572
2508
  function isCachedErrorCause(cause) {
@@ -2815,24 +2751,7 @@ function createHook() {
2815
2751
  var STREAM_WRITE_ERROR = "STREAM_WRITE_ERROR";
2816
2752
  var STREAM_READ_ERROR = "STREAM_READ_ERROR";
2817
2753
  var STREAM_CLOSE_ERROR = "STREAM_CLOSE_ERROR";
2818
- var STREAM_STORE_ERROR = "STREAM_STORE_ERROR";
2819
2754
  var STREAM_ENDED = "STREAM_ENDED";
2820
- var STREAM_BACKPRESSURE_ERROR = "STREAM_BACKPRESSURE_ERROR";
2821
- function isStreamEnded(error) {
2822
- return typeof error === "object" && error !== null && error.type === STREAM_ENDED;
2823
- }
2824
- function isStreamWriteError(error) {
2825
- return typeof error === "object" && error !== null && error.type === STREAM_WRITE_ERROR;
2826
- }
2827
- function isStreamReadError(error) {
2828
- return typeof error === "object" && error !== null && error.type === STREAM_READ_ERROR;
2829
- }
2830
- function isStreamStoreError(error) {
2831
- return typeof error === "object" && error !== null && error.type === STREAM_STORE_ERROR;
2832
- }
2833
- function isStreamBackpressureError(error) {
2834
- return typeof error === "object" && error !== null && error.type === STREAM_BACKPRESSURE_ERROR;
2835
- }
2836
2755
  function streamWriteError(reason, message, cause) {
2837
2756
  return {
2838
2757
  type: STREAM_WRITE_ERROR,
@@ -2857,27 +2776,12 @@ function streamCloseError(reason, message, cause) {
2857
2776
  ...cause !== void 0 ? { cause } : {}
2858
2777
  };
2859
2778
  }
2860
- function streamStoreError(reason, message, cause) {
2861
- return {
2862
- type: STREAM_STORE_ERROR,
2863
- reason,
2864
- message,
2865
- ...cause !== void 0 ? { cause } : {}
2866
- };
2867
- }
2868
2779
  function streamEnded(finalPosition) {
2869
2780
  return {
2870
2781
  type: STREAM_ENDED,
2871
2782
  finalPosition
2872
2783
  };
2873
2784
  }
2874
- function streamBackpressureError(bufferedCount, highWaterMark) {
2875
- return {
2876
- type: STREAM_BACKPRESSURE_ERROR,
2877
- bufferedCount,
2878
- highWaterMark
2879
- };
2880
- }
2881
2785
 
2882
2786
  // src/streaming/backpressure.ts
2883
2787
  function createBackpressureController(options = {}) {
@@ -2949,9 +2853,6 @@ function createBackpressureController(options = {}) {
2949
2853
  }
2950
2854
  };
2951
2855
  }
2952
- function shouldApplyBackpressure(controller) {
2953
- return controller.state === "paused";
2954
- }
2955
2856
 
2956
2857
  // src/workflow/validation.ts
2957
2858
  function isInputValidationError(e) {
@@ -3831,7 +3732,7 @@ Use serializeResumeState() and deserializeResumeState() from 'awaitly/workflow'
3831
3732
  const results = [];
3832
3733
  let processedCount = 0;
3833
3734
  let lastPosition = -1;
3834
- const isStreamReader2 = (s) => {
3735
+ const isStreamReader = (s) => {
3835
3736
  return typeof s === "object" && s !== null && "read" in s && typeof s.read === "function";
3836
3737
  };
3837
3738
  const processItem = async (item, itemIndex, itemPosition, namespace) => {
@@ -3845,7 +3746,7 @@ Use serializeResumeState() and deserializeResumeState() from 'awaitly/workflow'
3845
3746
  );
3846
3747
  return { index: itemIndex, position: itemPosition, result: stepResult };
3847
3748
  };
3848
- if (isStreamReader2(source)) {
3749
+ if (isStreamReader(source)) {
3849
3750
  if (concurrency <= 1) {
3850
3751
  let itemPosition = source.position;
3851
3752
  let readResult = await source.read();
@@ -4146,2361 +4047,6 @@ Use serializeResumeState() and deserializeResumeState() from 'awaitly/workflow'
4146
4047
  return workflow;
4147
4048
  }
4148
4049
 
4149
- // src/durable/index.ts
4150
- var defaultStore;
4151
- function createMemorySnapshotStore() {
4152
- const store = /* @__PURE__ */ new Map();
4153
- return {
4154
- async save(id, snapshot) {
4155
- store.set(id, { snapshot, updatedAt: /* @__PURE__ */ new Date() });
4156
- },
4157
- async load(id) {
4158
- const entry = store.get(id);
4159
- return entry?.snapshot ?? null;
4160
- },
4161
- async delete(id) {
4162
- store.delete(id);
4163
- },
4164
- async list(options) {
4165
- const prefix = options?.prefix ?? "";
4166
- const limit = options?.limit ?? 100;
4167
- const results = [];
4168
- for (const [id, entry] of store.entries()) {
4169
- if (prefix && !id.startsWith(prefix)) continue;
4170
- results.push({ id, updatedAt: entry.updatedAt.toISOString() });
4171
- if (results.length >= limit) break;
4172
- }
4173
- results.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
4174
- return results;
4175
- },
4176
- async close() {
4177
- }
4178
- };
4179
- }
4180
- function getDefaultStore() {
4181
- if (defaultStore === void 0) {
4182
- defaultStore = createMemorySnapshotStore();
4183
- }
4184
- return defaultStore;
4185
- }
4186
- function isVersionMismatch(error) {
4187
- return typeof error === "object" && error !== null && error.type === "VERSION_MISMATCH";
4188
- }
4189
- function isConcurrentExecution(error) {
4190
- return typeof error === "object" && error !== null && error.type === "CONCURRENT_EXECUTION";
4191
- }
4192
- function isPersistenceError(error) {
4193
- return typeof error === "object" && error !== null && error.type === "PERSISTENCE_ERROR";
4194
- }
4195
- function isLeaseExpired(error) {
4196
- return typeof error === "object" && error !== null && error.type === "LEASE_EXPIRED";
4197
- }
4198
- function isIdempotencyConflict(error) {
4199
- return typeof error === "object" && error !== null && error.type === "IDEMPOTENCY_CONFLICT";
4200
- }
4201
- function hasWorkflowLock(store) {
4202
- return typeof store.tryAcquire === "function" && typeof store.release === "function";
4203
- }
4204
- var activeWorkflows = /* @__PURE__ */ new Set();
4205
- var pendingIdempotencyRuns = /* @__PURE__ */ new Map();
4206
- var durable = {
4207
- /**
4208
- * Execute a workflow with automatic state persistence.
4209
- *
4210
- * Features:
4211
- * - **Automatic checkpointing**: State is saved after each keyed step
4212
- * - **Crash recovery**: Resume from the last completed step on restart
4213
- * - **Version checking**: Reject resume if workflow logic version changed
4214
- * - **Concurrency control**: Prevent duplicate executions of the same workflow ID
4215
- * - **Cancellation support**: Integrates with AbortSignal for graceful shutdown
4216
- *
4217
- * ## How It Works
4218
- *
4219
- * 1. On start: Load existing state from store (if any)
4220
- * 2. Check version compatibility (reject if mismatch)
4221
- * 3. Pre-populate cache from loaded state (skip completed steps)
4222
- * 4. Execute workflow, persisting state after each keyed step
4223
- * 5. On completion: Delete stored state (clean up)
4224
- * 6. On error/cancellation: State remains for future resume
4225
- *
4226
- * ## Important Notes
4227
- *
4228
- * - **Only keyed steps are durable**: Use `{ key: 'step-name' }` option
4229
- * - **Steps should be idempotent**: They may be retried on resume
4230
- * - **Serialization**: State is JSON-serialized; complex objects may lose fidelity
4231
- *
4232
- * @param deps - Workflow dependencies (Result-returning functions)
4233
- * @param fn - Workflow function receiving ({ step, deps, ctx })
4234
- * @param options - Durable execution options
4235
- * @returns AsyncResult with workflow result or error
4236
- *
4237
- * @example
4238
- * ```typescript
4239
- * import { durable } from 'awaitly/workflow';
4240
- *
4241
- * // Zero-config: uses in-memory store (per process)
4242
- * const result = await durable.run(
4243
- * { fetchUser, createOrder, sendEmail },
4244
- * async ({ step, deps: { fetchUser, createOrder, sendEmail } }) => {
4245
- * const user = await step(() => fetchUser('123'), { key: 'fetch-user' });
4246
- * const order = await step(() => createOrder(user), { key: 'create-order' });
4247
- * await step(() => sendEmail(order), { key: 'send-email' });
4248
- * return order;
4249
- * },
4250
- * { id: 'checkout-123' }
4251
- * );
4252
- *
4253
- * // Persistence across restarts: pass a store adapter
4254
- * import { postgres } from 'awaitly-postgres'; // or awaitly-mongo, awaitly-libsql
4255
- * const store = postgres(process.env.DATABASE_URL);
4256
- * await durable.run(deps, fn, { id: 'checkout-123', store });
4257
- *
4258
- * if (result.ok) {
4259
- * console.log('Order completed:', result.value);
4260
- * } else if (isWorkflowCancelled(result.error)) {
4261
- * console.log('Workflow cancelled at:', result.error.lastStepKey);
4262
- * }
4263
- * ```
4264
- */
4265
- async run(deps, fn, options) {
4266
- const {
4267
- id,
4268
- store: storeOption,
4269
- version = 1,
4270
- allowConcurrent = false,
4271
- lockTtlMs = 6e4,
4272
- heartbeatIntervalMs,
4273
- abortOnLeaseLoss,
4274
- metadata,
4275
- signal,
4276
- createContext,
4277
- onEvent,
4278
- onError,
4279
- onVersionMismatch,
4280
- idempotencyKey,
4281
- input
4282
- } = options;
4283
- const effectiveStore = storeOption ?? getDefaultStore();
4284
- let resolveIdempotencyRun;
4285
- if (idempotencyKey) {
4286
- const idemId = `idem:${idempotencyKey}`;
4287
- const pending = pendingIdempotencyRuns.get(idemId);
4288
- if (pending) {
4289
- return await pending;
4290
- }
4291
- pendingIdempotencyRuns.set(idemId, new Promise((r) => {
4292
- resolveIdempotencyRun = r;
4293
- }));
4294
- try {
4295
- const idemSnapshot = await effectiveStore.load(idemId);
4296
- if (idemSnapshot) {
4297
- if (input !== void 0 && idemSnapshot.metadata?.input !== void 0) {
4298
- const storedInput = JSON.stringify(idemSnapshot.metadata.input);
4299
- const currentInput = JSON.stringify(input);
4300
- if (storedInput !== currentInput) {
4301
- const result = err({
4302
- type: "IDEMPOTENCY_CONFLICT",
4303
- idempotencyKey,
4304
- workflowId: id,
4305
- message: `Idempotency key '${idempotencyKey}' already used with different input for workflow '${id}'.`
4306
- });
4307
- resolveIdempotencyRun(result);
4308
- pendingIdempotencyRuns.delete(idemId);
4309
- resolveIdempotencyRun = void 0;
4310
- return result;
4311
- }
4312
- }
4313
- if (idemSnapshot.execution.status === "completed" && idemSnapshot.metadata?.finalResult !== void 0) {
4314
- const result = idemSnapshot.metadata.finalResult;
4315
- resolveIdempotencyRun(result);
4316
- pendingIdempotencyRuns.delete(idemId);
4317
- resolveIdempotencyRun = void 0;
4318
- return result;
4319
- }
4320
- if (idemSnapshot.execution.status === "running") {
4321
- const result = err({
4322
- type: "CONCURRENT_EXECUTION",
4323
- workflowId: id,
4324
- message: `Workflow '${id}' with idempotency key '${idempotencyKey}' is already running.`,
4325
- reason: "cross-process"
4326
- });
4327
- resolveIdempotencyRun(result);
4328
- pendingIdempotencyRuns.delete(idemId);
4329
- resolveIdempotencyRun = void 0;
4330
- return result;
4331
- }
4332
- }
4333
- } catch {
4334
- }
4335
- try {
4336
- await effectiveStore.save(idemId, {
4337
- formatVersion: 1,
4338
- steps: {},
4339
- execution: {
4340
- status: "running",
4341
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
4342
- },
4343
- metadata: {
4344
- workflowId: id,
4345
- idempotencyKey,
4346
- input
4347
- }
4348
- });
4349
- } catch {
4350
- }
4351
- }
4352
- if (!allowConcurrent && activeWorkflows.has(id)) {
4353
- const error = {
4354
- type: "CONCURRENT_EXECUTION",
4355
- workflowId: id,
4356
- message: `Workflow '${id}' is already running. Set allowConcurrent: true to allow parallel executions.`,
4357
- reason: "in-process"
4358
- };
4359
- return err(error);
4360
- }
4361
- let leaseOwnerToken = null;
4362
- if (!allowConcurrent && hasWorkflowLock(effectiveStore)) {
4363
- let lease;
4364
- try {
4365
- lease = await effectiveStore.tryAcquire(id, { ttlMs: lockTtlMs });
4366
- } catch (lockError) {
4367
- const error = {
4368
- type: "PERSISTENCE_ERROR",
4369
- operation: "load",
4370
- workflowId: id,
4371
- cause: lockError,
4372
- message: `Failed to acquire lock for workflow '${id}': ${lockError instanceof Error ? lockError.message : String(lockError)}`
4373
- };
4374
- return err(error);
4375
- }
4376
- if (lease === null) {
4377
- const error = {
4378
- type: "CONCURRENT_EXECUTION",
4379
- workflowId: id,
4380
- message: `Workflow '${id}' is already running (lease held by another process). Set allowConcurrent: true to allow parallel executions.`,
4381
- reason: "cross-process"
4382
- };
4383
- return err(error);
4384
- }
4385
- leaseOwnerToken = lease.ownerToken;
4386
- }
4387
- let heartbeatTimer;
4388
- let leaseAbortController;
4389
- const lockStore = effectiveStore;
4390
- if (leaseOwnerToken && typeof lockStore.renew === "function") {
4391
- const heartbeatMs = heartbeatIntervalMs ?? Math.floor(lockTtlMs / 3);
4392
- leaseAbortController = new AbortController();
4393
- heartbeatTimer = setInterval(async () => {
4394
- try {
4395
- const renewed = await lockStore.renew(id, leaseOwnerToken, { ttlMs: lockTtlMs });
4396
- if (!renewed) {
4397
- leaseAbortController.abort(new Error("Lease expired"));
4398
- }
4399
- } catch {
4400
- leaseAbortController.abort(new Error("Lease renewal failed"));
4401
- }
4402
- }, heartbeatMs);
4403
- }
4404
- activeWorkflows.add(id);
4405
- let durableResult;
4406
- try {
4407
- let existingSnapshot = null;
4408
- try {
4409
- existingSnapshot = await effectiveStore.load(id);
4410
- } catch (loadError) {
4411
- const error = {
4412
- type: "PERSISTENCE_ERROR",
4413
- operation: "load",
4414
- workflowId: id,
4415
- cause: loadError,
4416
- message: `Failed to load state for workflow '${id}': ${loadError instanceof Error ? loadError.message : String(loadError)}`
4417
- };
4418
- durableResult = err(error);
4419
- return err(error);
4420
- }
4421
- if (existingSnapshot) {
4422
- try {
4423
- assertValidSnapshot(existingSnapshot);
4424
- } catch (validationError2) {
4425
- if (validationError2 instanceof SnapshotFormatError) {
4426
- const error = {
4427
- type: "PERSISTENCE_ERROR",
4428
- operation: "load",
4429
- workflowId: id,
4430
- cause: validationError2,
4431
- message: `Invalid snapshot format for workflow '${id}': ${validationError2.message}`
4432
- };
4433
- durableResult = err(error);
4434
- return err(error);
4435
- }
4436
- throw validationError2;
4437
- }
4438
- }
4439
- if (existingSnapshot) {
4440
- const storedVersion = typeof existingSnapshot.metadata?.version === "number" ? existingSnapshot.metadata.version : 1;
4441
- if (storedVersion !== version) {
4442
- const error = {
4443
- type: "VERSION_MISMATCH",
4444
- workflowId: id,
4445
- storedVersion,
4446
- requestedVersion: version,
4447
- currentVersion: version,
4448
- message: `Workflow '${id}' has stored state at version ${storedVersion} but this run requested version ${version}. Migrate the stored state to the new version, or clear state for this id (e.g. durable.deleteState(store, '${id}')) and re-run.`
4449
- };
4450
- if (!onVersionMismatch) {
4451
- durableResult = err(error);
4452
- return err(error);
4453
- }
4454
- const resolution = await Promise.resolve(
4455
- onVersionMismatch({ id, storedVersion, requestedVersion: version })
4456
- );
4457
- if (resolution === "throw") {
4458
- durableResult = err(error);
4459
- return err(error);
4460
- }
4461
- if (resolution === "clear") {
4462
- try {
4463
- await effectiveStore.delete(id);
4464
- } catch {
4465
- }
4466
- existingSnapshot = null;
4467
- } else {
4468
- existingSnapshot = resolution.migratedSnapshot;
4469
- }
4470
- }
4471
- }
4472
- const emitDurableEvent = (event, ctx) => {
4473
- if (onEvent) {
4474
- onEvent(event, ctx);
4475
- }
4476
- };
4477
- const resumeCollector = createResumeStateCollector();
4478
- const workflowOptions = {
4479
- // Restore from existing snapshot
4480
- snapshot: existingSnapshot,
4481
- // Persist after each keyed step
4482
- onAfterStep: async (stepKey, result2, wfId, ctx) => {
4483
- try {
4484
- const collectedState = resumeCollector.getResumeState();
4485
- const steps = {};
4486
- for (const [key, entry] of collectedState.steps) {
4487
- if (entry.result.ok) {
4488
- steps[key] = { ok: true, value: entry.result.value };
4489
- } else {
4490
- const cause = entry.result.cause;
4491
- const serializedCause = cause instanceof Error ? serializeError(cause) : serializeThrown(cause);
4492
- const origin = entry.meta?.origin === "throw" ? "throw" : "result";
4493
- steps[key] = {
4494
- ok: false,
4495
- error: entry.result.error,
4496
- cause: serializedCause,
4497
- meta: { origin }
4498
- };
4499
- }
4500
- }
4501
- const currentSnapshot = {
4502
- formatVersion: 1,
4503
- workflowName: id,
4504
- steps,
4505
- execution: {
4506
- status: "running",
4507
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
4508
- currentStepId: stepKey
4509
- },
4510
- metadata: {
4511
- ...existingSnapshot?.metadata ?? {},
4512
- ...metadata,
4513
- version,
4514
- lastStepKey: stepKey
4515
- }
4516
- };
4517
- let snapshotToSave = existingSnapshot ? mergeSnapshots(existingSnapshot, currentSnapshot) : currentSnapshot;
4518
- if (snapshotToSave.warnings && snapshotToSave.warnings.length > 0) {
4519
- const currentStepKeys = new Set(Object.keys(currentSnapshot.steps));
4520
- const filtered = snapshotToSave.warnings.filter(
4521
- (w) => !currentStepKeys.has(w.stepId)
4522
- );
4523
- snapshotToSave = {
4524
- ...snapshotToSave,
4525
- warnings: filtered.length > 0 ? filtered : void 0
4526
- };
4527
- }
4528
- await effectiveStore.save(id, snapshotToSave);
4529
- emitDurableEvent(
4530
- {
4531
- type: "persist_success",
4532
- workflowId: wfId,
4533
- stepKey,
4534
- ts: Date.now(),
4535
- context: ctx
4536
- },
4537
- ctx
4538
- );
4539
- } catch (persistError) {
4540
- emitDurableEvent(
4541
- {
4542
- type: "persist_error",
4543
- workflowId: wfId,
4544
- stepKey,
4545
- error: persistError,
4546
- ts: Date.now(),
4547
- context: ctx
4548
- },
4549
- ctx
4550
- );
4551
- }
4552
- },
4553
- // Forward events and collect step results for snapshot building
4554
- onEvent: (event, ctx) => {
4555
- resumeCollector.handleEvent(event);
4556
- emitDurableEvent(event, ctx);
4557
- },
4558
- onError,
4559
- signal: leaseAbortController && signal ? AbortSignal.any([signal, leaseAbortController.signal]) : leaseAbortController?.signal ?? signal,
4560
- createContext
4561
- };
4562
- let workflowInstance;
4563
- try {
4564
- workflowInstance = createWorkflow(id, deps, workflowOptions);
4565
- } catch (createError) {
4566
- if (createError instanceof SnapshotFormatError) {
4567
- const error = {
4568
- type: "PERSISTENCE_ERROR",
4569
- operation: "load",
4570
- workflowId: id,
4571
- cause: createError,
4572
- message: `Invalid snapshot format for workflow '${id}': ${createError.message}`
4573
- };
4574
- durableResult = err(error);
4575
- return err(error);
4576
- }
4577
- throw createError;
4578
- }
4579
- let result;
4580
- try {
4581
- result = await workflowInstance.run(fn);
4582
- } catch (runError) {
4583
- if (runError instanceof SnapshotFormatError || runError instanceof SnapshotDecodeError) {
4584
- const error = {
4585
- type: "PERSISTENCE_ERROR",
4586
- operation: "load",
4587
- workflowId: id,
4588
- cause: runError,
4589
- message: `Invalid snapshot format for workflow '${id}': ${runError.message}`
4590
- };
4591
- durableResult = err(error);
4592
- return err(error);
4593
- }
4594
- throw runError;
4595
- }
4596
- if (abortOnLeaseLoss !== false && leaseAbortController?.signal.aborted) {
4597
- const leaseErr = err({
4598
- type: "LEASE_EXPIRED",
4599
- workflowId: id,
4600
- message: `Lease expired for workflow '${id}' during execution. The workflow may have been reclaimed by another process.`
4601
- });
4602
- durableResult = leaseErr;
4603
- return leaseErr;
4604
- }
4605
- if (result.ok) {
4606
- try {
4607
- await effectiveStore.delete(id);
4608
- } catch (deleteError) {
4609
- const error = {
4610
- type: "PERSISTENCE_ERROR",
4611
- operation: "delete",
4612
- workflowId: id,
4613
- cause: deleteError,
4614
- message: `Failed to delete state for workflow '${id}': ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`
4615
- };
4616
- durableResult = err(error);
4617
- return err(error);
4618
- }
4619
- if (idempotencyKey) {
4620
- const idemId = `idem:${idempotencyKey}`;
4621
- try {
4622
- await effectiveStore.save(idemId, {
4623
- formatVersion: 1,
4624
- steps: {},
4625
- execution: {
4626
- status: "completed",
4627
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
4628
- completedAt: (/* @__PURE__ */ new Date()).toISOString()
4629
- },
4630
- metadata: {
4631
- workflowId: id,
4632
- idempotencyKey,
4633
- input,
4634
- finalResult: result
4635
- }
4636
- });
4637
- } catch {
4638
- }
4639
- }
4640
- }
4641
- durableResult = result;
4642
- return result;
4643
- } finally {
4644
- activeWorkflows.delete(id);
4645
- if (resolveIdempotencyRun) {
4646
- resolveIdempotencyRun(durableResult);
4647
- pendingIdempotencyRuns.delete(`idem:${idempotencyKey}`);
4648
- }
4649
- if (heartbeatTimer) {
4650
- clearInterval(heartbeatTimer);
4651
- }
4652
- if (leaseOwnerToken !== null && hasWorkflowLock(effectiveStore)) {
4653
- try {
4654
- await effectiveStore.release(id, leaseOwnerToken);
4655
- } catch {
4656
- }
4657
- }
4658
- }
4659
- },
4660
- /**
4661
- * Clear all persisted workflow state from the store.
4662
- * Use for admin/testing. If the store implements `clear()`, that is used;
4663
- * otherwise clears by listing and deleting in pages.
4664
- *
4665
- * @param store - Snapshot store
4666
- */
4667
- async clearState(store) {
4668
- const storeWithClear = store;
4669
- if (typeof storeWithClear.clear === "function") {
4670
- await storeWithClear.clear();
4671
- return;
4672
- }
4673
- const limit = 100;
4674
- for (; ; ) {
4675
- const entries = await store.list({ limit });
4676
- if (entries.length === 0) break;
4677
- const ids = entries.map((e) => e.id);
4678
- await this.deleteStates(store, ids, { continueOnError: true });
4679
- if (entries.length < limit) break;
4680
- }
4681
- },
4682
- /**
4683
- * Check if a workflow ID has persisted state (can be resumed).
4684
- *
4685
- * @param store - Snapshot store
4686
- * @param id - Workflow execution ID
4687
- * @returns `true` if state exists, `false` otherwise (including on store errors)
4688
- */
4689
- async hasState(store, id) {
4690
- try {
4691
- const snapshot = await store.load(id);
4692
- return snapshot !== null;
4693
- } catch {
4694
- return false;
4695
- }
4696
- },
4697
- /**
4698
- * Delete persisted state for a workflow (cancel resume capability).
4699
- * Deleting is effectively an ack/reset: the workflow can no longer resume from that state.
4700
- * If you delete while a run is in flight, the run continues; on success it may delete again (no-op) or save (recreating state).
4701
- * For multi-worker safety, prefer deleting only when the workflow is not running or when you hold the lock.
4702
- *
4703
- * @param store - Snapshot store
4704
- * @param id - Workflow execution ID
4705
- * @returns `true` on success, `false` on store errors
4706
- */
4707
- async deleteState(store, id) {
4708
- try {
4709
- await store.delete(id);
4710
- return true;
4711
- } catch {
4712
- return false;
4713
- }
4714
- },
4715
- /**
4716
- * Bulk delete persisted state for multiple workflow IDs (best-effort).
4717
- * Use for admin/cleanup. Deletes in a loop with optional concurrency.
4718
- *
4719
- * @param store - Snapshot store
4720
- * @param ids - Workflow execution IDs to delete
4721
- * @param options - Optional concurrency and error handling
4722
- * @returns Count of deleted entries and any errors when continueOnError is true
4723
- */
4724
- async deleteStates(store, ids, options = {}) {
4725
- const { concurrency = 10, continueOnError = true } = options;
4726
- if (ids.length === 0) {
4727
- return { deleted: 0 };
4728
- }
4729
- const errors = [];
4730
- let deleted = 0;
4731
- const run2 = async (id) => {
4732
- try {
4733
- await store.delete(id);
4734
- deleted++;
4735
- } catch (error) {
4736
- if (continueOnError) errors.push({ id, error });
4737
- else throw error;
4738
- }
4739
- };
4740
- const limit = Math.max(1, concurrency);
4741
- for (let i = 0; i < ids.length; i += limit) {
4742
- const batch = ids.slice(i, i + limit);
4743
- await Promise.all(batch.map((id) => run2(id)));
4744
- }
4745
- return errors.length > 0 ? { deleted, errors } : { deleted };
4746
- },
4747
- /**
4748
- * List workflow IDs with persisted state.
4749
- *
4750
- * @param store - Snapshot store
4751
- * @param options - Optional prefix and limit
4752
- * @returns Array of { id, updatedAt } entries
4753
- */
4754
- async listPending(store, options) {
4755
- try {
4756
- return await store.list(options);
4757
- } catch {
4758
- return [];
4759
- }
4760
- }
4761
- };
4762
-
4763
- // src/versioning.ts
4764
- function isMigrationError(error) {
4765
- return typeof error === "object" && error !== null && error.type === "MIGRATION_ERROR";
4766
- }
4767
- function isVersionIncompatibleError(error) {
4768
- return typeof error === "object" && error !== null && error.type === "VERSION_INCOMPATIBLE";
4769
- }
4770
- async function migrateState(state, targetVersion, migrations) {
4771
- let currentState = state.state;
4772
- let currentVersion = state.version;
4773
- if (currentVersion > targetVersion) {
4774
- return err({
4775
- type: "VERSION_INCOMPATIBLE",
4776
- stateVersion: currentVersion,
4777
- currentVersion: targetVersion,
4778
- reason: "State version is higher than current workflow version. Cannot downgrade."
4779
- });
4780
- }
4781
- if (currentVersion === targetVersion) {
4782
- return ok2({ version: currentVersion, state: currentState });
4783
- }
4784
- while (currentVersion < targetVersion) {
4785
- const migration = migrations[currentVersion];
4786
- if (!migration) {
4787
- return err({
4788
- type: "VERSION_INCOMPATIBLE",
4789
- stateVersion: state.version,
4790
- currentVersion: targetVersion,
4791
- reason: `No migration found for version ${currentVersion} to ${currentVersion + 1}`
4792
- });
4793
- }
4794
- try {
4795
- currentState = await migration(currentState);
4796
- currentVersion++;
4797
- } catch (cause) {
4798
- return err({
4799
- type: "MIGRATION_ERROR",
4800
- fromVersion: currentVersion,
4801
- toVersion: currentVersion + 1,
4802
- cause
4803
- });
4804
- }
4805
- }
4806
- return ok2({ version: currentVersion, state: currentState });
4807
- }
4808
- function createVersionedStateLoader(config) {
4809
- const { version, migrations = {}, strictVersioning = true } = config;
4810
- return async (versionedState) => {
4811
- if (!versionedState) {
4812
- return ok2(void 0);
4813
- }
4814
- if (strictVersioning && versionedState.version > version) {
4815
- return err({
4816
- type: "VERSION_INCOMPATIBLE",
4817
- stateVersion: versionedState.version,
4818
- currentVersion: version,
4819
- reason: "Saved state is from a newer workflow version"
4820
- });
4821
- }
4822
- if (versionedState.version === version) {
4823
- return ok2(versionedState.state);
4824
- }
4825
- const result = await migrateState(versionedState, version, migrations);
4826
- if (!result.ok) {
4827
- return result;
4828
- }
4829
- return ok2(result.value.state);
4830
- };
4831
- }
4832
- function createVersionedState(state, version) {
4833
- return { version, state };
4834
- }
4835
- function parseVersionedState(json) {
4836
- if (!json) return null;
4837
- try {
4838
- const parsed = typeof json === "string" ? JSON.parse(json) : json;
4839
- if (typeof parsed !== "object" || parsed === null || !("version" in parsed) || typeof parsed.version !== "number" || !("state" in parsed) || !parsed.state || !Array.isArray(parsed.state.steps)) {
4840
- return null;
4841
- }
4842
- const typedParsed = parsed;
4843
- const steps = new Map(typedParsed.state.steps);
4844
- return {
4845
- version: typedParsed.version,
4846
- state: { steps }
4847
- };
4848
- } catch {
4849
- return null;
4850
- }
4851
- }
4852
- function stringifyVersionedState(state) {
4853
- return JSON.stringify({
4854
- version: state.version,
4855
- state: {
4856
- steps: Array.from(state.state.steps.entries())
4857
- }
4858
- });
4859
- }
4860
- function createKeyRenameMigration(renames) {
4861
- return (state) => {
4862
- const newSteps = /* @__PURE__ */ new Map();
4863
- for (const [key, entry] of state.steps) {
4864
- const newKey = renames[key] ?? key;
4865
- newSteps.set(newKey, entry);
4866
- }
4867
- return { steps: newSteps };
4868
- };
4869
- }
4870
- function createKeyRemoveMigration(keysToRemove) {
4871
- const keysSet = new Set(keysToRemove);
4872
- return (state) => {
4873
- const newSteps = /* @__PURE__ */ new Map();
4874
- for (const [key, entry] of state.steps) {
4875
- if (!keysSet.has(key)) {
4876
- newSteps.set(key, entry);
4877
- }
4878
- }
4879
- return { steps: newSteps };
4880
- };
4881
- }
4882
- function createValueTransformMigration(transforms) {
4883
- return (state) => {
4884
- const newSteps = /* @__PURE__ */ new Map();
4885
- for (const [key, entry] of state.steps) {
4886
- const transform = transforms[key];
4887
- newSteps.set(key, transform ? transform(entry) : entry);
4888
- }
4889
- return { steps: newSteps };
4890
- };
4891
- }
4892
- function composeMigrations(migrations) {
4893
- return async (state) => {
4894
- let currentState = state;
4895
- for (const migration of migrations) {
4896
- currentState = await migration(currentState);
4897
- }
4898
- return currentState;
4899
- };
4900
- }
4901
-
4902
- // src/hitl/index.ts
4903
- function createMemoryApprovalStore() {
4904
- const approvals = /* @__PURE__ */ new Map();
4905
- return {
4906
- async getApproval(key) {
4907
- const approval = approvals.get(key);
4908
- if (!approval) {
4909
- return { status: "pending" };
4910
- }
4911
- if (approval.expiresAt && Date.now() > approval.expiresAt) {
4912
- return { status: "expired", expiredAt: approval.expiresAt };
4913
- }
4914
- return approval;
4915
- },
4916
- async createApproval(key, options) {
4917
- approvals.set(key, {
4918
- status: "pending",
4919
- metadata: options?.metadata,
4920
- expiresAt: options?.expiresAt
4921
- });
4922
- },
4923
- async grantApproval(key, value, options) {
4924
- approvals.set(key, {
4925
- status: "approved",
4926
- value,
4927
- approvedBy: options?.approvedBy,
4928
- approvedAt: Date.now()
4929
- });
4930
- },
4931
- async rejectApproval(key, reason, options) {
4932
- approvals.set(key, {
4933
- status: "rejected",
4934
- reason,
4935
- rejectedBy: options?.rejectedBy,
4936
- rejectedAt: Date.now()
4937
- });
4938
- },
4939
- async editApproval(key, originalValue, editedValue, options) {
4940
- approvals.set(key, {
4941
- status: "edited",
4942
- originalValue,
4943
- editedValue,
4944
- editedBy: options?.editedBy,
4945
- editedAt: Date.now()
4946
- });
4947
- },
4948
- async cancelApproval(key) {
4949
- approvals.delete(key);
4950
- },
4951
- async listPending(options) {
4952
- const pending = [];
4953
- for (const [key, approval] of approvals) {
4954
- if (approval.status === "pending") {
4955
- if (!options?.prefix || key.startsWith(options.prefix)) {
4956
- pending.push(key);
4957
- }
4958
- }
4959
- }
4960
- return pending;
4961
- }
4962
- };
4963
- }
4964
- function createMemoryWorkflowStateStore() {
4965
- const states = /* @__PURE__ */ new Map();
4966
- return {
4967
- async save(state) {
4968
- states.set(state.runId, { ...state, updatedAt: Date.now() });
4969
- },
4970
- async load(runId) {
4971
- return states.get(runId);
4972
- },
4973
- async delete(runId) {
4974
- states.delete(runId);
4975
- },
4976
- async list(options) {
4977
- const results = [];
4978
- for (const [runId, state] of states) {
4979
- if (options?.workflowName && state.workflowName !== options.workflowName) {
4980
- continue;
4981
- }
4982
- if (options?.hasPendingApprovals !== void 0) {
4983
- const hasPending = state.pendingApprovals.length > 0;
4984
- if (options.hasPendingApprovals !== hasPending) {
4985
- continue;
4986
- }
4987
- }
4988
- results.push(runId);
4989
- }
4990
- return results;
4991
- },
4992
- async findByPendingApproval(approvalKey) {
4993
- const results = [];
4994
- for (const [runId, state] of states) {
4995
- if (state.pendingApprovals.includes(approvalKey)) {
4996
- results.push(runId);
4997
- }
4998
- }
4999
- return results;
5000
- }
5001
- };
5002
- }
5003
- function createHITLOrchestrator(options) {
5004
- const {
5005
- approvalStore,
5006
- workflowStateStore,
5007
- defaultExpirationMs = 7 * 24 * 60 * 60 * 1e3,
5008
- // 7 days
5009
- logger = () => {
5010
- },
5011
- notificationChannel
5012
- } = options;
5013
- async function execute(workflowName, workflowFactory, workflowFn, input, opts) {
5014
- const runId = opts?.runId ?? crypto.randomUUID();
5015
- const collector = createApprovalStateCollector();
5016
- const workflow = workflowFactory({
5017
- onEvent: collector.handleEvent
5018
- });
5019
- const args = input;
5020
- const result = await workflow.run(
5021
- async (context) => workflowFn({ ...context, args })
5022
- );
5023
- const pendingApprovals = collector.getPendingApprovals().map((p) => p.stepKey);
5024
- if (pendingApprovals.length > 0) {
5025
- const state = {
5026
- runId,
5027
- workflowName,
5028
- resumeState: collector.getResumeState(),
5029
- pendingApprovals,
5030
- input,
5031
- metadata: opts?.metadata,
5032
- startedAt: Date.now(),
5033
- updatedAt: Date.now()
5034
- };
5035
- await workflowStateStore.save(state);
5036
- let reason;
5037
- let pendingMetadata;
5038
- if (!result.ok && isPendingApproval(result.error)) {
5039
- reason = result.error.reason;
5040
- pendingMetadata = result.error.metadata;
5041
- }
5042
- const expiresAt = Date.now() + defaultExpirationMs;
5043
- for (const key of pendingApprovals) {
5044
- await approvalStore.createApproval(key, {
5045
- metadata: { ...pendingMetadata, runId, workflowName },
5046
- expiresAt
5047
- });
5048
- }
5049
- if (notificationChannel) {
5050
- for (const key of pendingApprovals) {
5051
- try {
5052
- const pendingInfo = collector.getPendingApprovals().find((p) => p.stepKey === key);
5053
- await notificationChannel.onApprovalNeeded({
5054
- approvalKey: key,
5055
- runId,
5056
- workflowName,
5057
- reason,
5058
- metadata: { ...opts?.metadata, ...pendingMetadata },
5059
- expiresAt,
5060
- summary: reason ?? `Approval needed for ${key}`,
5061
- pendingArgs: pendingInfo?.error.metadata
5062
- });
5063
- } catch (notifyError) {
5064
- logger(`Failed to notify for approval ${key}: ${notifyError}`);
5065
- }
5066
- }
5067
- }
5068
- logger(`Workflow ${runId} paused, waiting for: ${pendingApprovals.join(", ")}`);
5069
- return {
5070
- status: "paused",
5071
- runId,
5072
- pendingApprovals,
5073
- reason
5074
- };
5075
- }
5076
- logger(`Workflow ${runId} completed`);
5077
- return { status: "completed", result };
5078
- }
5079
- async function resume(runId, workflowFactory, workflowFn) {
5080
- const savedState = await workflowStateStore.load(runId);
5081
- if (!savedState) {
5082
- throw new Error(`Workflow run not found: ${runId}`);
5083
- }
5084
- for (const key of savedState.pendingApprovals) {
5085
- const status = await approvalStore.getApproval(key);
5086
- if (status.status === "pending") {
5087
- return {
5088
- status: "paused",
5089
- runId,
5090
- pendingApprovals: savedState.pendingApprovals
5091
- };
5092
- }
5093
- if (status.status === "rejected") {
5094
- }
5095
- if (status.status === "expired") {
5096
- throw new Error(`Approval ${key} has expired`);
5097
- }
5098
- }
5099
- let resumeState = savedState.resumeState;
5100
- for (const key of savedState.pendingApprovals) {
5101
- const status = await approvalStore.getApproval(key);
5102
- if (status.status === "approved") {
5103
- resumeState = injectApproval(resumeState, {
5104
- stepKey: key,
5105
- value: status.value
5106
- });
5107
- } else if (status.status === "edited") {
5108
- resumeState = injectApproval(resumeState, {
5109
- stepKey: key,
5110
- value: status.editedValue
5111
- });
5112
- }
5113
- }
5114
- const collector = createApprovalStateCollector();
5115
- const workflow = workflowFactory({
5116
- resumeState,
5117
- onEvent: collector.handleEvent
5118
- });
5119
- const args = savedState.input;
5120
- const result = await workflow.run(
5121
- async (context) => workflowFn({ ...context, args })
5122
- );
5123
- const newPendingApprovals = collector.getPendingApprovals().map((p) => p.stepKey);
5124
- if (newPendingApprovals.length > 0) {
5125
- const updatedState = {
5126
- ...savedState,
5127
- resumeState: collector.getResumeState(),
5128
- pendingApprovals: newPendingApprovals,
5129
- updatedAt: Date.now()
5130
- };
5131
- await workflowStateStore.save(updatedState);
5132
- for (const key of newPendingApprovals) {
5133
- await approvalStore.createApproval(key, {
5134
- metadata: { runId, workflowName: savedState.workflowName },
5135
- expiresAt: Date.now() + defaultExpirationMs
5136
- });
5137
- }
5138
- logger(`Workflow ${runId} paused again, waiting for: ${newPendingApprovals.join(", ")}`);
5139
- let reason;
5140
- if (!result.ok && isPendingApproval(result.error)) {
5141
- reason = result.error.reason;
5142
- }
5143
- return {
5144
- status: "paused",
5145
- runId,
5146
- pendingApprovals: newPendingApprovals,
5147
- reason
5148
- };
5149
- }
5150
- await workflowStateStore.delete(runId);
5151
- logger(`Workflow ${runId} resumed and completed`);
5152
- return { status: "resumed", runId, result };
5153
- }
5154
- async function grantApprovalFn(approvalKey, value, opts) {
5155
- await approvalStore.grantApproval(approvalKey, value, {
5156
- approvedBy: opts?.approvedBy
5157
- });
5158
- const grantedAt = Date.now();
5159
- const resumedWorkflows = [];
5160
- if (opts?.autoResume !== false) {
5161
- const waitingWorkflows = await workflowStateStore.findByPendingApproval(approvalKey);
5162
- for (const runId of waitingWorkflows) {
5163
- resumedWorkflows.push(runId);
5164
- }
5165
- }
5166
- if (notificationChannel?.onApprovalResolved) {
5167
- await notificationChannel.onApprovalResolved({
5168
- approvalKey,
5169
- action: "approved",
5170
- actorId: opts?.approvedBy,
5171
- resolvedAt: grantedAt,
5172
- value
5173
- });
5174
- }
5175
- logger(`Approval ${approvalKey} granted by ${opts?.approvedBy ?? "unknown"}`);
5176
- return { grantedAt, resumedWorkflows };
5177
- }
5178
- async function rejectApprovalFn(approvalKey, reason, opts) {
5179
- await approvalStore.rejectApproval(approvalKey, reason, {
5180
- rejectedBy: opts?.rejectedBy
5181
- });
5182
- if (notificationChannel?.onApprovalResolved) {
5183
- await notificationChannel.onApprovalResolved({
5184
- approvalKey,
5185
- action: "rejected",
5186
- actorId: opts?.rejectedBy,
5187
- resolvedAt: Date.now(),
5188
- reason
5189
- });
5190
- }
5191
- logger(`Approval ${approvalKey} rejected: ${reason}`);
5192
- }
5193
- async function editApprovalFn(approvalKey, originalValue, editedValue, opts) {
5194
- await approvalStore.editApproval(approvalKey, originalValue, editedValue, {
5195
- editedBy: opts?.editedBy
5196
- });
5197
- const editedAt = Date.now();
5198
- if (notificationChannel?.onApprovalResolved) {
5199
- await notificationChannel.onApprovalResolved({
5200
- approvalKey,
5201
- action: "edited",
5202
- actorId: opts?.editedBy,
5203
- resolvedAt: editedAt,
5204
- value: editedValue,
5205
- originalValue
5206
- });
5207
- }
5208
- logger(`Approval ${approvalKey} edited by ${opts?.editedBy ?? "unknown"}`);
5209
- return { editedAt };
5210
- }
5211
- async function pollApproval(approvalKey, opts) {
5212
- const {
5213
- intervalMs = 1e3,
5214
- maxPolls,
5215
- timeoutMs,
5216
- onPollStart,
5217
- onPollComplete
5218
- } = opts ?? {};
5219
- const startTime = Date.now();
5220
- let pollCount = 0;
5221
- while (true) {
5222
- onPollStart?.();
5223
- const status = await approvalStore.getApproval(approvalKey);
5224
- onPollComplete?.(status);
5225
- if (status.status !== "pending") {
5226
- return status;
5227
- }
5228
- pollCount++;
5229
- if (maxPolls !== void 0 && pollCount >= maxPolls) {
5230
- return { status: "pending" };
5231
- }
5232
- if (timeoutMs !== void 0 && Date.now() - startTime >= timeoutMs) {
5233
- return { status: "pending" };
5234
- }
5235
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
5236
- }
5237
- }
5238
- async function getWorkflowStatus(runId) {
5239
- return workflowStateStore.load(runId);
5240
- }
5241
- async function listPendingWorkflows(workflowName) {
5242
- return workflowStateStore.list({
5243
- workflowName,
5244
- hasPendingApprovals: true
5245
- });
5246
- }
5247
- async function cleanup(maxAgeMs) {
5248
- const allWorkflows = await workflowStateStore.list();
5249
- let cleaned = 0;
5250
- const cutoff = Date.now() - maxAgeMs;
5251
- for (const runId of allWorkflows) {
5252
- const state = await workflowStateStore.load(runId);
5253
- if (state && state.updatedAt < cutoff && state.pendingApprovals.length === 0) {
5254
- await workflowStateStore.delete(runId);
5255
- cleaned++;
5256
- }
5257
- }
5258
- logger(`Cleaned up ${cleaned} old workflow states`);
5259
- return cleaned;
5260
- }
5261
- return {
5262
- execute,
5263
- resume,
5264
- grantApproval: grantApprovalFn,
5265
- rejectApproval: rejectApprovalFn,
5266
- editApproval: editApprovalFn,
5267
- pollApproval,
5268
- getWorkflowStatus,
5269
- listPendingWorkflows,
5270
- cleanup
5271
- };
5272
- }
5273
- function createApprovalWebhookHandler(store) {
5274
- return async (request) => {
5275
- const { key, action, value, originalValue, editedValue, reason, actorId } = request;
5276
- try {
5277
- switch (action) {
5278
- case "approve":
5279
- await store.grantApproval(key, value, { approvedBy: actorId });
5280
- return {
5281
- success: true,
5282
- message: `Approval ${key} granted`,
5283
- data: { key, action, timestamp: Date.now() }
5284
- };
5285
- case "reject":
5286
- if (!reason) {
5287
- return { success: false, message: "Reason is required for rejection" };
5288
- }
5289
- await store.rejectApproval(key, reason, { rejectedBy: actorId });
5290
- return {
5291
- success: true,
5292
- message: `Approval ${key} rejected`,
5293
- data: { key, action, timestamp: Date.now() }
5294
- };
5295
- case "edit":
5296
- if (originalValue === void 0 || editedValue === void 0) {
5297
- return { success: false, message: "Both originalValue and editedValue are required for edit" };
5298
- }
5299
- await store.editApproval(key, originalValue, editedValue, { editedBy: actorId });
5300
- return {
5301
- success: true,
5302
- message: `Approval ${key} edited`,
5303
- data: { key, action, timestamp: Date.now() }
5304
- };
5305
- case "cancel":
5306
- await store.cancelApproval(key);
5307
- return {
5308
- success: true,
5309
- message: `Approval ${key} cancelled`,
5310
- data: { key, action, timestamp: Date.now() }
5311
- };
5312
- default:
5313
- return { success: false, message: `Unknown action: ${action}` };
5314
- }
5315
- } catch (error) {
5316
- return {
5317
- success: false,
5318
- message: error instanceof Error ? error.message : String(error)
5319
- };
5320
- }
5321
- };
5322
- }
5323
- function createApprovalChecker(store) {
5324
- return (key) => async () => {
5325
- const status = await store.getApproval(key);
5326
- switch (status.status) {
5327
- case "pending":
5328
- return { status: "pending" };
5329
- case "approved":
5330
- return { status: "approved", value: status.value };
5331
- case "edited":
5332
- return { status: "approved", value: status.editedValue };
5333
- case "rejected":
5334
- return { status: "rejected", reason: status.reason };
5335
- case "expired":
5336
- return { status: "rejected", reason: "Approval request expired" };
5337
- default:
5338
- return { status: "pending" };
5339
- }
5340
- };
5341
- }
5342
-
5343
- // src/saga.ts
5344
- function isSagaCompensationError(error) {
5345
- return typeof error === "object" && error !== null && error.type === "SAGA_COMPENSATION_ERROR";
5346
- }
5347
- function createSagaWorkflow(workflowName, deps, options) {
5348
- return createWorkflow(workflowName, deps, options);
5349
- }
5350
- async function runSaga(fn, options) {
5351
- const sagaId = crypto.randomUUID();
5352
- const startTime = performance.now();
5353
- const compensations = [];
5354
- const emit = (e) => options?.onEvent?.(e);
5355
- emit({ type: "saga_start", sagaId, ts: Date.now() });
5356
- const stepFn = async (name, operation, stepOptions) => {
5357
- if (typeof name !== "string" || name.length === 0) {
5358
- throw new TypeError(
5359
- "step(name, operation, options?): first argument must be a string."
5360
- );
5361
- }
5362
- const result = await operation();
5363
- if (result.ok) {
5364
- if (stepOptions?.compensate) {
5365
- compensations.push({
5366
- name,
5367
- value: result.value,
5368
- compensate: stepOptions.compensate
5369
- });
5370
- }
5371
- return result.value;
5372
- }
5373
- throw createEarlyExit(result.error, {
5374
- origin: "result",
5375
- resultCause: result.cause
5376
- });
5377
- };
5378
- const stepTry = async (name, operation, opts) => {
5379
- if (typeof name !== "string" || name.length === 0) {
5380
- throw new TypeError(
5381
- "step.try(name, operation, options): first argument must be a string."
5382
- );
5383
- }
5384
- const mapToError = "error" in opts ? () => opts.error : opts.onError;
5385
- try {
5386
- const value = await operation();
5387
- if (opts.compensate) {
5388
- compensations.push({
5389
- name,
5390
- value,
5391
- compensate: opts.compensate
5392
- });
5393
- }
5394
- return value;
5395
- } catch (thrown) {
5396
- const mapped = mapToError(thrown);
5397
- throw createEarlyExit(mapped, { origin: "throw", thrown });
5398
- }
5399
- };
5400
- const step = Object.assign(stepFn, { try: stepTry });
5401
- try {
5402
- const value = await fn({ step });
5403
- emit({
5404
- type: "saga_success",
5405
- sagaId,
5406
- ts: Date.now(),
5407
- durationMs: performance.now() - startTime
5408
- });
5409
- return ok2(value);
5410
- } catch (thrown) {
5411
- const durationMs = performance.now() - startTime;
5412
- const originalError = isEarlyExit(thrown) ? thrown.error : thrown;
5413
- emit({ type: "saga_error", sagaId, ts: Date.now(), durationMs, error: originalError });
5414
- emit({
5415
- type: "saga_compensation_start",
5416
- sagaId,
5417
- ts: Date.now(),
5418
- stepCount: compensations.length
5419
- });
5420
- const compensationStart = performance.now();
5421
- const compensationErrors = [];
5422
- for (let i = compensations.length - 1; i >= 0; i--) {
5423
- const comp = compensations[i];
5424
- try {
5425
- await comp.compensate(comp.value);
5426
- emit({
5427
- type: "saga_compensation_step",
5428
- sagaId,
5429
- stepName: comp.name,
5430
- ts: Date.now(),
5431
- success: true
5432
- });
5433
- } catch (error) {
5434
- compensationErrors.push({ stepName: comp.name, error });
5435
- emit({
5436
- type: "saga_compensation_step",
5437
- sagaId,
5438
- stepName: comp.name,
5439
- ts: Date.now(),
5440
- success: false,
5441
- error
5442
- });
5443
- }
5444
- }
5445
- emit({
5446
- type: "saga_compensation_end",
5447
- sagaId,
5448
- ts: Date.now(),
5449
- durationMs: performance.now() - compensationStart,
5450
- success: compensationErrors.length === 0,
5451
- failedCount: compensationErrors.length
5452
- });
5453
- if (compensationErrors.length > 0) {
5454
- const sagaError = {
5455
- type: "SAGA_COMPENSATION_ERROR",
5456
- originalError,
5457
- compensationErrors
5458
- };
5459
- options?.onError?.(sagaError);
5460
- if (options?.throwOnCompensationFailure) throw sagaError;
5461
- return err(sagaError);
5462
- }
5463
- options?.onError?.(originalError);
5464
- if (!isEarlyExit(thrown)) {
5465
- return err(new UnexpectedError({ cause: thrown }));
5466
- }
5467
- return err(originalError);
5468
- }
5469
- }
5470
-
5471
- // src/streaming/stores/memory.ts
5472
- function createMemoryStreamStore(options = {}) {
5473
- const maxItemsPerStream = options.maxItemsPerStream ?? Infinity;
5474
- const streams = /* @__PURE__ */ new Map();
5475
- function getStreamKey(workflowId, namespace) {
5476
- return `${workflowId}:${namespace}`;
5477
- }
5478
- function getOrCreateStream(workflowId, namespace) {
5479
- const key = getStreamKey(workflowId, namespace);
5480
- let data = streams.get(key);
5481
- if (!data) {
5482
- data = {
5483
- metadata: {
5484
- id: key,
5485
- namespace,
5486
- workflowId,
5487
- length: 0,
5488
- closed: false,
5489
- createdAt: Date.now()
5490
- },
5491
- items: [],
5492
- subscribers: /* @__PURE__ */ new Set()
5493
- };
5494
- streams.set(key, data);
5495
- }
5496
- return data;
5497
- }
5498
- return {
5499
- async append(workflowId, namespace, item) {
5500
- try {
5501
- const data = getOrCreateStream(workflowId, namespace);
5502
- if (data.metadata.closed) {
5503
- return err(
5504
- streamStoreError("write_error", "Cannot write to closed stream")
5505
- );
5506
- }
5507
- if (data.items.length >= maxItemsPerStream) {
5508
- return err(
5509
- streamStoreError(
5510
- "write_error",
5511
- `Stream exceeded max items limit (${maxItemsPerStream})`
5512
- )
5513
- );
5514
- }
5515
- data.items.push(item);
5516
- data.metadata.length = data.items.length;
5517
- data.metadata.lastWriteAt = Date.now();
5518
- for (const callback of data.subscribers) {
5519
- try {
5520
- callback(item);
5521
- } catch {
5522
- }
5523
- }
5524
- return ok2(void 0);
5525
- } catch (cause) {
5526
- return err(
5527
- streamStoreError("write_error", "Failed to append item", cause)
5528
- );
5529
- }
5530
- },
5531
- async read(workflowId, namespace, startIndex, limit) {
5532
- try {
5533
- const key = getStreamKey(workflowId, namespace);
5534
- const data = streams.get(key);
5535
- if (!data) {
5536
- return ok2([]);
5537
- }
5538
- const endIndex = limit !== void 0 ? Math.min(startIndex + limit, data.items.length) : data.items.length;
5539
- const items = data.items.slice(startIndex, endIndex);
5540
- return ok2(items);
5541
- } catch (cause) {
5542
- return err(streamStoreError("read_error", "Failed to read items", cause));
5543
- }
5544
- },
5545
- async getMetadata(workflowId, namespace) {
5546
- try {
5547
- const key = getStreamKey(workflowId, namespace);
5548
- const data = streams.get(key);
5549
- return ok2(data?.metadata);
5550
- } catch (cause) {
5551
- return err(
5552
- streamStoreError("metadata_error", "Failed to get metadata", cause)
5553
- );
5554
- }
5555
- },
5556
- async closeStream(workflowId, namespace) {
5557
- try {
5558
- const key = getStreamKey(workflowId, namespace);
5559
- const data = streams.get(key);
5560
- if (data) {
5561
- data.metadata.closed = true;
5562
- data.metadata.closedAt = Date.now();
5563
- }
5564
- return ok2(void 0);
5565
- } catch (cause) {
5566
- return err(
5567
- streamStoreError("close_error", "Failed to close stream", cause)
5568
- );
5569
- }
5570
- },
5571
- subscribe(workflowId, namespace, callback) {
5572
- const data = getOrCreateStream(workflowId, namespace);
5573
- const typedCallback = callback;
5574
- data.subscribers.add(typedCallback);
5575
- return () => {
5576
- data.subscribers.delete(typedCallback);
5577
- };
5578
- }
5579
- };
5580
- }
5581
- function createTestableMemoryStreamStore(options = {}) {
5582
- const streams = /* @__PURE__ */ new Map();
5583
- const maxItemsPerStream = options.maxItemsPerStream ?? Infinity;
5584
- function getStreamKey(workflowId, namespace) {
5585
- return `${workflowId}:${namespace}`;
5586
- }
5587
- function getOrCreateStream(workflowId, namespace) {
5588
- const key = getStreamKey(workflowId, namespace);
5589
- let data = streams.get(key);
5590
- if (!data) {
5591
- data = {
5592
- metadata: {
5593
- id: key,
5594
- namespace,
5595
- workflowId,
5596
- length: 0,
5597
- closed: false,
5598
- createdAt: Date.now()
5599
- },
5600
- items: [],
5601
- subscribers: /* @__PURE__ */ new Set()
5602
- };
5603
- streams.set(key, data);
5604
- }
5605
- return data;
5606
- }
5607
- const store = createMemoryStreamStore(options);
5608
- return {
5609
- ...store,
5610
- // Override to use our local streams map
5611
- async append(workflowId, namespace, item) {
5612
- try {
5613
- const data = getOrCreateStream(workflowId, namespace);
5614
- if (data.metadata.closed) {
5615
- return err(
5616
- streamStoreError("write_error", "Cannot write to closed stream")
5617
- );
5618
- }
5619
- if (data.items.length >= maxItemsPerStream) {
5620
- return err(
5621
- streamStoreError(
5622
- "write_error",
5623
- `Stream exceeded max items limit (${maxItemsPerStream})`
5624
- )
5625
- );
5626
- }
5627
- data.items.push(item);
5628
- data.metadata.length = data.items.length;
5629
- data.metadata.lastWriteAt = Date.now();
5630
- for (const callback of data.subscribers) {
5631
- try {
5632
- callback(item);
5633
- } catch {
5634
- }
5635
- }
5636
- return ok2(void 0);
5637
- } catch (cause) {
5638
- return err(
5639
- streamStoreError("write_error", "Failed to append item", cause)
5640
- );
5641
- }
5642
- },
5643
- async read(workflowId, namespace, startIndex, limit) {
5644
- try {
5645
- const key = getStreamKey(workflowId, namespace);
5646
- const data = streams.get(key);
5647
- if (!data) {
5648
- return ok2([]);
5649
- }
5650
- const endIndex = limit !== void 0 ? Math.min(startIndex + limit, data.items.length) : data.items.length;
5651
- const items = data.items.slice(startIndex, endIndex);
5652
- return ok2(items);
5653
- } catch (cause) {
5654
- return err(streamStoreError("read_error", "Failed to read items", cause));
5655
- }
5656
- },
5657
- async getMetadata(workflowId, namespace) {
5658
- try {
5659
- const key = getStreamKey(workflowId, namespace);
5660
- const data = streams.get(key);
5661
- return ok2(data?.metadata);
5662
- } catch (cause) {
5663
- return err(
5664
- streamStoreError("metadata_error", "Failed to get metadata", cause)
5665
- );
5666
- }
5667
- },
5668
- async closeStream(workflowId, namespace) {
5669
- try {
5670
- const key = getStreamKey(workflowId, namespace);
5671
- const data = streams.get(key);
5672
- if (data) {
5673
- data.metadata.closed = true;
5674
- data.metadata.closedAt = Date.now();
5675
- }
5676
- return ok2(void 0);
5677
- } catch (cause) {
5678
- return err(
5679
- streamStoreError("close_error", "Failed to close stream", cause)
5680
- );
5681
- }
5682
- },
5683
- subscribe(workflowId, namespace, callback) {
5684
- const data = getOrCreateStream(workflowId, namespace);
5685
- const typedCallback = callback;
5686
- data.subscribers.add(typedCallback);
5687
- return () => {
5688
- data.subscribers.delete(typedCallback);
5689
- };
5690
- },
5691
- clear() {
5692
- streams.clear();
5693
- },
5694
- keys() {
5695
- return Array.from(streams.keys());
5696
- },
5697
- has(workflowId, namespace) {
5698
- return streams.has(getStreamKey(workflowId, namespace));
5699
- },
5700
- delete(workflowId, namespace) {
5701
- return streams.delete(getStreamKey(workflowId, namespace));
5702
- }
5703
- };
5704
- }
5705
-
5706
- // src/streaming/stores/file.ts
5707
- function createFileStreamStore(options) {
5708
- const { directory, fs } = options;
5709
- const subscribers = /* @__PURE__ */ new Map();
5710
- function getStreamDir(workflowId, namespace) {
5711
- return `${directory}/${workflowId}/${namespace}`;
5712
- }
5713
- function getMetadataPath(workflowId, namespace) {
5714
- return `${getStreamDir(workflowId, namespace)}/metadata.json`;
5715
- }
5716
- function getItemsPath(workflowId, namespace) {
5717
- return `${getStreamDir(workflowId, namespace)}/items.jsonl`;
5718
- }
5719
- function getSubscriberKey(workflowId, namespace) {
5720
- return `${workflowId}:${namespace}`;
5721
- }
5722
- async function ensureDir(workflowId, namespace) {
5723
- const dir = getStreamDir(workflowId, namespace);
5724
- await fs.mkdir(dir, { recursive: true });
5725
- }
5726
- async function readMetadata(workflowId, namespace) {
5727
- const metaPath = getMetadataPath(workflowId, namespace);
5728
- if (!await fs.exists(metaPath)) {
5729
- return void 0;
5730
- }
5731
- const content = await fs.readFile(metaPath);
5732
- return JSON.parse(content);
5733
- }
5734
- async function writeMetadata(workflowId, namespace, metadata) {
5735
- await ensureDir(workflowId, namespace);
5736
- const metaPath = getMetadataPath(workflowId, namespace);
5737
- await fs.writeFile(metaPath, JSON.stringify(metadata, null, 2));
5738
- }
5739
- async function appendItem(workflowId, namespace, item) {
5740
- await ensureDir(workflowId, namespace);
5741
- const itemsPath = getItemsPath(workflowId, namespace);
5742
- const line = JSON.stringify(item) + "\n";
5743
- let content = "";
5744
- if (await fs.exists(itemsPath)) {
5745
- content = await fs.readFile(itemsPath);
5746
- }
5747
- await fs.writeFile(itemsPath, content + line);
5748
- }
5749
- async function readItems(workflowId, namespace, startIndex, limit) {
5750
- const itemsPath = getItemsPath(workflowId, namespace);
5751
- if (!await fs.exists(itemsPath)) {
5752
- return [];
5753
- }
5754
- const content = await fs.readFile(itemsPath);
5755
- const lines = content.trim().split("\n").filter(Boolean);
5756
- const items = [];
5757
- for (const line of lines) {
5758
- const item = JSON.parse(line);
5759
- if (item.position >= startIndex) {
5760
- items.push(item);
5761
- if (limit !== void 0 && items.length >= limit) {
5762
- break;
5763
- }
5764
- }
5765
- }
5766
- return items;
5767
- }
5768
- return {
5769
- async append(workflowId, namespace, item) {
5770
- try {
5771
- let metadata = await readMetadata(workflowId, namespace);
5772
- if (!metadata) {
5773
- metadata = {
5774
- id: `${workflowId}:${namespace}`,
5775
- namespace,
5776
- workflowId,
5777
- length: 0,
5778
- closed: false,
5779
- createdAt: Date.now()
5780
- };
5781
- }
5782
- if (metadata.closed) {
5783
- return err(
5784
- streamStoreError("write_error", "Cannot write to closed stream")
5785
- );
5786
- }
5787
- const serializedItem = {
5788
- value: item.value,
5789
- position: item.position,
5790
- ts: item.ts
5791
- };
5792
- await appendItem(workflowId, namespace, serializedItem);
5793
- metadata.length++;
5794
- metadata.lastWriteAt = Date.now();
5795
- await writeMetadata(workflowId, namespace, metadata);
5796
- const key = getSubscriberKey(workflowId, namespace);
5797
- const subs = subscribers.get(key);
5798
- if (subs) {
5799
- for (const callback of subs) {
5800
- try {
5801
- callback(item);
5802
- } catch {
5803
- }
5804
- }
5805
- }
5806
- return ok2(void 0);
5807
- } catch (cause) {
5808
- return err(
5809
- streamStoreError("write_error", "Failed to append item", cause)
5810
- );
5811
- }
5812
- },
5813
- async read(workflowId, namespace, startIndex, limit) {
5814
- try {
5815
- const serializedItems = await readItems(
5816
- workflowId,
5817
- namespace,
5818
- startIndex,
5819
- limit
5820
- );
5821
- const items = serializedItems.map((si) => ({
5822
- value: si.value,
5823
- position: si.position,
5824
- ts: si.ts
5825
- }));
5826
- return ok2(items);
5827
- } catch (cause) {
5828
- return err(streamStoreError("read_error", "Failed to read items", cause));
5829
- }
5830
- },
5831
- async getMetadata(workflowId, namespace) {
5832
- try {
5833
- const meta = await readMetadata(workflowId, namespace);
5834
- if (!meta) {
5835
- return ok2(void 0);
5836
- }
5837
- return ok2({
5838
- id: meta.id,
5839
- namespace: meta.namespace,
5840
- workflowId: meta.workflowId,
5841
- length: meta.length,
5842
- closed: meta.closed,
5843
- createdAt: meta.createdAt,
5844
- lastWriteAt: meta.lastWriteAt,
5845
- closedAt: meta.closedAt
5846
- });
5847
- } catch (cause) {
5848
- return err(
5849
- streamStoreError("metadata_error", "Failed to get metadata", cause)
5850
- );
5851
- }
5852
- },
5853
- async closeStream(workflowId, namespace) {
5854
- try {
5855
- const metadata = await readMetadata(workflowId, namespace);
5856
- if (metadata) {
5857
- metadata.closed = true;
5858
- metadata.closedAt = Date.now();
5859
- await writeMetadata(workflowId, namespace, metadata);
5860
- }
5861
- return ok2(void 0);
5862
- } catch (cause) {
5863
- return err(
5864
- streamStoreError("close_error", "Failed to close stream", cause)
5865
- );
5866
- }
5867
- },
5868
- subscribe(workflowId, namespace, callback) {
5869
- const key = getSubscriberKey(workflowId, namespace);
5870
- let subs = subscribers.get(key);
5871
- if (!subs) {
5872
- subs = /* @__PURE__ */ new Set();
5873
- subscribers.set(key, subs);
5874
- }
5875
- const typedCallback = callback;
5876
- subs.add(typedCallback);
5877
- return () => {
5878
- subs?.delete(typedCallback);
5879
- if (subs?.size === 0) {
5880
- subscribers.delete(key);
5881
- }
5882
- };
5883
- }
5884
- };
5885
- }
5886
-
5887
- // src/streaming/transformers.ts
5888
- async function* toAsyncIterable(reader) {
5889
- while (reader.readable) {
5890
- const result = await reader.read();
5891
- if (!result.ok) {
5892
- if (isStreamEnded(result.error)) {
5893
- break;
5894
- }
5895
- throw result.error;
5896
- }
5897
- yield result.value;
5898
- }
5899
- }
5900
- async function* map(source, fn) {
5901
- const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
5902
- let index = 0;
5903
- for await (const item of iterable) {
5904
- yield await fn(item, index++);
5905
- }
5906
- }
5907
- async function* filter(source, predicate) {
5908
- const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
5909
- let index = 0;
5910
- for await (const item of iterable) {
5911
- if (await predicate(item, index++)) {
5912
- yield item;
5913
- }
5914
- }
5915
- }
5916
- async function* chunk(source, size) {
5917
- if (size < 1) {
5918
- throw new Error("Chunk size must be at least 1");
5919
- }
5920
- const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
5921
- let currentChunk = [];
5922
- for await (const item of iterable) {
5923
- currentChunk.push(item);
5924
- if (currentChunk.length >= size) {
5925
- yield currentChunk;
5926
- currentChunk = [];
5927
- }
5928
- }
5929
- if (currentChunk.length > 0) {
5930
- yield currentChunk;
5931
- }
5932
- }
5933
- async function* flatMap(source, fn) {
5934
- const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
5935
- let index = 0;
5936
- for await (const item of iterable) {
5937
- const mapped = fn(item, index++);
5938
- if (Symbol.asyncIterator in mapped) {
5939
- for await (const subItem of mapped) {
5940
- yield subItem;
5941
- }
5942
- } else {
5943
- for (const subItem of mapped) {
5944
- yield subItem;
5945
- }
5946
- }
5947
- }
5948
- }
5949
- async function* flatMapAsync(source, fn) {
5950
- const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
5951
- let index = 0;
5952
- for await (const item of iterable) {
5953
- const result = await fn(item, index++);
5954
- if (!result.ok) {
5955
- yield err(result.error);
5956
- return;
5957
- }
5958
- const mapped = result.value;
5959
- if (Symbol.asyncIterator in mapped) {
5960
- for await (const subItem of mapped) {
5961
- yield ok2(subItem);
5962
- }
5963
- } else {
5964
- for (const subItem of mapped) {
5965
- yield ok2(subItem);
5966
- }
5967
- }
5968
- }
5969
- }
5970
- async function* mapAsync(source, fn) {
5971
- const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
5972
- let index = 0;
5973
- for await (const item of iterable) {
5974
- yield await fn(item, index++);
5975
- }
5976
- }
5977
- async function* take(source, count) {
5978
- if (count <= 0) return;
5979
- const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
5980
- let taken = 0;
5981
- for await (const item of iterable) {
5982
- yield item;
5983
- taken++;
5984
- if (taken >= count) break;
5985
- }
5986
- }
5987
- async function* skip(source, count) {
5988
- const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
5989
- let skipped = 0;
5990
- for await (const item of iterable) {
5991
- if (skipped < count) {
5992
- skipped++;
5993
- continue;
5994
- }
5995
- yield item;
5996
- }
5997
- }
5998
- async function* takeWhile(source, predicate) {
5999
- const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
6000
- let index = 0;
6001
- for await (const item of iterable) {
6002
- if (!await predicate(item, index++)) break;
6003
- yield item;
6004
- }
6005
- }
6006
- async function* skipWhile(source, predicate) {
6007
- const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
6008
- let skipping = true;
6009
- let index = 0;
6010
- for await (const item of iterable) {
6011
- if (skipping && await predicate(item, index++)) {
6012
- continue;
6013
- }
6014
- skipping = false;
6015
- yield item;
6016
- }
6017
- }
6018
- async function collect(source) {
6019
- const items = [];
6020
- const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
6021
- for await (const item of iterable) {
6022
- items.push(item);
6023
- }
6024
- return items;
6025
- }
6026
- async function reduce(source, reducer, initial) {
6027
- let accumulator = initial;
6028
- const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
6029
- let index = 0;
6030
- for await (const item of iterable) {
6031
- accumulator = await reducer(accumulator, item, index++);
6032
- }
6033
- return accumulator;
6034
- }
6035
- function isStreamReader(value) {
6036
- return typeof value === "object" && value !== null && "read" in value && typeof value.read === "function" && "readable" in value;
6037
- }
6038
- function pipe(source, ...transformers) {
6039
- let result = isStreamReader(source) ? toAsyncIterable(source) : source;
6040
- for (const transformer of transformers) {
6041
- result = transformer(result);
6042
- }
6043
- return result;
6044
- }
6045
-
6046
- // src/streaming/index.ts
6047
- function getStreamReader(options) {
6048
- const {
6049
- store,
6050
- workflowId,
6051
- namespace = "default",
6052
- startIndex = 0,
6053
- pollInterval = 100,
6054
- pollTimeout = 3e4
6055
- } = options;
6056
- let position = startIndex;
6057
- let readable = true;
6058
- let closed = false;
6059
- let bufferedItems = [];
6060
- let bufferIndex = 0;
6061
- const reader = {
6062
- async read() {
6063
- if (closed) {
6064
- return err(streamReadError("closed", "Reader is closed"));
6065
- }
6066
- if (bufferIndex < bufferedItems.length) {
6067
- const item = bufferedItems[bufferIndex++];
6068
- position = item.position + 1;
6069
- return ok2(item.value);
6070
- }
6071
- const pollStart = Date.now();
6072
- while (Date.now() - pollStart < pollTimeout) {
6073
- const result = await store.read(workflowId, namespace, position, 100);
6074
- if (!result.ok) {
6075
- return err(streamReadError("store_error", result.error.message, result.error));
6076
- }
6077
- const items = result.value;
6078
- if (items.length > 0) {
6079
- bufferedItems = items;
6080
- bufferIndex = 1;
6081
- const item = items[0];
6082
- position = item.position + 1;
6083
- return ok2(item.value);
6084
- }
6085
- const metaResult = await store.getMetadata(workflowId, namespace);
6086
- if (metaResult.ok && metaResult.value?.closed) {
6087
- readable = false;
6088
- return err(streamEnded(position));
6089
- }
6090
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
6091
- }
6092
- readable = false;
6093
- return err(streamEnded(position));
6094
- },
6095
- close() {
6096
- closed = true;
6097
- readable = false;
6098
- bufferedItems = [];
6099
- },
6100
- get readable() {
6101
- return readable;
6102
- },
6103
- get position() {
6104
- return position;
6105
- },
6106
- get namespace() {
6107
- return namespace;
6108
- }
6109
- };
6110
- return reader;
6111
- }
6112
-
6113
- // src/webhook/index.ts
6114
- function isValidationError(e) {
6115
- return typeof e === "object" && e !== null && e.type === "VALIDATION_ERROR";
6116
- }
6117
- function defaultValidationErrorMapper(error) {
6118
- return {
6119
- status: 400,
6120
- body: {
6121
- error: {
6122
- type: error.type,
6123
- message: error.message,
6124
- details: error.field ? { field: error.field } : error.details
6125
- }
6126
- }
6127
- };
6128
- }
6129
- function defaultUnexpectedErrorMapper(error) {
6130
- return {
6131
- status: 500,
6132
- body: {
6133
- error: {
6134
- type: "INTERNAL_ERROR",
6135
- message: error instanceof Error ? error.message : "An unexpected error occurred"
6136
- }
6137
- }
6138
- };
6139
- }
6140
- function createWebhookHandler(workflow, workflowFn, config) {
6141
- const {
6142
- validateInput: validateInput2,
6143
- mapResult,
6144
- mapValidationError = defaultValidationErrorMapper,
6145
- mapUnexpectedError = defaultUnexpectedErrorMapper,
6146
- beforeValidation,
6147
- afterResponse
6148
- } = config;
6149
- return async (req) => {
6150
- try {
6151
- const processedReq = beforeValidation ? await beforeValidation(req) : req;
6152
- const validationResult = await validateInput2(processedReq);
6153
- if (!isOk(validationResult)) {
6154
- const response2 = mapValidationError(validationResult.error, processedReq);
6155
- return afterResponse ? await afterResponse(response2, processedReq) : response2;
6156
- }
6157
- const args = validationResult.value;
6158
- const workflowResult = await workflow.run(
6159
- async ({ step, deps }) => workflowFn({ step, deps, args })
6160
- );
6161
- const response = mapResult(workflowResult, processedReq);
6162
- return afterResponse ? await afterResponse(response, processedReq) : response;
6163
- } catch (error) {
6164
- const response = mapUnexpectedError(error, req);
6165
- return afterResponse ? await afterResponse(response, req) : response;
6166
- }
6167
- };
6168
- }
6169
- function createSimpleHandler(config) {
6170
- const {
6171
- validateInput: validateInput2,
6172
- handler,
6173
- mapResult,
6174
- mapValidationError = defaultValidationErrorMapper,
6175
- mapUnexpectedError = defaultUnexpectedErrorMapper
6176
- } = config;
6177
- return async (req) => {
6178
- try {
6179
- const validationResult = await validateInput2(req);
6180
- if (!isOk(validationResult)) {
6181
- return mapValidationError(validationResult.error, req);
6182
- }
6183
- const result = await handler(validationResult.value, req);
6184
- return mapResult(result, req);
6185
- } catch (error) {
6186
- return mapUnexpectedError(error, req);
6187
- }
6188
- };
6189
- }
6190
- function createResultMapper(mappings, options = {}) {
6191
- const { defaultStatus = 500, successStatus = 200 } = options;
6192
- const errorMap = /* @__PURE__ */ new Map();
6193
- for (const mapping of mappings) {
6194
- errorMap.set(mapping.error, mapping);
6195
- }
6196
- return (result) => {
6197
- if (result.ok) {
6198
- return {
6199
- status: successStatus,
6200
- body: result.value
6201
- };
6202
- }
6203
- if (isUnexpectedError(result.error)) {
6204
- return {
6205
- status: 500,
6206
- body: {
6207
- error: {
6208
- type: "INTERNAL_ERROR",
6209
- message: result.error instanceof Error ? result.error.message : "An unexpected error occurred"
6210
- }
6211
- }
6212
- };
6213
- }
6214
- const mapping = errorMap.get(result.error);
6215
- if (mapping) {
6216
- return {
6217
- status: mapping.status,
6218
- body: {
6219
- error: {
6220
- type: String(mapping.error),
6221
- message: mapping.message
6222
- }
6223
- }
6224
- };
6225
- }
6226
- return {
6227
- status: defaultStatus,
6228
- body: {
6229
- error: {
6230
- type: String(result.error)
6231
- }
6232
- }
6233
- };
6234
- };
6235
- }
6236
- function toWebhookRequest(req) {
6237
- return {
6238
- method: req.method,
6239
- path: req.path,
6240
- headers: req.headers,
6241
- body: req.body,
6242
- query: req.query,
6243
- params: req.params,
6244
- raw: req
6245
- };
6246
- }
6247
- function sendWebhookResponse(res, response) {
6248
- if (response.headers) {
6249
- res.set(response.headers);
6250
- }
6251
- res.status(response.status).json(response.body);
6252
- }
6253
- function createExpressHandler(handler) {
6254
- return async (req, res) => {
6255
- const webhookReq = toWebhookRequest(req);
6256
- const response = await handler(webhookReq);
6257
- sendWebhookResponse(res, response);
6258
- };
6259
- }
6260
- function validationError(message, field, details) {
6261
- return {
6262
- type: "VALIDATION_ERROR",
6263
- message,
6264
- field,
6265
- details
6266
- };
6267
- }
6268
- function requireFields(fields) {
6269
- return (body) => {
6270
- for (const field of fields) {
6271
- if (body[field] === void 0 || body[field] === null || body[field] === "") {
6272
- return err(validationError(`Missing required field: ${field}`, field));
6273
- }
6274
- }
6275
- return ok2(void 0);
6276
- };
6277
- }
6278
- function composeValidators(...validators) {
6279
- return (input) => {
6280
- for (const validator of validators) {
6281
- const result = validator(input);
6282
- if (!result.ok) return result;
6283
- }
6284
- return ok2(void 0);
6285
- };
6286
- }
6287
- function createEventHandler(workflow, workflowFn, config) {
6288
- const { validatePayload, mapResult } = config;
6289
- return async (event) => {
6290
- try {
6291
- const validationResult = validatePayload(event);
6292
- if (!isOk(validationResult)) {
6293
- return {
6294
- success: false,
6295
- ack: true,
6296
- // Don't retry validation errors
6297
- error: {
6298
- type: validationResult.error.type,
6299
- message: validationResult.error.message,
6300
- retryable: false
6301
- }
6302
- };
6303
- }
6304
- const args = validationResult.value;
6305
- const workflowResult = await workflow.run(
6306
- async ({ step, deps }) => workflowFn({ step, deps, args })
6307
- );
6308
- return mapResult(workflowResult, event);
6309
- } catch (error) {
6310
- return {
6311
- success: false,
6312
- ack: false,
6313
- // Retry unexpected errors
6314
- error: {
6315
- type: "UnexpectedError",
6316
- message: error instanceof Error ? error.message : String(error),
6317
- retryable: true
6318
- }
6319
- };
6320
- }
6321
- };
6322
- }
6323
-
6324
- // src/engine/index.ts
6325
- import { randomUUID } from "crypto";
6326
- function createEngine(options) {
6327
- const {
6328
- store,
6329
- workflows,
6330
- concurrency = 5,
6331
- onEvent,
6332
- onError
6333
- } = options;
6334
- const schedules = /* @__PURE__ */ new Map();
6335
- let pollTimer;
6336
- let running = false;
6337
- let tickInFlight = false;
6338
- function emit(event) {
6339
- try {
6340
- onEvent?.(event);
6341
- } catch {
6342
- }
6343
- }
6344
- async function enqueue(name, opts) {
6345
- const wf = workflows[name];
6346
- if (!wf) {
6347
- throw new Error(`Unknown workflow: '${name}'. Registered: ${Object.keys(workflows).join(", ")}`);
6348
- }
6349
- const id = opts?.id ?? `${name}:${randomUUID()}`;
6350
- const snapshot = {
6351
- formatVersion: 1,
6352
- workflowName: name,
6353
- steps: {},
6354
- execution: {
6355
- status: "running",
6356
- // "running" so durable.run can resume it
6357
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
6358
- },
6359
- metadata: {
6360
- engineState: "queued",
6361
- workflowName: name,
6362
- input: opts?.input ?? null,
6363
- idempotencyKey: opts?.idempotencyKey,
6364
- enqueuedAt: (/* @__PURE__ */ new Date()).toISOString(),
6365
- ...opts?.metadata
6366
- }
6367
- };
6368
- await store.save(id, snapshot);
6369
- emit({ type: "workflow_enqueued", workflowName: name, id, ts: Date.now() });
6370
- return id;
6371
- }
6372
- async function tick() {
6373
- if (tickInFlight) return 0;
6374
- tickInFlight = true;
6375
- try {
6376
- const entries = await store.list({ limit: concurrency * 2 });
6377
- let processed = 0;
6378
- const queued = [];
6379
- for (const entry of entries) {
6380
- if (queued.length >= concurrency) break;
6381
- const snapshot = await store.load(entry.id);
6382
- if (snapshot && snapshot.metadata?.engineState === "queued" && typeof snapshot.metadata?.workflowName === "string") {
6383
- queued.push({ id: entry.id, workflowName: snapshot.metadata.workflowName, snapshot });
6384
- }
6385
- }
6386
- const results = await Promise.allSettled(
6387
- queued.map(async ({ id, workflowName, snapshot }) => {
6388
- const wf = workflows[workflowName];
6389
- if (!wf) return;
6390
- const processingSnapshot = {
6391
- ...snapshot,
6392
- metadata: {
6393
- ...snapshot.metadata,
6394
- engineState: "processing"
6395
- }
6396
- };
6397
- await store.save(id, processingSnapshot);
6398
- emit({ type: "workflow_started", workflowName, id, ts: Date.now() });
6399
- const result = await durable.run(wf.deps, wf.fn, {
6400
- id,
6401
- store,
6402
- idempotencyKey: snapshot.metadata?.idempotencyKey,
6403
- input: snapshot.metadata?.input,
6404
- ...wf.durableDefaults
6405
- });
6406
- if (result.ok) {
6407
- emit({ type: "workflow_completed", workflowName, id, ts: Date.now() });
6408
- } else {
6409
- emit({ type: "workflow_failed", workflowName, id, error: result.error, ts: Date.now() });
6410
- }
6411
- processed++;
6412
- })
6413
- );
6414
- for (const r of results) {
6415
- if (r.status === "rejected") {
6416
- try {
6417
- onError?.(r.reason);
6418
- } catch {
6419
- }
6420
- }
6421
- }
6422
- emit({ type: "engine_tick", ts: Date.now(), processed });
6423
- return processed;
6424
- } finally {
6425
- tickInFlight = false;
6426
- }
6427
- }
6428
- function schedule(name, opts) {
6429
- const scheduleId = opts?.id ? `schedule:${opts.id}` : `schedule:${randomUUID()}`;
6430
- if (opts.immediate) {
6431
- void enqueue(name, opts).catch((e) => {
6432
- try {
6433
- onError?.(e);
6434
- } catch {
6435
- }
6436
- });
6437
- }
6438
- const interval = setInterval(() => {
6439
- void enqueue(name, opts).catch((e) => {
6440
- try {
6441
- onError?.(e);
6442
- } catch {
6443
- }
6444
- });
6445
- }, opts.intervalMs);
6446
- schedules.set(scheduleId, interval);
6447
- emit({ type: "schedule_created", workflowName: name, scheduleId, intervalMs: opts.intervalMs, ts: Date.now() });
6448
- return scheduleId;
6449
- }
6450
- function unschedule(scheduleId) {
6451
- const interval = schedules.get(scheduleId);
6452
- if (!interval) return false;
6453
- clearInterval(interval);
6454
- schedules.delete(scheduleId);
6455
- emit({ type: "schedule_removed", scheduleId, ts: Date.now() });
6456
- return true;
6457
- }
6458
- function start(pollIntervalMs = 1e3) {
6459
- if (running) return;
6460
- running = true;
6461
- emit({ type: "engine_start", ts: Date.now() });
6462
- void tick().catch((e) => {
6463
- try {
6464
- onError?.(e);
6465
- } catch {
6466
- }
6467
- });
6468
- pollTimer = setInterval(() => {
6469
- void tick().catch((e) => {
6470
- try {
6471
- onError?.(e);
6472
- } catch {
6473
- }
6474
- });
6475
- }, pollIntervalMs);
6476
- }
6477
- async function stop() {
6478
- if (!running) return;
6479
- running = false;
6480
- if (pollTimer) {
6481
- clearInterval(pollTimer);
6482
- pollTimer = void 0;
6483
- }
6484
- for (const [id, interval] of schedules) {
6485
- clearInterval(interval);
6486
- schedules.delete(id);
6487
- }
6488
- while (tickInFlight) {
6489
- await new Promise((resolve) => setTimeout(resolve, 50));
6490
- }
6491
- emit({ type: "engine_stop", ts: Date.now() });
6492
- }
6493
- return {
6494
- enqueue,
6495
- schedule,
6496
- unschedule,
6497
- start,
6498
- stop,
6499
- tick,
6500
- status: () => ({ running, pendingSchedules: schedules.size })
6501
- };
6502
- }
6503
-
6504
4050
  // src/resource.ts
6505
4051
  function isResourceCleanupError(error) {
6506
4052
  return typeof error === "object" && error !== null && error.type === "RESOURCE_CLEANUP_ERROR";
@@ -6792,66 +4338,26 @@ export {
6792
4338
  Duration,
6793
4339
  HOOK_STEP_KEY_PREFIX2 as HOOK_STEP_KEY_PREFIX,
6794
4340
  STEP_TIMEOUT_MARKER,
6795
- STREAM_BACKPRESSURE_ERROR,
6796
- STREAM_CLOSE_ERROR,
6797
- STREAM_ENDED,
6798
- STREAM_READ_ERROR,
6799
- STREAM_STORE_ERROR,
6800
- STREAM_WRITE_ERROR,
6801
4341
  SnapshotDecodeError,
6802
4342
  SnapshotFormatError,
6803
4343
  SnapshotMismatchError,
6804
4344
  UnexpectedError,
6805
4345
  assertValidSnapshot,
6806
4346
  batchPresets,
6807
- chunk,
6808
4347
  clearStep,
6809
- collect,
6810
- composeMigrations,
6811
- composeValidators,
6812
- createApprovalChecker,
6813
4348
  createApprovalStateCollector,
6814
4349
  createApprovalStep,
6815
- createApprovalWebhookHandler,
6816
- createBackpressureController,
6817
- createEngine,
6818
- createEventHandler,
6819
- createExpressHandler,
6820
- createFileStreamStore,
6821
- createHITLOrchestrator,
6822
4350
  createHook,
6823
- createKeyRemoveMigration,
6824
- createKeyRenameMigration,
6825
- createMemoryApprovalStore,
6826
- createMemoryCache,
6827
- createMemoryStreamStore,
6828
- createMemoryWorkflowStateStore,
6829
4351
  createResource,
6830
4352
  createResourceScope,
6831
- createResultMapper,
6832
4353
  createResumeStateCollector,
6833
- createSagaWorkflow,
6834
- createSimpleHandler,
6835
- createTestableMemoryStreamStore,
6836
- createValueTransformMigration,
6837
- createVersionedState,
6838
- createVersionedStateLoader,
6839
- createWebhookHandler,
6840
4354
  createWorkflow,
6841
4355
  days,
6842
- defaultUnexpectedErrorMapper,
6843
- defaultValidationErrorMapper,
6844
- deserializeCauseNew,
6845
4356
  deserializeResumeState,
6846
- durable,
6847
- filter,
6848
- flatMap,
6849
- flatMapAsync,
6850
4357
  gatedStep,
6851
4358
  getPendingApprovals,
6852
4359
  getPendingHooks,
6853
4360
  getStepTimeoutMeta,
6854
- getStreamReader,
6855
4361
  hasPendingApproval,
6856
4362
  hasPendingHook,
6857
4363
  hours,
@@ -6859,75 +4365,36 @@ export {
6859
4365
  injectHook,
6860
4366
  isApprovalRejected,
6861
4367
  isBatchProcessingError,
6862
- isConcurrentExecution,
6863
4368
  isDuration,
6864
- isIdempotencyConflict,
6865
4369
  isInputValidationError,
6866
4370
  isInvalidBatchConfigError,
6867
- isLeaseExpired,
6868
- isMigrationError,
6869
4371
  isPendingApproval,
6870
4372
  isPendingHook,
6871
- isPersistenceError,
6872
4373
  isResourceCleanupError,
6873
4374
  isResumeState,
6874
- isSagaCompensationError,
6875
4375
  isSerializedResumeState,
6876
4376
  isStepComplete,
6877
4377
  isStepTimeoutError,
6878
- isStreamBackpressureError,
6879
- isStreamEnded,
6880
- isStreamReadError,
6881
- isStreamStoreError,
6882
- isStreamWriteError,
6883
- isValidationError,
6884
- isVersionIncompatibleError,
6885
- isVersionMismatch,
6886
4378
  isWorkflowCancelled,
6887
4379
  isWorkflowSnapshot,
6888
4380
  looksLikeWorkflowSnapshot,
6889
- map,
6890
- mapAsync,
6891
4381
  mergeSnapshots,
6892
4382
  millis,
6893
4383
  minutes,
6894
- parseVersionedState,
6895
4384
  pendingApproval,
6896
4385
  pendingHook,
6897
- pipe,
6898
4386
  processInBatches,
6899
- reduce,
6900
- requireFields,
6901
4387
  run,
6902
- runSaga,
6903
4388
  seconds,
6904
- sendWebhookResponse,
6905
- serializeError,
6906
4389
  serializeResumeState,
6907
- serializeThrown,
6908
- shouldApplyBackpressure,
6909
- skip,
6910
- skipWhile,
6911
- streamBackpressureError,
6912
- streamCloseError,
6913
- streamEnded,
6914
- streamReadError,
6915
- streamStoreError,
6916
- streamWriteError,
6917
- stringifyVersionedState,
6918
- take,
6919
- takeWhile,
6920
- toAsyncIterable,
6921
4390
  toDays,
6922
4391
  toHours,
6923
4392
  toMillis,
6924
4393
  toMinutes,
6925
4394
  toResumeState,
6926
4395
  toSeconds,
6927
- toWebhookRequest,
6928
4396
  validateInput,
6929
4397
  validateSnapshot,
6930
- validationError,
6931
4398
  withDeps,
6932
4399
  withScope
6933
4400
  };