awaitly 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/di-Biw_plBn.d.cts +15 -0
- package/dist/di-DxyH2N3i.d.ts +15 -0
- package/dist/durable.cjs +4670 -0
- package/dist/durable.cjs.map +1 -0
- package/dist/durable.d.cts +9 -0
- package/dist/durable.d.ts +9 -0
- package/dist/durable.js +4610 -0
- package/dist/durable.js.map +1 -0
- package/dist/{di-BbFFfO8y.d.ts → duration-BjVn3QpB.d.cts} +1 -15
- package/dist/{di-BDlT7InM.d.cts → duration-BjVn3QpB.d.ts} +1 -15
- package/dist/engine.cjs +4501 -0
- package/dist/engine.cjs.map +1 -0
- package/dist/engine.d.cts +113 -0
- package/dist/engine.d.ts +113 -0
- package/dist/engine.js +4474 -0
- package/dist/engine.js.map +1 -0
- package/dist/{errors-DtXvrCiO.d.cts → errors-sDSeaZBO.d.cts} +1 -1
- package/dist/{errors-DtXvrCiO.d.ts → errors-sDSeaZBO.d.ts} +1 -1
- package/dist/guards-CwQZro1F.d.ts +72 -0
- package/dist/guards-DhhOJZda.d.cts +72 -0
- package/dist/hitl-BnnnLzh2.d.cts +468 -0
- package/dist/hitl-pYfkQMv_.d.ts +468 -0
- package/dist/hitl.cjs +646 -0
- package/dist/hitl.cjs.map +1 -0
- package/dist/hitl.d.cts +440 -0
- package/dist/hitl.d.ts +440 -0
- package/dist/hitl.js +605 -0
- package/dist/hitl.js.map +1 -0
- package/dist/index-Bew12SZ8.d.ts +417 -0
- package/dist/index-Bwt-iz50.d.cts +417 -0
- package/dist/{types-B8NfNRGX.d.ts → index-Cv4K_3sZ.d.ts} +2 -1162
- package/dist/{types-BZ2f4MRR.d.cts → index-ICmfQi08.d.cts} +2 -1162
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1527
- package/dist/index.d.ts +11 -1527
- package/dist/persistence.cjs +552 -0
- package/dist/persistence.cjs.map +1 -0
- package/dist/persistence.d.cts +254 -0
- package/dist/persistence.d.ts +254 -0
- package/dist/persistence.js +499 -0
- package/dist/persistence.js.map +1 -0
- package/dist/reliability.cjs +1651 -0
- package/dist/reliability.cjs.map +1 -0
- package/dist/reliability.d.cts +1442 -0
- package/dist/reliability.d.ts +1442 -0
- package/dist/reliability.js +1589 -0
- package/dist/reliability.js.map +1 -0
- package/dist/result.d.cts +1 -1
- package/dist/result.d.ts +1 -1
- package/dist/run.cjs +2283 -0
- package/dist/run.cjs.map +1 -0
- package/dist/run.d.cts +90 -0
- package/dist/run.d.ts +90 -0
- package/dist/run.js +2247 -0
- package/dist/run.js.map +1 -0
- package/dist/saga.cjs +3699 -0
- package/dist/saga.cjs.map +1 -0
- package/dist/saga.d.cts +162 -0
- package/dist/saga.d.ts +162 -0
- package/dist/saga.js +3670 -0
- package/dist/saga.js.map +1 -0
- package/dist/store-contract-BI98VYmX.d.ts +66 -0
- package/dist/store-contract-C2HyR_o6.d.cts +66 -0
- package/dist/streaming.cjs +895 -0
- package/dist/streaming.cjs.map +1 -0
- package/dist/streaming.d.cts +594 -0
- package/dist/streaming.d.ts +594 -0
- package/dist/streaming.js +832 -0
- package/dist/streaming.js.map +1 -0
- package/dist/testing.d.cts +4 -2
- package/dist/testing.d.ts +4 -2
- package/dist/types-BDTxgKKc.d.ts +845 -0
- package/dist/types-C9Y71dua.d.cts +845 -0
- package/dist/types-d8q8iQlk.d.cts +323 -0
- package/dist/types-sObbY4mX.d.ts +323 -0
- package/dist/webhook.cjs +461 -0
- package/dist/webhook.cjs.map +1 -0
- package/dist/webhook.d.cts +497 -0
- package/dist/webhook.d.ts +497 -0
- package/dist/webhook.js +422 -0
- package/dist/webhook.js.map +1 -0
- package/dist/workflow.cjs +2 -2614
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +12 -3023
- package/dist/workflow.d.ts +12 -3023
- package/dist/workflow.js +2 -2535
- package/dist/workflow.js.map +1 -1
- package/package.json +46 -1
package/dist/workflow.cjs
CHANGED
|
@@ -23,66 +23,26 @@ __export(workflow_entry_exports, {
|
|
|
23
23
|
Duration: () => Duration,
|
|
24
24
|
HOOK_STEP_KEY_PREFIX: () => HOOK_STEP_KEY_PREFIX2,
|
|
25
25
|
STEP_TIMEOUT_MARKER: () => STEP_TIMEOUT_MARKER,
|
|
26
|
-
STREAM_BACKPRESSURE_ERROR: () => STREAM_BACKPRESSURE_ERROR,
|
|
27
|
-
STREAM_CLOSE_ERROR: () => STREAM_CLOSE_ERROR,
|
|
28
|
-
STREAM_ENDED: () => STREAM_ENDED,
|
|
29
|
-
STREAM_READ_ERROR: () => STREAM_READ_ERROR,
|
|
30
|
-
STREAM_STORE_ERROR: () => STREAM_STORE_ERROR,
|
|
31
|
-
STREAM_WRITE_ERROR: () => STREAM_WRITE_ERROR,
|
|
32
26
|
SnapshotDecodeError: () => SnapshotDecodeError,
|
|
33
27
|
SnapshotFormatError: () => SnapshotFormatError,
|
|
34
28
|
SnapshotMismatchError: () => SnapshotMismatchError,
|
|
35
29
|
UnexpectedError: () => UnexpectedError,
|
|
36
30
|
assertValidSnapshot: () => assertValidSnapshot,
|
|
37
31
|
batchPresets: () => batchPresets,
|
|
38
|
-
chunk: () => chunk,
|
|
39
32
|
clearStep: () => clearStep,
|
|
40
|
-
collect: () => collect,
|
|
41
|
-
composeMigrations: () => composeMigrations,
|
|
42
|
-
composeValidators: () => composeValidators,
|
|
43
|
-
createApprovalChecker: () => createApprovalChecker,
|
|
44
33
|
createApprovalStateCollector: () => createApprovalStateCollector,
|
|
45
34
|
createApprovalStep: () => createApprovalStep,
|
|
46
|
-
createApprovalWebhookHandler: () => createApprovalWebhookHandler,
|
|
47
|
-
createBackpressureController: () => createBackpressureController,
|
|
48
|
-
createEngine: () => createEngine,
|
|
49
|
-
createEventHandler: () => createEventHandler,
|
|
50
|
-
createExpressHandler: () => createExpressHandler,
|
|
51
|
-
createFileStreamStore: () => createFileStreamStore,
|
|
52
|
-
createHITLOrchestrator: () => createHITLOrchestrator,
|
|
53
35
|
createHook: () => createHook,
|
|
54
|
-
createKeyRemoveMigration: () => createKeyRemoveMigration,
|
|
55
|
-
createKeyRenameMigration: () => createKeyRenameMigration,
|
|
56
|
-
createMemoryApprovalStore: () => createMemoryApprovalStore,
|
|
57
|
-
createMemoryCache: () => createMemoryCache,
|
|
58
|
-
createMemoryStreamStore: () => createMemoryStreamStore,
|
|
59
|
-
createMemoryWorkflowStateStore: () => createMemoryWorkflowStateStore,
|
|
60
36
|
createResource: () => createResource,
|
|
61
37
|
createResourceScope: () => createResourceScope,
|
|
62
|
-
createResultMapper: () => createResultMapper,
|
|
63
38
|
createResumeStateCollector: () => createResumeStateCollector,
|
|
64
|
-
createSagaWorkflow: () => createSagaWorkflow,
|
|
65
|
-
createSimpleHandler: () => createSimpleHandler,
|
|
66
|
-
createTestableMemoryStreamStore: () => createTestableMemoryStreamStore,
|
|
67
|
-
createValueTransformMigration: () => createValueTransformMigration,
|
|
68
|
-
createVersionedState: () => createVersionedState,
|
|
69
|
-
createVersionedStateLoader: () => createVersionedStateLoader,
|
|
70
|
-
createWebhookHandler: () => createWebhookHandler,
|
|
71
39
|
createWorkflow: () => createWorkflow,
|
|
72
40
|
days: () => days,
|
|
73
|
-
defaultUnexpectedErrorMapper: () => defaultUnexpectedErrorMapper,
|
|
74
|
-
defaultValidationErrorMapper: () => defaultValidationErrorMapper,
|
|
75
|
-
deserializeCauseNew: () => deserializeCauseNew,
|
|
76
41
|
deserializeResumeState: () => deserializeResumeState,
|
|
77
|
-
durable: () => durable,
|
|
78
|
-
filter: () => filter,
|
|
79
|
-
flatMap: () => flatMap,
|
|
80
|
-
flatMapAsync: () => flatMapAsync,
|
|
81
42
|
gatedStep: () => gatedStep,
|
|
82
43
|
getPendingApprovals: () => getPendingApprovals,
|
|
83
44
|
getPendingHooks: () => getPendingHooks,
|
|
84
45
|
getStepTimeoutMeta: () => getStepTimeoutMeta,
|
|
85
|
-
getStreamReader: () => getStreamReader,
|
|
86
46
|
hasPendingApproval: () => hasPendingApproval,
|
|
87
47
|
hasPendingHook: () => hasPendingHook,
|
|
88
48
|
hours: () => hours,
|
|
@@ -90,75 +50,36 @@ __export(workflow_entry_exports, {
|
|
|
90
50
|
injectHook: () => injectHook,
|
|
91
51
|
isApprovalRejected: () => isApprovalRejected,
|
|
92
52
|
isBatchProcessingError: () => isBatchProcessingError,
|
|
93
|
-
isConcurrentExecution: () => isConcurrentExecution,
|
|
94
53
|
isDuration: () => isDuration,
|
|
95
|
-
isIdempotencyConflict: () => isIdempotencyConflict,
|
|
96
54
|
isInputValidationError: () => isInputValidationError,
|
|
97
55
|
isInvalidBatchConfigError: () => isInvalidBatchConfigError,
|
|
98
|
-
isLeaseExpired: () => isLeaseExpired,
|
|
99
|
-
isMigrationError: () => isMigrationError,
|
|
100
56
|
isPendingApproval: () => isPendingApproval,
|
|
101
57
|
isPendingHook: () => isPendingHook,
|
|
102
|
-
isPersistenceError: () => isPersistenceError,
|
|
103
58
|
isResourceCleanupError: () => isResourceCleanupError,
|
|
104
59
|
isResumeState: () => isResumeState,
|
|
105
|
-
isSagaCompensationError: () => isSagaCompensationError,
|
|
106
60
|
isSerializedResumeState: () => isSerializedResumeState,
|
|
107
61
|
isStepComplete: () => isStepComplete,
|
|
108
62
|
isStepTimeoutError: () => isStepTimeoutError,
|
|
109
|
-
isStreamBackpressureError: () => isStreamBackpressureError,
|
|
110
|
-
isStreamEnded: () => isStreamEnded,
|
|
111
|
-
isStreamReadError: () => isStreamReadError,
|
|
112
|
-
isStreamStoreError: () => isStreamStoreError,
|
|
113
|
-
isStreamWriteError: () => isStreamWriteError,
|
|
114
|
-
isValidationError: () => isValidationError,
|
|
115
|
-
isVersionIncompatibleError: () => isVersionIncompatibleError,
|
|
116
|
-
isVersionMismatch: () => isVersionMismatch,
|
|
117
63
|
isWorkflowCancelled: () => isWorkflowCancelled,
|
|
118
64
|
isWorkflowSnapshot: () => isWorkflowSnapshot,
|
|
119
65
|
looksLikeWorkflowSnapshot: () => looksLikeWorkflowSnapshot,
|
|
120
|
-
map: () => map,
|
|
121
|
-
mapAsync: () => mapAsync,
|
|
122
66
|
mergeSnapshots: () => mergeSnapshots,
|
|
123
67
|
millis: () => millis,
|
|
124
68
|
minutes: () => minutes,
|
|
125
|
-
parseVersionedState: () => parseVersionedState,
|
|
126
69
|
pendingApproval: () => pendingApproval,
|
|
127
70
|
pendingHook: () => pendingHook,
|
|
128
|
-
pipe: () => pipe,
|
|
129
71
|
processInBatches: () => processInBatches,
|
|
130
|
-
reduce: () => reduce,
|
|
131
|
-
requireFields: () => requireFields,
|
|
132
72
|
run: () => run,
|
|
133
|
-
runSaga: () => runSaga,
|
|
134
73
|
seconds: () => seconds,
|
|
135
|
-
sendWebhookResponse: () => sendWebhookResponse,
|
|
136
|
-
serializeError: () => serializeError,
|
|
137
74
|
serializeResumeState: () => serializeResumeState,
|
|
138
|
-
serializeThrown: () => serializeThrown,
|
|
139
|
-
shouldApplyBackpressure: () => shouldApplyBackpressure,
|
|
140
|
-
skip: () => skip,
|
|
141
|
-
skipWhile: () => skipWhile,
|
|
142
|
-
streamBackpressureError: () => streamBackpressureError,
|
|
143
|
-
streamCloseError: () => streamCloseError,
|
|
144
|
-
streamEnded: () => streamEnded,
|
|
145
|
-
streamReadError: () => streamReadError,
|
|
146
|
-
streamStoreError: () => streamStoreError,
|
|
147
|
-
streamWriteError: () => streamWriteError,
|
|
148
|
-
stringifyVersionedState: () => stringifyVersionedState,
|
|
149
|
-
take: () => take,
|
|
150
|
-
takeWhile: () => takeWhile,
|
|
151
|
-
toAsyncIterable: () => toAsyncIterable,
|
|
152
75
|
toDays: () => toDays,
|
|
153
76
|
toHours: () => toHours,
|
|
154
77
|
toMillis: () => toMillis,
|
|
155
78
|
toMinutes: () => toMinutes,
|
|
156
79
|
toResumeState: () => toResumeState,
|
|
157
80
|
toSeconds: () => toSeconds,
|
|
158
|
-
toWebhookRequest: () => toWebhookRequest,
|
|
159
81
|
validateInput: () => validateInput,
|
|
160
82
|
validateSnapshot: () => validateSnapshot,
|
|
161
|
-
validationError: () => validationError,
|
|
162
83
|
withDeps: () => withDeps,
|
|
163
84
|
withScope: () => withScope
|
|
164
85
|
});
|
|
@@ -551,7 +472,6 @@ function err(error, options) {
|
|
|
551
472
|
const cause = options?.cause;
|
|
552
473
|
return { ok: false, error, ...cause !== void 0 ? { cause } : {} };
|
|
553
474
|
}
|
|
554
|
-
var isOk = (r) => r.ok;
|
|
555
475
|
var isUnexpectedError = (e) => e instanceof UnexpectedError || typeof e === "object" && e !== null && "_tag" in e && e._tag === "UnexpectedError";
|
|
556
476
|
function extractErrorTag(error) {
|
|
557
477
|
if (error == null) return "unknown";
|
|
@@ -2670,69 +2590,6 @@ function deserializeCauseNew(serialized) {
|
|
|
2670
2590
|
}
|
|
2671
2591
|
return serialized.value !== void 0 ? serialized.value : serialized.stringRepresentation;
|
|
2672
2592
|
}
|
|
2673
|
-
function createMemoryCache(options = {}) {
|
|
2674
|
-
const { maxSize, ttl } = options;
|
|
2675
|
-
const cache = /* @__PURE__ */ new Map();
|
|
2676
|
-
const isExpired = (entry) => {
|
|
2677
|
-
const effectiveTtl = entry.entryTtl ?? ttl;
|
|
2678
|
-
if (!effectiveTtl) return false;
|
|
2679
|
-
return Date.now() - entry.timestamp > effectiveTtl;
|
|
2680
|
-
};
|
|
2681
|
-
const evictExpired = () => {
|
|
2682
|
-
for (const [key, entry] of cache) {
|
|
2683
|
-
if (isExpired(entry)) {
|
|
2684
|
-
cache.delete(key);
|
|
2685
|
-
}
|
|
2686
|
-
}
|
|
2687
|
-
};
|
|
2688
|
-
const evictOldest = () => {
|
|
2689
|
-
if (!maxSize || cache.size < maxSize) return;
|
|
2690
|
-
let oldestKey;
|
|
2691
|
-
let oldestTime = Infinity;
|
|
2692
|
-
for (const [key, entry] of cache) {
|
|
2693
|
-
if (entry.timestamp < oldestTime) {
|
|
2694
|
-
oldestTime = entry.timestamp;
|
|
2695
|
-
oldestKey = key;
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
if (oldestKey) {
|
|
2699
|
-
cache.delete(oldestKey);
|
|
2700
|
-
}
|
|
2701
|
-
};
|
|
2702
|
-
return {
|
|
2703
|
-
get(key) {
|
|
2704
|
-
evictExpired();
|
|
2705
|
-
const entry = cache.get(key);
|
|
2706
|
-
if (!entry) return void 0;
|
|
2707
|
-
if (isExpired(entry)) {
|
|
2708
|
-
cache.delete(key);
|
|
2709
|
-
return void 0;
|
|
2710
|
-
}
|
|
2711
|
-
return entry.result;
|
|
2712
|
-
},
|
|
2713
|
-
set(key, result, options2) {
|
|
2714
|
-
evictExpired();
|
|
2715
|
-
evictOldest();
|
|
2716
|
-
cache.set(key, { result, timestamp: Date.now(), entryTtl: options2?.ttl });
|
|
2717
|
-
},
|
|
2718
|
-
has(key) {
|
|
2719
|
-
evictExpired();
|
|
2720
|
-
const entry = cache.get(key);
|
|
2721
|
-
if (!entry) return false;
|
|
2722
|
-
if (isExpired(entry)) {
|
|
2723
|
-
cache.delete(key);
|
|
2724
|
-
return false;
|
|
2725
|
-
}
|
|
2726
|
-
return true;
|
|
2727
|
-
},
|
|
2728
|
-
delete(key) {
|
|
2729
|
-
return cache.delete(key);
|
|
2730
|
-
},
|
|
2731
|
-
clear() {
|
|
2732
|
-
cache.clear();
|
|
2733
|
-
}
|
|
2734
|
-
};
|
|
2735
|
-
}
|
|
2736
2593
|
|
|
2737
2594
|
// src/workflow/cache-encoding.ts
|
|
2738
2595
|
function isCachedErrorCause(cause) {
|
|
@@ -2981,24 +2838,7 @@ function createHook() {
|
|
|
2981
2838
|
var STREAM_WRITE_ERROR = "STREAM_WRITE_ERROR";
|
|
2982
2839
|
var STREAM_READ_ERROR = "STREAM_READ_ERROR";
|
|
2983
2840
|
var STREAM_CLOSE_ERROR = "STREAM_CLOSE_ERROR";
|
|
2984
|
-
var STREAM_STORE_ERROR = "STREAM_STORE_ERROR";
|
|
2985
2841
|
var STREAM_ENDED = "STREAM_ENDED";
|
|
2986
|
-
var STREAM_BACKPRESSURE_ERROR = "STREAM_BACKPRESSURE_ERROR";
|
|
2987
|
-
function isStreamEnded(error) {
|
|
2988
|
-
return typeof error === "object" && error !== null && error.type === STREAM_ENDED;
|
|
2989
|
-
}
|
|
2990
|
-
function isStreamWriteError(error) {
|
|
2991
|
-
return typeof error === "object" && error !== null && error.type === STREAM_WRITE_ERROR;
|
|
2992
|
-
}
|
|
2993
|
-
function isStreamReadError(error) {
|
|
2994
|
-
return typeof error === "object" && error !== null && error.type === STREAM_READ_ERROR;
|
|
2995
|
-
}
|
|
2996
|
-
function isStreamStoreError(error) {
|
|
2997
|
-
return typeof error === "object" && error !== null && error.type === STREAM_STORE_ERROR;
|
|
2998
|
-
}
|
|
2999
|
-
function isStreamBackpressureError(error) {
|
|
3000
|
-
return typeof error === "object" && error !== null && error.type === STREAM_BACKPRESSURE_ERROR;
|
|
3001
|
-
}
|
|
3002
2842
|
function streamWriteError(reason, message, cause) {
|
|
3003
2843
|
return {
|
|
3004
2844
|
type: STREAM_WRITE_ERROR,
|
|
@@ -3023,27 +2863,12 @@ function streamCloseError(reason, message, cause) {
|
|
|
3023
2863
|
...cause !== void 0 ? { cause } : {}
|
|
3024
2864
|
};
|
|
3025
2865
|
}
|
|
3026
|
-
function streamStoreError(reason, message, cause) {
|
|
3027
|
-
return {
|
|
3028
|
-
type: STREAM_STORE_ERROR,
|
|
3029
|
-
reason,
|
|
3030
|
-
message,
|
|
3031
|
-
...cause !== void 0 ? { cause } : {}
|
|
3032
|
-
};
|
|
3033
|
-
}
|
|
3034
2866
|
function streamEnded(finalPosition) {
|
|
3035
2867
|
return {
|
|
3036
2868
|
type: STREAM_ENDED,
|
|
3037
2869
|
finalPosition
|
|
3038
2870
|
};
|
|
3039
2871
|
}
|
|
3040
|
-
function streamBackpressureError(bufferedCount, highWaterMark) {
|
|
3041
|
-
return {
|
|
3042
|
-
type: STREAM_BACKPRESSURE_ERROR,
|
|
3043
|
-
bufferedCount,
|
|
3044
|
-
highWaterMark
|
|
3045
|
-
};
|
|
3046
|
-
}
|
|
3047
2872
|
|
|
3048
2873
|
// src/streaming/backpressure.ts
|
|
3049
2874
|
function createBackpressureController(options = {}) {
|
|
@@ -3115,9 +2940,6 @@ function createBackpressureController(options = {}) {
|
|
|
3115
2940
|
}
|
|
3116
2941
|
};
|
|
3117
2942
|
}
|
|
3118
|
-
function shouldApplyBackpressure(controller) {
|
|
3119
|
-
return controller.state === "paused";
|
|
3120
|
-
}
|
|
3121
2943
|
|
|
3122
2944
|
// src/workflow/validation.ts
|
|
3123
2945
|
function isInputValidationError(e) {
|
|
@@ -3997,7 +3819,7 @@ Use serializeResumeState() and deserializeResumeState() from 'awaitly/workflow'
|
|
|
3997
3819
|
const results = [];
|
|
3998
3820
|
let processedCount = 0;
|
|
3999
3821
|
let lastPosition = -1;
|
|
4000
|
-
const
|
|
3822
|
+
const isStreamReader = (s) => {
|
|
4001
3823
|
return typeof s === "object" && s !== null && "read" in s && typeof s.read === "function";
|
|
4002
3824
|
};
|
|
4003
3825
|
const processItem = async (item, itemIndex, itemPosition, namespace) => {
|
|
@@ -4011,7 +3833,7 @@ Use serializeResumeState() and deserializeResumeState() from 'awaitly/workflow'
|
|
|
4011
3833
|
);
|
|
4012
3834
|
return { index: itemIndex, position: itemPosition, result: stepResult };
|
|
4013
3835
|
};
|
|
4014
|
-
if (
|
|
3836
|
+
if (isStreamReader(source)) {
|
|
4015
3837
|
if (concurrency <= 1) {
|
|
4016
3838
|
let itemPosition = source.position;
|
|
4017
3839
|
let readResult = await source.read();
|
|
@@ -4312,2361 +4134,6 @@ Use serializeResumeState() and deserializeResumeState() from 'awaitly/workflow'
|
|
|
4312
4134
|
return workflow;
|
|
4313
4135
|
}
|
|
4314
4136
|
|
|
4315
|
-
// src/durable/index.ts
|
|
4316
|
-
var defaultStore;
|
|
4317
|
-
function createMemorySnapshotStore() {
|
|
4318
|
-
const store = /* @__PURE__ */ new Map();
|
|
4319
|
-
return {
|
|
4320
|
-
async save(id, snapshot) {
|
|
4321
|
-
store.set(id, { snapshot, updatedAt: /* @__PURE__ */ new Date() });
|
|
4322
|
-
},
|
|
4323
|
-
async load(id) {
|
|
4324
|
-
const entry = store.get(id);
|
|
4325
|
-
return entry?.snapshot ?? null;
|
|
4326
|
-
},
|
|
4327
|
-
async delete(id) {
|
|
4328
|
-
store.delete(id);
|
|
4329
|
-
},
|
|
4330
|
-
async list(options) {
|
|
4331
|
-
const prefix = options?.prefix ?? "";
|
|
4332
|
-
const limit = options?.limit ?? 100;
|
|
4333
|
-
const results = [];
|
|
4334
|
-
for (const [id, entry] of store.entries()) {
|
|
4335
|
-
if (prefix && !id.startsWith(prefix)) continue;
|
|
4336
|
-
results.push({ id, updatedAt: entry.updatedAt.toISOString() });
|
|
4337
|
-
if (results.length >= limit) break;
|
|
4338
|
-
}
|
|
4339
|
-
results.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
4340
|
-
return results;
|
|
4341
|
-
},
|
|
4342
|
-
async close() {
|
|
4343
|
-
}
|
|
4344
|
-
};
|
|
4345
|
-
}
|
|
4346
|
-
function getDefaultStore() {
|
|
4347
|
-
if (defaultStore === void 0) {
|
|
4348
|
-
defaultStore = createMemorySnapshotStore();
|
|
4349
|
-
}
|
|
4350
|
-
return defaultStore;
|
|
4351
|
-
}
|
|
4352
|
-
function isVersionMismatch(error) {
|
|
4353
|
-
return typeof error === "object" && error !== null && error.type === "VERSION_MISMATCH";
|
|
4354
|
-
}
|
|
4355
|
-
function isConcurrentExecution(error) {
|
|
4356
|
-
return typeof error === "object" && error !== null && error.type === "CONCURRENT_EXECUTION";
|
|
4357
|
-
}
|
|
4358
|
-
function isPersistenceError(error) {
|
|
4359
|
-
return typeof error === "object" && error !== null && error.type === "PERSISTENCE_ERROR";
|
|
4360
|
-
}
|
|
4361
|
-
function isLeaseExpired(error) {
|
|
4362
|
-
return typeof error === "object" && error !== null && error.type === "LEASE_EXPIRED";
|
|
4363
|
-
}
|
|
4364
|
-
function isIdempotencyConflict(error) {
|
|
4365
|
-
return typeof error === "object" && error !== null && error.type === "IDEMPOTENCY_CONFLICT";
|
|
4366
|
-
}
|
|
4367
|
-
function hasWorkflowLock(store) {
|
|
4368
|
-
return typeof store.tryAcquire === "function" && typeof store.release === "function";
|
|
4369
|
-
}
|
|
4370
|
-
var activeWorkflows = /* @__PURE__ */ new Set();
|
|
4371
|
-
var pendingIdempotencyRuns = /* @__PURE__ */ new Map();
|
|
4372
|
-
var durable = {
|
|
4373
|
-
/**
|
|
4374
|
-
* Execute a workflow with automatic state persistence.
|
|
4375
|
-
*
|
|
4376
|
-
* Features:
|
|
4377
|
-
* - **Automatic checkpointing**: State is saved after each keyed step
|
|
4378
|
-
* - **Crash recovery**: Resume from the last completed step on restart
|
|
4379
|
-
* - **Version checking**: Reject resume if workflow logic version changed
|
|
4380
|
-
* - **Concurrency control**: Prevent duplicate executions of the same workflow ID
|
|
4381
|
-
* - **Cancellation support**: Integrates with AbortSignal for graceful shutdown
|
|
4382
|
-
*
|
|
4383
|
-
* ## How It Works
|
|
4384
|
-
*
|
|
4385
|
-
* 1. On start: Load existing state from store (if any)
|
|
4386
|
-
* 2. Check version compatibility (reject if mismatch)
|
|
4387
|
-
* 3. Pre-populate cache from loaded state (skip completed steps)
|
|
4388
|
-
* 4. Execute workflow, persisting state after each keyed step
|
|
4389
|
-
* 5. On completion: Delete stored state (clean up)
|
|
4390
|
-
* 6. On error/cancellation: State remains for future resume
|
|
4391
|
-
*
|
|
4392
|
-
* ## Important Notes
|
|
4393
|
-
*
|
|
4394
|
-
* - **Only keyed steps are durable**: Use `{ key: 'step-name' }` option
|
|
4395
|
-
* - **Steps should be idempotent**: They may be retried on resume
|
|
4396
|
-
* - **Serialization**: State is JSON-serialized; complex objects may lose fidelity
|
|
4397
|
-
*
|
|
4398
|
-
* @param deps - Workflow dependencies (Result-returning functions)
|
|
4399
|
-
* @param fn - Workflow function receiving ({ step, deps, ctx })
|
|
4400
|
-
* @param options - Durable execution options
|
|
4401
|
-
* @returns AsyncResult with workflow result or error
|
|
4402
|
-
*
|
|
4403
|
-
* @example
|
|
4404
|
-
* ```typescript
|
|
4405
|
-
* import { durable } from 'awaitly/workflow';
|
|
4406
|
-
*
|
|
4407
|
-
* // Zero-config: uses in-memory store (per process)
|
|
4408
|
-
* const result = await durable.run(
|
|
4409
|
-
* { fetchUser, createOrder, sendEmail },
|
|
4410
|
-
* async ({ step, deps: { fetchUser, createOrder, sendEmail } }) => {
|
|
4411
|
-
* const user = await step(() => fetchUser('123'), { key: 'fetch-user' });
|
|
4412
|
-
* const order = await step(() => createOrder(user), { key: 'create-order' });
|
|
4413
|
-
* await step(() => sendEmail(order), { key: 'send-email' });
|
|
4414
|
-
* return order;
|
|
4415
|
-
* },
|
|
4416
|
-
* { id: 'checkout-123' }
|
|
4417
|
-
* );
|
|
4418
|
-
*
|
|
4419
|
-
* // Persistence across restarts: pass a store adapter
|
|
4420
|
-
* import { postgres } from 'awaitly-postgres'; // or awaitly-mongo, awaitly-libsql
|
|
4421
|
-
* const store = postgres(process.env.DATABASE_URL);
|
|
4422
|
-
* await durable.run(deps, fn, { id: 'checkout-123', store });
|
|
4423
|
-
*
|
|
4424
|
-
* if (result.ok) {
|
|
4425
|
-
* console.log('Order completed:', result.value);
|
|
4426
|
-
* } else if (isWorkflowCancelled(result.error)) {
|
|
4427
|
-
* console.log('Workflow cancelled at:', result.error.lastStepKey);
|
|
4428
|
-
* }
|
|
4429
|
-
* ```
|
|
4430
|
-
*/
|
|
4431
|
-
async run(deps, fn, options) {
|
|
4432
|
-
const {
|
|
4433
|
-
id,
|
|
4434
|
-
store: storeOption,
|
|
4435
|
-
version = 1,
|
|
4436
|
-
allowConcurrent = false,
|
|
4437
|
-
lockTtlMs = 6e4,
|
|
4438
|
-
heartbeatIntervalMs,
|
|
4439
|
-
abortOnLeaseLoss,
|
|
4440
|
-
metadata,
|
|
4441
|
-
signal,
|
|
4442
|
-
createContext,
|
|
4443
|
-
onEvent,
|
|
4444
|
-
onError,
|
|
4445
|
-
onVersionMismatch,
|
|
4446
|
-
idempotencyKey,
|
|
4447
|
-
input
|
|
4448
|
-
} = options;
|
|
4449
|
-
const effectiveStore = storeOption ?? getDefaultStore();
|
|
4450
|
-
let resolveIdempotencyRun;
|
|
4451
|
-
if (idempotencyKey) {
|
|
4452
|
-
const idemId = `idem:${idempotencyKey}`;
|
|
4453
|
-
const pending = pendingIdempotencyRuns.get(idemId);
|
|
4454
|
-
if (pending) {
|
|
4455
|
-
return await pending;
|
|
4456
|
-
}
|
|
4457
|
-
pendingIdempotencyRuns.set(idemId, new Promise((r) => {
|
|
4458
|
-
resolveIdempotencyRun = r;
|
|
4459
|
-
}));
|
|
4460
|
-
try {
|
|
4461
|
-
const idemSnapshot = await effectiveStore.load(idemId);
|
|
4462
|
-
if (idemSnapshot) {
|
|
4463
|
-
if (input !== void 0 && idemSnapshot.metadata?.input !== void 0) {
|
|
4464
|
-
const storedInput = JSON.stringify(idemSnapshot.metadata.input);
|
|
4465
|
-
const currentInput = JSON.stringify(input);
|
|
4466
|
-
if (storedInput !== currentInput) {
|
|
4467
|
-
const result = err({
|
|
4468
|
-
type: "IDEMPOTENCY_CONFLICT",
|
|
4469
|
-
idempotencyKey,
|
|
4470
|
-
workflowId: id,
|
|
4471
|
-
message: `Idempotency key '${idempotencyKey}' already used with different input for workflow '${id}'.`
|
|
4472
|
-
});
|
|
4473
|
-
resolveIdempotencyRun(result);
|
|
4474
|
-
pendingIdempotencyRuns.delete(idemId);
|
|
4475
|
-
resolveIdempotencyRun = void 0;
|
|
4476
|
-
return result;
|
|
4477
|
-
}
|
|
4478
|
-
}
|
|
4479
|
-
if (idemSnapshot.execution.status === "completed" && idemSnapshot.metadata?.finalResult !== void 0) {
|
|
4480
|
-
const result = idemSnapshot.metadata.finalResult;
|
|
4481
|
-
resolveIdempotencyRun(result);
|
|
4482
|
-
pendingIdempotencyRuns.delete(idemId);
|
|
4483
|
-
resolveIdempotencyRun = void 0;
|
|
4484
|
-
return result;
|
|
4485
|
-
}
|
|
4486
|
-
if (idemSnapshot.execution.status === "running") {
|
|
4487
|
-
const result = err({
|
|
4488
|
-
type: "CONCURRENT_EXECUTION",
|
|
4489
|
-
workflowId: id,
|
|
4490
|
-
message: `Workflow '${id}' with idempotency key '${idempotencyKey}' is already running.`,
|
|
4491
|
-
reason: "cross-process"
|
|
4492
|
-
});
|
|
4493
|
-
resolveIdempotencyRun(result);
|
|
4494
|
-
pendingIdempotencyRuns.delete(idemId);
|
|
4495
|
-
resolveIdempotencyRun = void 0;
|
|
4496
|
-
return result;
|
|
4497
|
-
}
|
|
4498
|
-
}
|
|
4499
|
-
} catch {
|
|
4500
|
-
}
|
|
4501
|
-
try {
|
|
4502
|
-
await effectiveStore.save(idemId, {
|
|
4503
|
-
formatVersion: 1,
|
|
4504
|
-
steps: {},
|
|
4505
|
-
execution: {
|
|
4506
|
-
status: "running",
|
|
4507
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
4508
|
-
},
|
|
4509
|
-
metadata: {
|
|
4510
|
-
workflowId: id,
|
|
4511
|
-
idempotencyKey,
|
|
4512
|
-
input
|
|
4513
|
-
}
|
|
4514
|
-
});
|
|
4515
|
-
} catch {
|
|
4516
|
-
}
|
|
4517
|
-
}
|
|
4518
|
-
if (!allowConcurrent && activeWorkflows.has(id)) {
|
|
4519
|
-
const error = {
|
|
4520
|
-
type: "CONCURRENT_EXECUTION",
|
|
4521
|
-
workflowId: id,
|
|
4522
|
-
message: `Workflow '${id}' is already running. Set allowConcurrent: true to allow parallel executions.`,
|
|
4523
|
-
reason: "in-process"
|
|
4524
|
-
};
|
|
4525
|
-
return err(error);
|
|
4526
|
-
}
|
|
4527
|
-
let leaseOwnerToken = null;
|
|
4528
|
-
if (!allowConcurrent && hasWorkflowLock(effectiveStore)) {
|
|
4529
|
-
let lease;
|
|
4530
|
-
try {
|
|
4531
|
-
lease = await effectiveStore.tryAcquire(id, { ttlMs: lockTtlMs });
|
|
4532
|
-
} catch (lockError) {
|
|
4533
|
-
const error = {
|
|
4534
|
-
type: "PERSISTENCE_ERROR",
|
|
4535
|
-
operation: "load",
|
|
4536
|
-
workflowId: id,
|
|
4537
|
-
cause: lockError,
|
|
4538
|
-
message: `Failed to acquire lock for workflow '${id}': ${lockError instanceof Error ? lockError.message : String(lockError)}`
|
|
4539
|
-
};
|
|
4540
|
-
return err(error);
|
|
4541
|
-
}
|
|
4542
|
-
if (lease === null) {
|
|
4543
|
-
const error = {
|
|
4544
|
-
type: "CONCURRENT_EXECUTION",
|
|
4545
|
-
workflowId: id,
|
|
4546
|
-
message: `Workflow '${id}' is already running (lease held by another process). Set allowConcurrent: true to allow parallel executions.`,
|
|
4547
|
-
reason: "cross-process"
|
|
4548
|
-
};
|
|
4549
|
-
return err(error);
|
|
4550
|
-
}
|
|
4551
|
-
leaseOwnerToken = lease.ownerToken;
|
|
4552
|
-
}
|
|
4553
|
-
let heartbeatTimer;
|
|
4554
|
-
let leaseAbortController;
|
|
4555
|
-
const lockStore = effectiveStore;
|
|
4556
|
-
if (leaseOwnerToken && typeof lockStore.renew === "function") {
|
|
4557
|
-
const heartbeatMs = heartbeatIntervalMs ?? Math.floor(lockTtlMs / 3);
|
|
4558
|
-
leaseAbortController = new AbortController();
|
|
4559
|
-
heartbeatTimer = setInterval(async () => {
|
|
4560
|
-
try {
|
|
4561
|
-
const renewed = await lockStore.renew(id, leaseOwnerToken, { ttlMs: lockTtlMs });
|
|
4562
|
-
if (!renewed) {
|
|
4563
|
-
leaseAbortController.abort(new Error("Lease expired"));
|
|
4564
|
-
}
|
|
4565
|
-
} catch {
|
|
4566
|
-
leaseAbortController.abort(new Error("Lease renewal failed"));
|
|
4567
|
-
}
|
|
4568
|
-
}, heartbeatMs);
|
|
4569
|
-
}
|
|
4570
|
-
activeWorkflows.add(id);
|
|
4571
|
-
let durableResult;
|
|
4572
|
-
try {
|
|
4573
|
-
let existingSnapshot = null;
|
|
4574
|
-
try {
|
|
4575
|
-
existingSnapshot = await effectiveStore.load(id);
|
|
4576
|
-
} catch (loadError) {
|
|
4577
|
-
const error = {
|
|
4578
|
-
type: "PERSISTENCE_ERROR",
|
|
4579
|
-
operation: "load",
|
|
4580
|
-
workflowId: id,
|
|
4581
|
-
cause: loadError,
|
|
4582
|
-
message: `Failed to load state for workflow '${id}': ${loadError instanceof Error ? loadError.message : String(loadError)}`
|
|
4583
|
-
};
|
|
4584
|
-
durableResult = err(error);
|
|
4585
|
-
return err(error);
|
|
4586
|
-
}
|
|
4587
|
-
if (existingSnapshot) {
|
|
4588
|
-
try {
|
|
4589
|
-
assertValidSnapshot(existingSnapshot);
|
|
4590
|
-
} catch (validationError2) {
|
|
4591
|
-
if (validationError2 instanceof SnapshotFormatError) {
|
|
4592
|
-
const error = {
|
|
4593
|
-
type: "PERSISTENCE_ERROR",
|
|
4594
|
-
operation: "load",
|
|
4595
|
-
workflowId: id,
|
|
4596
|
-
cause: validationError2,
|
|
4597
|
-
message: `Invalid snapshot format for workflow '${id}': ${validationError2.message}`
|
|
4598
|
-
};
|
|
4599
|
-
durableResult = err(error);
|
|
4600
|
-
return err(error);
|
|
4601
|
-
}
|
|
4602
|
-
throw validationError2;
|
|
4603
|
-
}
|
|
4604
|
-
}
|
|
4605
|
-
if (existingSnapshot) {
|
|
4606
|
-
const storedVersion = typeof existingSnapshot.metadata?.version === "number" ? existingSnapshot.metadata.version : 1;
|
|
4607
|
-
if (storedVersion !== version) {
|
|
4608
|
-
const error = {
|
|
4609
|
-
type: "VERSION_MISMATCH",
|
|
4610
|
-
workflowId: id,
|
|
4611
|
-
storedVersion,
|
|
4612
|
-
requestedVersion: version,
|
|
4613
|
-
currentVersion: version,
|
|
4614
|
-
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.`
|
|
4615
|
-
};
|
|
4616
|
-
if (!onVersionMismatch) {
|
|
4617
|
-
durableResult = err(error);
|
|
4618
|
-
return err(error);
|
|
4619
|
-
}
|
|
4620
|
-
const resolution = await Promise.resolve(
|
|
4621
|
-
onVersionMismatch({ id, storedVersion, requestedVersion: version })
|
|
4622
|
-
);
|
|
4623
|
-
if (resolution === "throw") {
|
|
4624
|
-
durableResult = err(error);
|
|
4625
|
-
return err(error);
|
|
4626
|
-
}
|
|
4627
|
-
if (resolution === "clear") {
|
|
4628
|
-
try {
|
|
4629
|
-
await effectiveStore.delete(id);
|
|
4630
|
-
} catch {
|
|
4631
|
-
}
|
|
4632
|
-
existingSnapshot = null;
|
|
4633
|
-
} else {
|
|
4634
|
-
existingSnapshot = resolution.migratedSnapshot;
|
|
4635
|
-
}
|
|
4636
|
-
}
|
|
4637
|
-
}
|
|
4638
|
-
const emitDurableEvent = (event, ctx) => {
|
|
4639
|
-
if (onEvent) {
|
|
4640
|
-
onEvent(event, ctx);
|
|
4641
|
-
}
|
|
4642
|
-
};
|
|
4643
|
-
const resumeCollector = createResumeStateCollector();
|
|
4644
|
-
const workflowOptions = {
|
|
4645
|
-
// Restore from existing snapshot
|
|
4646
|
-
snapshot: existingSnapshot,
|
|
4647
|
-
// Persist after each keyed step
|
|
4648
|
-
onAfterStep: async (stepKey, result2, wfId, ctx) => {
|
|
4649
|
-
try {
|
|
4650
|
-
const collectedState = resumeCollector.getResumeState();
|
|
4651
|
-
const steps = {};
|
|
4652
|
-
for (const [key, entry] of collectedState.steps) {
|
|
4653
|
-
if (entry.result.ok) {
|
|
4654
|
-
steps[key] = { ok: true, value: entry.result.value };
|
|
4655
|
-
} else {
|
|
4656
|
-
const cause = entry.result.cause;
|
|
4657
|
-
const serializedCause = cause instanceof Error ? serializeError(cause) : serializeThrown(cause);
|
|
4658
|
-
const origin = entry.meta?.origin === "throw" ? "throw" : "result";
|
|
4659
|
-
steps[key] = {
|
|
4660
|
-
ok: false,
|
|
4661
|
-
error: entry.result.error,
|
|
4662
|
-
cause: serializedCause,
|
|
4663
|
-
meta: { origin }
|
|
4664
|
-
};
|
|
4665
|
-
}
|
|
4666
|
-
}
|
|
4667
|
-
const currentSnapshot = {
|
|
4668
|
-
formatVersion: 1,
|
|
4669
|
-
workflowName: id,
|
|
4670
|
-
steps,
|
|
4671
|
-
execution: {
|
|
4672
|
-
status: "running",
|
|
4673
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4674
|
-
currentStepId: stepKey
|
|
4675
|
-
},
|
|
4676
|
-
metadata: {
|
|
4677
|
-
...existingSnapshot?.metadata ?? {},
|
|
4678
|
-
...metadata,
|
|
4679
|
-
version,
|
|
4680
|
-
lastStepKey: stepKey
|
|
4681
|
-
}
|
|
4682
|
-
};
|
|
4683
|
-
let snapshotToSave = existingSnapshot ? mergeSnapshots(existingSnapshot, currentSnapshot) : currentSnapshot;
|
|
4684
|
-
if (snapshotToSave.warnings && snapshotToSave.warnings.length > 0) {
|
|
4685
|
-
const currentStepKeys = new Set(Object.keys(currentSnapshot.steps));
|
|
4686
|
-
const filtered = snapshotToSave.warnings.filter(
|
|
4687
|
-
(w) => !currentStepKeys.has(w.stepId)
|
|
4688
|
-
);
|
|
4689
|
-
snapshotToSave = {
|
|
4690
|
-
...snapshotToSave,
|
|
4691
|
-
warnings: filtered.length > 0 ? filtered : void 0
|
|
4692
|
-
};
|
|
4693
|
-
}
|
|
4694
|
-
await effectiveStore.save(id, snapshotToSave);
|
|
4695
|
-
emitDurableEvent(
|
|
4696
|
-
{
|
|
4697
|
-
type: "persist_success",
|
|
4698
|
-
workflowId: wfId,
|
|
4699
|
-
stepKey,
|
|
4700
|
-
ts: Date.now(),
|
|
4701
|
-
context: ctx
|
|
4702
|
-
},
|
|
4703
|
-
ctx
|
|
4704
|
-
);
|
|
4705
|
-
} catch (persistError) {
|
|
4706
|
-
emitDurableEvent(
|
|
4707
|
-
{
|
|
4708
|
-
type: "persist_error",
|
|
4709
|
-
workflowId: wfId,
|
|
4710
|
-
stepKey,
|
|
4711
|
-
error: persistError,
|
|
4712
|
-
ts: Date.now(),
|
|
4713
|
-
context: ctx
|
|
4714
|
-
},
|
|
4715
|
-
ctx
|
|
4716
|
-
);
|
|
4717
|
-
}
|
|
4718
|
-
},
|
|
4719
|
-
// Forward events and collect step results for snapshot building
|
|
4720
|
-
onEvent: (event, ctx) => {
|
|
4721
|
-
resumeCollector.handleEvent(event);
|
|
4722
|
-
emitDurableEvent(event, ctx);
|
|
4723
|
-
},
|
|
4724
|
-
onError,
|
|
4725
|
-
signal: leaseAbortController && signal ? AbortSignal.any([signal, leaseAbortController.signal]) : leaseAbortController?.signal ?? signal,
|
|
4726
|
-
createContext
|
|
4727
|
-
};
|
|
4728
|
-
let workflowInstance;
|
|
4729
|
-
try {
|
|
4730
|
-
workflowInstance = createWorkflow(id, deps, workflowOptions);
|
|
4731
|
-
} catch (createError) {
|
|
4732
|
-
if (createError instanceof SnapshotFormatError) {
|
|
4733
|
-
const error = {
|
|
4734
|
-
type: "PERSISTENCE_ERROR",
|
|
4735
|
-
operation: "load",
|
|
4736
|
-
workflowId: id,
|
|
4737
|
-
cause: createError,
|
|
4738
|
-
message: `Invalid snapshot format for workflow '${id}': ${createError.message}`
|
|
4739
|
-
};
|
|
4740
|
-
durableResult = err(error);
|
|
4741
|
-
return err(error);
|
|
4742
|
-
}
|
|
4743
|
-
throw createError;
|
|
4744
|
-
}
|
|
4745
|
-
let result;
|
|
4746
|
-
try {
|
|
4747
|
-
result = await workflowInstance.run(fn);
|
|
4748
|
-
} catch (runError) {
|
|
4749
|
-
if (runError instanceof SnapshotFormatError || runError instanceof SnapshotDecodeError) {
|
|
4750
|
-
const error = {
|
|
4751
|
-
type: "PERSISTENCE_ERROR",
|
|
4752
|
-
operation: "load",
|
|
4753
|
-
workflowId: id,
|
|
4754
|
-
cause: runError,
|
|
4755
|
-
message: `Invalid snapshot format for workflow '${id}': ${runError.message}`
|
|
4756
|
-
};
|
|
4757
|
-
durableResult = err(error);
|
|
4758
|
-
return err(error);
|
|
4759
|
-
}
|
|
4760
|
-
throw runError;
|
|
4761
|
-
}
|
|
4762
|
-
if (abortOnLeaseLoss !== false && leaseAbortController?.signal.aborted) {
|
|
4763
|
-
const leaseErr = err({
|
|
4764
|
-
type: "LEASE_EXPIRED",
|
|
4765
|
-
workflowId: id,
|
|
4766
|
-
message: `Lease expired for workflow '${id}' during execution. The workflow may have been reclaimed by another process.`
|
|
4767
|
-
});
|
|
4768
|
-
durableResult = leaseErr;
|
|
4769
|
-
return leaseErr;
|
|
4770
|
-
}
|
|
4771
|
-
if (result.ok) {
|
|
4772
|
-
try {
|
|
4773
|
-
await effectiveStore.delete(id);
|
|
4774
|
-
} catch (deleteError) {
|
|
4775
|
-
const error = {
|
|
4776
|
-
type: "PERSISTENCE_ERROR",
|
|
4777
|
-
operation: "delete",
|
|
4778
|
-
workflowId: id,
|
|
4779
|
-
cause: deleteError,
|
|
4780
|
-
message: `Failed to delete state for workflow '${id}': ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`
|
|
4781
|
-
};
|
|
4782
|
-
durableResult = err(error);
|
|
4783
|
-
return err(error);
|
|
4784
|
-
}
|
|
4785
|
-
if (idempotencyKey) {
|
|
4786
|
-
const idemId = `idem:${idempotencyKey}`;
|
|
4787
|
-
try {
|
|
4788
|
-
await effectiveStore.save(idemId, {
|
|
4789
|
-
formatVersion: 1,
|
|
4790
|
-
steps: {},
|
|
4791
|
-
execution: {
|
|
4792
|
-
status: "completed",
|
|
4793
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4794
|
-
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4795
|
-
},
|
|
4796
|
-
metadata: {
|
|
4797
|
-
workflowId: id,
|
|
4798
|
-
idempotencyKey,
|
|
4799
|
-
input,
|
|
4800
|
-
finalResult: result
|
|
4801
|
-
}
|
|
4802
|
-
});
|
|
4803
|
-
} catch {
|
|
4804
|
-
}
|
|
4805
|
-
}
|
|
4806
|
-
}
|
|
4807
|
-
durableResult = result;
|
|
4808
|
-
return result;
|
|
4809
|
-
} finally {
|
|
4810
|
-
activeWorkflows.delete(id);
|
|
4811
|
-
if (resolveIdempotencyRun) {
|
|
4812
|
-
resolveIdempotencyRun(durableResult);
|
|
4813
|
-
pendingIdempotencyRuns.delete(`idem:${idempotencyKey}`);
|
|
4814
|
-
}
|
|
4815
|
-
if (heartbeatTimer) {
|
|
4816
|
-
clearInterval(heartbeatTimer);
|
|
4817
|
-
}
|
|
4818
|
-
if (leaseOwnerToken !== null && hasWorkflowLock(effectiveStore)) {
|
|
4819
|
-
try {
|
|
4820
|
-
await effectiveStore.release(id, leaseOwnerToken);
|
|
4821
|
-
} catch {
|
|
4822
|
-
}
|
|
4823
|
-
}
|
|
4824
|
-
}
|
|
4825
|
-
},
|
|
4826
|
-
/**
|
|
4827
|
-
* Clear all persisted workflow state from the store.
|
|
4828
|
-
* Use for admin/testing. If the store implements `clear()`, that is used;
|
|
4829
|
-
* otherwise clears by listing and deleting in pages.
|
|
4830
|
-
*
|
|
4831
|
-
* @param store - Snapshot store
|
|
4832
|
-
*/
|
|
4833
|
-
async clearState(store) {
|
|
4834
|
-
const storeWithClear = store;
|
|
4835
|
-
if (typeof storeWithClear.clear === "function") {
|
|
4836
|
-
await storeWithClear.clear();
|
|
4837
|
-
return;
|
|
4838
|
-
}
|
|
4839
|
-
const limit = 100;
|
|
4840
|
-
for (; ; ) {
|
|
4841
|
-
const entries = await store.list({ limit });
|
|
4842
|
-
if (entries.length === 0) break;
|
|
4843
|
-
const ids = entries.map((e) => e.id);
|
|
4844
|
-
await this.deleteStates(store, ids, { continueOnError: true });
|
|
4845
|
-
if (entries.length < limit) break;
|
|
4846
|
-
}
|
|
4847
|
-
},
|
|
4848
|
-
/**
|
|
4849
|
-
* Check if a workflow ID has persisted state (can be resumed).
|
|
4850
|
-
*
|
|
4851
|
-
* @param store - Snapshot store
|
|
4852
|
-
* @param id - Workflow execution ID
|
|
4853
|
-
* @returns `true` if state exists, `false` otherwise (including on store errors)
|
|
4854
|
-
*/
|
|
4855
|
-
async hasState(store, id) {
|
|
4856
|
-
try {
|
|
4857
|
-
const snapshot = await store.load(id);
|
|
4858
|
-
return snapshot !== null;
|
|
4859
|
-
} catch {
|
|
4860
|
-
return false;
|
|
4861
|
-
}
|
|
4862
|
-
},
|
|
4863
|
-
/**
|
|
4864
|
-
* Delete persisted state for a workflow (cancel resume capability).
|
|
4865
|
-
* Deleting is effectively an ack/reset: the workflow can no longer resume from that state.
|
|
4866
|
-
* If you delete while a run is in flight, the run continues; on success it may delete again (no-op) or save (recreating state).
|
|
4867
|
-
* For multi-worker safety, prefer deleting only when the workflow is not running or when you hold the lock.
|
|
4868
|
-
*
|
|
4869
|
-
* @param store - Snapshot store
|
|
4870
|
-
* @param id - Workflow execution ID
|
|
4871
|
-
* @returns `true` on success, `false` on store errors
|
|
4872
|
-
*/
|
|
4873
|
-
async deleteState(store, id) {
|
|
4874
|
-
try {
|
|
4875
|
-
await store.delete(id);
|
|
4876
|
-
return true;
|
|
4877
|
-
} catch {
|
|
4878
|
-
return false;
|
|
4879
|
-
}
|
|
4880
|
-
},
|
|
4881
|
-
/**
|
|
4882
|
-
* Bulk delete persisted state for multiple workflow IDs (best-effort).
|
|
4883
|
-
* Use for admin/cleanup. Deletes in a loop with optional concurrency.
|
|
4884
|
-
*
|
|
4885
|
-
* @param store - Snapshot store
|
|
4886
|
-
* @param ids - Workflow execution IDs to delete
|
|
4887
|
-
* @param options - Optional concurrency and error handling
|
|
4888
|
-
* @returns Count of deleted entries and any errors when continueOnError is true
|
|
4889
|
-
*/
|
|
4890
|
-
async deleteStates(store, ids, options = {}) {
|
|
4891
|
-
const { concurrency = 10, continueOnError = true } = options;
|
|
4892
|
-
if (ids.length === 0) {
|
|
4893
|
-
return { deleted: 0 };
|
|
4894
|
-
}
|
|
4895
|
-
const errors = [];
|
|
4896
|
-
let deleted = 0;
|
|
4897
|
-
const run2 = async (id) => {
|
|
4898
|
-
try {
|
|
4899
|
-
await store.delete(id);
|
|
4900
|
-
deleted++;
|
|
4901
|
-
} catch (error) {
|
|
4902
|
-
if (continueOnError) errors.push({ id, error });
|
|
4903
|
-
else throw error;
|
|
4904
|
-
}
|
|
4905
|
-
};
|
|
4906
|
-
const limit = Math.max(1, concurrency);
|
|
4907
|
-
for (let i = 0; i < ids.length; i += limit) {
|
|
4908
|
-
const batch = ids.slice(i, i + limit);
|
|
4909
|
-
await Promise.all(batch.map((id) => run2(id)));
|
|
4910
|
-
}
|
|
4911
|
-
return errors.length > 0 ? { deleted, errors } : { deleted };
|
|
4912
|
-
},
|
|
4913
|
-
/**
|
|
4914
|
-
* List workflow IDs with persisted state.
|
|
4915
|
-
*
|
|
4916
|
-
* @param store - Snapshot store
|
|
4917
|
-
* @param options - Optional prefix and limit
|
|
4918
|
-
* @returns Array of { id, updatedAt } entries
|
|
4919
|
-
*/
|
|
4920
|
-
async listPending(store, options) {
|
|
4921
|
-
try {
|
|
4922
|
-
return await store.list(options);
|
|
4923
|
-
} catch {
|
|
4924
|
-
return [];
|
|
4925
|
-
}
|
|
4926
|
-
}
|
|
4927
|
-
};
|
|
4928
|
-
|
|
4929
|
-
// src/versioning.ts
|
|
4930
|
-
function isMigrationError(error) {
|
|
4931
|
-
return typeof error === "object" && error !== null && error.type === "MIGRATION_ERROR";
|
|
4932
|
-
}
|
|
4933
|
-
function isVersionIncompatibleError(error) {
|
|
4934
|
-
return typeof error === "object" && error !== null && error.type === "VERSION_INCOMPATIBLE";
|
|
4935
|
-
}
|
|
4936
|
-
async function migrateState(state, targetVersion, migrations) {
|
|
4937
|
-
let currentState = state.state;
|
|
4938
|
-
let currentVersion = state.version;
|
|
4939
|
-
if (currentVersion > targetVersion) {
|
|
4940
|
-
return err({
|
|
4941
|
-
type: "VERSION_INCOMPATIBLE",
|
|
4942
|
-
stateVersion: currentVersion,
|
|
4943
|
-
currentVersion: targetVersion,
|
|
4944
|
-
reason: "State version is higher than current workflow version. Cannot downgrade."
|
|
4945
|
-
});
|
|
4946
|
-
}
|
|
4947
|
-
if (currentVersion === targetVersion) {
|
|
4948
|
-
return ok2({ version: currentVersion, state: currentState });
|
|
4949
|
-
}
|
|
4950
|
-
while (currentVersion < targetVersion) {
|
|
4951
|
-
const migration = migrations[currentVersion];
|
|
4952
|
-
if (!migration) {
|
|
4953
|
-
return err({
|
|
4954
|
-
type: "VERSION_INCOMPATIBLE",
|
|
4955
|
-
stateVersion: state.version,
|
|
4956
|
-
currentVersion: targetVersion,
|
|
4957
|
-
reason: `No migration found for version ${currentVersion} to ${currentVersion + 1}`
|
|
4958
|
-
});
|
|
4959
|
-
}
|
|
4960
|
-
try {
|
|
4961
|
-
currentState = await migration(currentState);
|
|
4962
|
-
currentVersion++;
|
|
4963
|
-
} catch (cause) {
|
|
4964
|
-
return err({
|
|
4965
|
-
type: "MIGRATION_ERROR",
|
|
4966
|
-
fromVersion: currentVersion,
|
|
4967
|
-
toVersion: currentVersion + 1,
|
|
4968
|
-
cause
|
|
4969
|
-
});
|
|
4970
|
-
}
|
|
4971
|
-
}
|
|
4972
|
-
return ok2({ version: currentVersion, state: currentState });
|
|
4973
|
-
}
|
|
4974
|
-
function createVersionedStateLoader(config) {
|
|
4975
|
-
const { version, migrations = {}, strictVersioning = true } = config;
|
|
4976
|
-
return async (versionedState) => {
|
|
4977
|
-
if (!versionedState) {
|
|
4978
|
-
return ok2(void 0);
|
|
4979
|
-
}
|
|
4980
|
-
if (strictVersioning && versionedState.version > version) {
|
|
4981
|
-
return err({
|
|
4982
|
-
type: "VERSION_INCOMPATIBLE",
|
|
4983
|
-
stateVersion: versionedState.version,
|
|
4984
|
-
currentVersion: version,
|
|
4985
|
-
reason: "Saved state is from a newer workflow version"
|
|
4986
|
-
});
|
|
4987
|
-
}
|
|
4988
|
-
if (versionedState.version === version) {
|
|
4989
|
-
return ok2(versionedState.state);
|
|
4990
|
-
}
|
|
4991
|
-
const result = await migrateState(versionedState, version, migrations);
|
|
4992
|
-
if (!result.ok) {
|
|
4993
|
-
return result;
|
|
4994
|
-
}
|
|
4995
|
-
return ok2(result.value.state);
|
|
4996
|
-
};
|
|
4997
|
-
}
|
|
4998
|
-
function createVersionedState(state, version) {
|
|
4999
|
-
return { version, state };
|
|
5000
|
-
}
|
|
5001
|
-
function parseVersionedState(json) {
|
|
5002
|
-
if (!json) return null;
|
|
5003
|
-
try {
|
|
5004
|
-
const parsed = typeof json === "string" ? JSON.parse(json) : json;
|
|
5005
|
-
if (typeof parsed !== "object" || parsed === null || !("version" in parsed) || typeof parsed.version !== "number" || !("state" in parsed) || !parsed.state || !Array.isArray(parsed.state.steps)) {
|
|
5006
|
-
return null;
|
|
5007
|
-
}
|
|
5008
|
-
const typedParsed = parsed;
|
|
5009
|
-
const steps = new Map(typedParsed.state.steps);
|
|
5010
|
-
return {
|
|
5011
|
-
version: typedParsed.version,
|
|
5012
|
-
state: { steps }
|
|
5013
|
-
};
|
|
5014
|
-
} catch {
|
|
5015
|
-
return null;
|
|
5016
|
-
}
|
|
5017
|
-
}
|
|
5018
|
-
function stringifyVersionedState(state) {
|
|
5019
|
-
return JSON.stringify({
|
|
5020
|
-
version: state.version,
|
|
5021
|
-
state: {
|
|
5022
|
-
steps: Array.from(state.state.steps.entries())
|
|
5023
|
-
}
|
|
5024
|
-
});
|
|
5025
|
-
}
|
|
5026
|
-
function createKeyRenameMigration(renames) {
|
|
5027
|
-
return (state) => {
|
|
5028
|
-
const newSteps = /* @__PURE__ */ new Map();
|
|
5029
|
-
for (const [key, entry] of state.steps) {
|
|
5030
|
-
const newKey = renames[key] ?? key;
|
|
5031
|
-
newSteps.set(newKey, entry);
|
|
5032
|
-
}
|
|
5033
|
-
return { steps: newSteps };
|
|
5034
|
-
};
|
|
5035
|
-
}
|
|
5036
|
-
function createKeyRemoveMigration(keysToRemove) {
|
|
5037
|
-
const keysSet = new Set(keysToRemove);
|
|
5038
|
-
return (state) => {
|
|
5039
|
-
const newSteps = /* @__PURE__ */ new Map();
|
|
5040
|
-
for (const [key, entry] of state.steps) {
|
|
5041
|
-
if (!keysSet.has(key)) {
|
|
5042
|
-
newSteps.set(key, entry);
|
|
5043
|
-
}
|
|
5044
|
-
}
|
|
5045
|
-
return { steps: newSteps };
|
|
5046
|
-
};
|
|
5047
|
-
}
|
|
5048
|
-
function createValueTransformMigration(transforms) {
|
|
5049
|
-
return (state) => {
|
|
5050
|
-
const newSteps = /* @__PURE__ */ new Map();
|
|
5051
|
-
for (const [key, entry] of state.steps) {
|
|
5052
|
-
const transform = transforms[key];
|
|
5053
|
-
newSteps.set(key, transform ? transform(entry) : entry);
|
|
5054
|
-
}
|
|
5055
|
-
return { steps: newSteps };
|
|
5056
|
-
};
|
|
5057
|
-
}
|
|
5058
|
-
function composeMigrations(migrations) {
|
|
5059
|
-
return async (state) => {
|
|
5060
|
-
let currentState = state;
|
|
5061
|
-
for (const migration of migrations) {
|
|
5062
|
-
currentState = await migration(currentState);
|
|
5063
|
-
}
|
|
5064
|
-
return currentState;
|
|
5065
|
-
};
|
|
5066
|
-
}
|
|
5067
|
-
|
|
5068
|
-
// src/hitl/index.ts
|
|
5069
|
-
function createMemoryApprovalStore() {
|
|
5070
|
-
const approvals = /* @__PURE__ */ new Map();
|
|
5071
|
-
return {
|
|
5072
|
-
async getApproval(key) {
|
|
5073
|
-
const approval = approvals.get(key);
|
|
5074
|
-
if (!approval) {
|
|
5075
|
-
return { status: "pending" };
|
|
5076
|
-
}
|
|
5077
|
-
if (approval.expiresAt && Date.now() > approval.expiresAt) {
|
|
5078
|
-
return { status: "expired", expiredAt: approval.expiresAt };
|
|
5079
|
-
}
|
|
5080
|
-
return approval;
|
|
5081
|
-
},
|
|
5082
|
-
async createApproval(key, options) {
|
|
5083
|
-
approvals.set(key, {
|
|
5084
|
-
status: "pending",
|
|
5085
|
-
metadata: options?.metadata,
|
|
5086
|
-
expiresAt: options?.expiresAt
|
|
5087
|
-
});
|
|
5088
|
-
},
|
|
5089
|
-
async grantApproval(key, value, options) {
|
|
5090
|
-
approvals.set(key, {
|
|
5091
|
-
status: "approved",
|
|
5092
|
-
value,
|
|
5093
|
-
approvedBy: options?.approvedBy,
|
|
5094
|
-
approvedAt: Date.now()
|
|
5095
|
-
});
|
|
5096
|
-
},
|
|
5097
|
-
async rejectApproval(key, reason, options) {
|
|
5098
|
-
approvals.set(key, {
|
|
5099
|
-
status: "rejected",
|
|
5100
|
-
reason,
|
|
5101
|
-
rejectedBy: options?.rejectedBy,
|
|
5102
|
-
rejectedAt: Date.now()
|
|
5103
|
-
});
|
|
5104
|
-
},
|
|
5105
|
-
async editApproval(key, originalValue, editedValue, options) {
|
|
5106
|
-
approvals.set(key, {
|
|
5107
|
-
status: "edited",
|
|
5108
|
-
originalValue,
|
|
5109
|
-
editedValue,
|
|
5110
|
-
editedBy: options?.editedBy,
|
|
5111
|
-
editedAt: Date.now()
|
|
5112
|
-
});
|
|
5113
|
-
},
|
|
5114
|
-
async cancelApproval(key) {
|
|
5115
|
-
approvals.delete(key);
|
|
5116
|
-
},
|
|
5117
|
-
async listPending(options) {
|
|
5118
|
-
const pending = [];
|
|
5119
|
-
for (const [key, approval] of approvals) {
|
|
5120
|
-
if (approval.status === "pending") {
|
|
5121
|
-
if (!options?.prefix || key.startsWith(options.prefix)) {
|
|
5122
|
-
pending.push(key);
|
|
5123
|
-
}
|
|
5124
|
-
}
|
|
5125
|
-
}
|
|
5126
|
-
return pending;
|
|
5127
|
-
}
|
|
5128
|
-
};
|
|
5129
|
-
}
|
|
5130
|
-
function createMemoryWorkflowStateStore() {
|
|
5131
|
-
const states = /* @__PURE__ */ new Map();
|
|
5132
|
-
return {
|
|
5133
|
-
async save(state) {
|
|
5134
|
-
states.set(state.runId, { ...state, updatedAt: Date.now() });
|
|
5135
|
-
},
|
|
5136
|
-
async load(runId) {
|
|
5137
|
-
return states.get(runId);
|
|
5138
|
-
},
|
|
5139
|
-
async delete(runId) {
|
|
5140
|
-
states.delete(runId);
|
|
5141
|
-
},
|
|
5142
|
-
async list(options) {
|
|
5143
|
-
const results = [];
|
|
5144
|
-
for (const [runId, state] of states) {
|
|
5145
|
-
if (options?.workflowName && state.workflowName !== options.workflowName) {
|
|
5146
|
-
continue;
|
|
5147
|
-
}
|
|
5148
|
-
if (options?.hasPendingApprovals !== void 0) {
|
|
5149
|
-
const hasPending = state.pendingApprovals.length > 0;
|
|
5150
|
-
if (options.hasPendingApprovals !== hasPending) {
|
|
5151
|
-
continue;
|
|
5152
|
-
}
|
|
5153
|
-
}
|
|
5154
|
-
results.push(runId);
|
|
5155
|
-
}
|
|
5156
|
-
return results;
|
|
5157
|
-
},
|
|
5158
|
-
async findByPendingApproval(approvalKey) {
|
|
5159
|
-
const results = [];
|
|
5160
|
-
for (const [runId, state] of states) {
|
|
5161
|
-
if (state.pendingApprovals.includes(approvalKey)) {
|
|
5162
|
-
results.push(runId);
|
|
5163
|
-
}
|
|
5164
|
-
}
|
|
5165
|
-
return results;
|
|
5166
|
-
}
|
|
5167
|
-
};
|
|
5168
|
-
}
|
|
5169
|
-
function createHITLOrchestrator(options) {
|
|
5170
|
-
const {
|
|
5171
|
-
approvalStore,
|
|
5172
|
-
workflowStateStore,
|
|
5173
|
-
defaultExpirationMs = 7 * 24 * 60 * 60 * 1e3,
|
|
5174
|
-
// 7 days
|
|
5175
|
-
logger = () => {
|
|
5176
|
-
},
|
|
5177
|
-
notificationChannel
|
|
5178
|
-
} = options;
|
|
5179
|
-
async function execute(workflowName, workflowFactory, workflowFn, input, opts) {
|
|
5180
|
-
const runId = opts?.runId ?? crypto.randomUUID();
|
|
5181
|
-
const collector = createApprovalStateCollector();
|
|
5182
|
-
const workflow = workflowFactory({
|
|
5183
|
-
onEvent: collector.handleEvent
|
|
5184
|
-
});
|
|
5185
|
-
const args = input;
|
|
5186
|
-
const result = await workflow.run(
|
|
5187
|
-
async (context) => workflowFn({ ...context, args })
|
|
5188
|
-
);
|
|
5189
|
-
const pendingApprovals = collector.getPendingApprovals().map((p) => p.stepKey);
|
|
5190
|
-
if (pendingApprovals.length > 0) {
|
|
5191
|
-
const state = {
|
|
5192
|
-
runId,
|
|
5193
|
-
workflowName,
|
|
5194
|
-
resumeState: collector.getResumeState(),
|
|
5195
|
-
pendingApprovals,
|
|
5196
|
-
input,
|
|
5197
|
-
metadata: opts?.metadata,
|
|
5198
|
-
startedAt: Date.now(),
|
|
5199
|
-
updatedAt: Date.now()
|
|
5200
|
-
};
|
|
5201
|
-
await workflowStateStore.save(state);
|
|
5202
|
-
let reason;
|
|
5203
|
-
let pendingMetadata;
|
|
5204
|
-
if (!result.ok && isPendingApproval(result.error)) {
|
|
5205
|
-
reason = result.error.reason;
|
|
5206
|
-
pendingMetadata = result.error.metadata;
|
|
5207
|
-
}
|
|
5208
|
-
const expiresAt = Date.now() + defaultExpirationMs;
|
|
5209
|
-
for (const key of pendingApprovals) {
|
|
5210
|
-
await approvalStore.createApproval(key, {
|
|
5211
|
-
metadata: { ...pendingMetadata, runId, workflowName },
|
|
5212
|
-
expiresAt
|
|
5213
|
-
});
|
|
5214
|
-
}
|
|
5215
|
-
if (notificationChannel) {
|
|
5216
|
-
for (const key of pendingApprovals) {
|
|
5217
|
-
try {
|
|
5218
|
-
const pendingInfo = collector.getPendingApprovals().find((p) => p.stepKey === key);
|
|
5219
|
-
await notificationChannel.onApprovalNeeded({
|
|
5220
|
-
approvalKey: key,
|
|
5221
|
-
runId,
|
|
5222
|
-
workflowName,
|
|
5223
|
-
reason,
|
|
5224
|
-
metadata: { ...opts?.metadata, ...pendingMetadata },
|
|
5225
|
-
expiresAt,
|
|
5226
|
-
summary: reason ?? `Approval needed for ${key}`,
|
|
5227
|
-
pendingArgs: pendingInfo?.error.metadata
|
|
5228
|
-
});
|
|
5229
|
-
} catch (notifyError) {
|
|
5230
|
-
logger(`Failed to notify for approval ${key}: ${notifyError}`);
|
|
5231
|
-
}
|
|
5232
|
-
}
|
|
5233
|
-
}
|
|
5234
|
-
logger(`Workflow ${runId} paused, waiting for: ${pendingApprovals.join(", ")}`);
|
|
5235
|
-
return {
|
|
5236
|
-
status: "paused",
|
|
5237
|
-
runId,
|
|
5238
|
-
pendingApprovals,
|
|
5239
|
-
reason
|
|
5240
|
-
};
|
|
5241
|
-
}
|
|
5242
|
-
logger(`Workflow ${runId} completed`);
|
|
5243
|
-
return { status: "completed", result };
|
|
5244
|
-
}
|
|
5245
|
-
async function resume(runId, workflowFactory, workflowFn) {
|
|
5246
|
-
const savedState = await workflowStateStore.load(runId);
|
|
5247
|
-
if (!savedState) {
|
|
5248
|
-
throw new Error(`Workflow run not found: ${runId}`);
|
|
5249
|
-
}
|
|
5250
|
-
for (const key of savedState.pendingApprovals) {
|
|
5251
|
-
const status = await approvalStore.getApproval(key);
|
|
5252
|
-
if (status.status === "pending") {
|
|
5253
|
-
return {
|
|
5254
|
-
status: "paused",
|
|
5255
|
-
runId,
|
|
5256
|
-
pendingApprovals: savedState.pendingApprovals
|
|
5257
|
-
};
|
|
5258
|
-
}
|
|
5259
|
-
if (status.status === "rejected") {
|
|
5260
|
-
}
|
|
5261
|
-
if (status.status === "expired") {
|
|
5262
|
-
throw new Error(`Approval ${key} has expired`);
|
|
5263
|
-
}
|
|
5264
|
-
}
|
|
5265
|
-
let resumeState = savedState.resumeState;
|
|
5266
|
-
for (const key of savedState.pendingApprovals) {
|
|
5267
|
-
const status = await approvalStore.getApproval(key);
|
|
5268
|
-
if (status.status === "approved") {
|
|
5269
|
-
resumeState = injectApproval(resumeState, {
|
|
5270
|
-
stepKey: key,
|
|
5271
|
-
value: status.value
|
|
5272
|
-
});
|
|
5273
|
-
} else if (status.status === "edited") {
|
|
5274
|
-
resumeState = injectApproval(resumeState, {
|
|
5275
|
-
stepKey: key,
|
|
5276
|
-
value: status.editedValue
|
|
5277
|
-
});
|
|
5278
|
-
}
|
|
5279
|
-
}
|
|
5280
|
-
const collector = createApprovalStateCollector();
|
|
5281
|
-
const workflow = workflowFactory({
|
|
5282
|
-
resumeState,
|
|
5283
|
-
onEvent: collector.handleEvent
|
|
5284
|
-
});
|
|
5285
|
-
const args = savedState.input;
|
|
5286
|
-
const result = await workflow.run(
|
|
5287
|
-
async (context) => workflowFn({ ...context, args })
|
|
5288
|
-
);
|
|
5289
|
-
const newPendingApprovals = collector.getPendingApprovals().map((p) => p.stepKey);
|
|
5290
|
-
if (newPendingApprovals.length > 0) {
|
|
5291
|
-
const updatedState = {
|
|
5292
|
-
...savedState,
|
|
5293
|
-
resumeState: collector.getResumeState(),
|
|
5294
|
-
pendingApprovals: newPendingApprovals,
|
|
5295
|
-
updatedAt: Date.now()
|
|
5296
|
-
};
|
|
5297
|
-
await workflowStateStore.save(updatedState);
|
|
5298
|
-
for (const key of newPendingApprovals) {
|
|
5299
|
-
await approvalStore.createApproval(key, {
|
|
5300
|
-
metadata: { runId, workflowName: savedState.workflowName },
|
|
5301
|
-
expiresAt: Date.now() + defaultExpirationMs
|
|
5302
|
-
});
|
|
5303
|
-
}
|
|
5304
|
-
logger(`Workflow ${runId} paused again, waiting for: ${newPendingApprovals.join(", ")}`);
|
|
5305
|
-
let reason;
|
|
5306
|
-
if (!result.ok && isPendingApproval(result.error)) {
|
|
5307
|
-
reason = result.error.reason;
|
|
5308
|
-
}
|
|
5309
|
-
return {
|
|
5310
|
-
status: "paused",
|
|
5311
|
-
runId,
|
|
5312
|
-
pendingApprovals: newPendingApprovals,
|
|
5313
|
-
reason
|
|
5314
|
-
};
|
|
5315
|
-
}
|
|
5316
|
-
await workflowStateStore.delete(runId);
|
|
5317
|
-
logger(`Workflow ${runId} resumed and completed`);
|
|
5318
|
-
return { status: "resumed", runId, result };
|
|
5319
|
-
}
|
|
5320
|
-
async function grantApprovalFn(approvalKey, value, opts) {
|
|
5321
|
-
await approvalStore.grantApproval(approvalKey, value, {
|
|
5322
|
-
approvedBy: opts?.approvedBy
|
|
5323
|
-
});
|
|
5324
|
-
const grantedAt = Date.now();
|
|
5325
|
-
const resumedWorkflows = [];
|
|
5326
|
-
if (opts?.autoResume !== false) {
|
|
5327
|
-
const waitingWorkflows = await workflowStateStore.findByPendingApproval(approvalKey);
|
|
5328
|
-
for (const runId of waitingWorkflows) {
|
|
5329
|
-
resumedWorkflows.push(runId);
|
|
5330
|
-
}
|
|
5331
|
-
}
|
|
5332
|
-
if (notificationChannel?.onApprovalResolved) {
|
|
5333
|
-
await notificationChannel.onApprovalResolved({
|
|
5334
|
-
approvalKey,
|
|
5335
|
-
action: "approved",
|
|
5336
|
-
actorId: opts?.approvedBy,
|
|
5337
|
-
resolvedAt: grantedAt,
|
|
5338
|
-
value
|
|
5339
|
-
});
|
|
5340
|
-
}
|
|
5341
|
-
logger(`Approval ${approvalKey} granted by ${opts?.approvedBy ?? "unknown"}`);
|
|
5342
|
-
return { grantedAt, resumedWorkflows };
|
|
5343
|
-
}
|
|
5344
|
-
async function rejectApprovalFn(approvalKey, reason, opts) {
|
|
5345
|
-
await approvalStore.rejectApproval(approvalKey, reason, {
|
|
5346
|
-
rejectedBy: opts?.rejectedBy
|
|
5347
|
-
});
|
|
5348
|
-
if (notificationChannel?.onApprovalResolved) {
|
|
5349
|
-
await notificationChannel.onApprovalResolved({
|
|
5350
|
-
approvalKey,
|
|
5351
|
-
action: "rejected",
|
|
5352
|
-
actorId: opts?.rejectedBy,
|
|
5353
|
-
resolvedAt: Date.now(),
|
|
5354
|
-
reason
|
|
5355
|
-
});
|
|
5356
|
-
}
|
|
5357
|
-
logger(`Approval ${approvalKey} rejected: ${reason}`);
|
|
5358
|
-
}
|
|
5359
|
-
async function editApprovalFn(approvalKey, originalValue, editedValue, opts) {
|
|
5360
|
-
await approvalStore.editApproval(approvalKey, originalValue, editedValue, {
|
|
5361
|
-
editedBy: opts?.editedBy
|
|
5362
|
-
});
|
|
5363
|
-
const editedAt = Date.now();
|
|
5364
|
-
if (notificationChannel?.onApprovalResolved) {
|
|
5365
|
-
await notificationChannel.onApprovalResolved({
|
|
5366
|
-
approvalKey,
|
|
5367
|
-
action: "edited",
|
|
5368
|
-
actorId: opts?.editedBy,
|
|
5369
|
-
resolvedAt: editedAt,
|
|
5370
|
-
value: editedValue,
|
|
5371
|
-
originalValue
|
|
5372
|
-
});
|
|
5373
|
-
}
|
|
5374
|
-
logger(`Approval ${approvalKey} edited by ${opts?.editedBy ?? "unknown"}`);
|
|
5375
|
-
return { editedAt };
|
|
5376
|
-
}
|
|
5377
|
-
async function pollApproval(approvalKey, opts) {
|
|
5378
|
-
const {
|
|
5379
|
-
intervalMs = 1e3,
|
|
5380
|
-
maxPolls,
|
|
5381
|
-
timeoutMs,
|
|
5382
|
-
onPollStart,
|
|
5383
|
-
onPollComplete
|
|
5384
|
-
} = opts ?? {};
|
|
5385
|
-
const startTime = Date.now();
|
|
5386
|
-
let pollCount = 0;
|
|
5387
|
-
while (true) {
|
|
5388
|
-
onPollStart?.();
|
|
5389
|
-
const status = await approvalStore.getApproval(approvalKey);
|
|
5390
|
-
onPollComplete?.(status);
|
|
5391
|
-
if (status.status !== "pending") {
|
|
5392
|
-
return status;
|
|
5393
|
-
}
|
|
5394
|
-
pollCount++;
|
|
5395
|
-
if (maxPolls !== void 0 && pollCount >= maxPolls) {
|
|
5396
|
-
return { status: "pending" };
|
|
5397
|
-
}
|
|
5398
|
-
if (timeoutMs !== void 0 && Date.now() - startTime >= timeoutMs) {
|
|
5399
|
-
return { status: "pending" };
|
|
5400
|
-
}
|
|
5401
|
-
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
5402
|
-
}
|
|
5403
|
-
}
|
|
5404
|
-
async function getWorkflowStatus(runId) {
|
|
5405
|
-
return workflowStateStore.load(runId);
|
|
5406
|
-
}
|
|
5407
|
-
async function listPendingWorkflows(workflowName) {
|
|
5408
|
-
return workflowStateStore.list({
|
|
5409
|
-
workflowName,
|
|
5410
|
-
hasPendingApprovals: true
|
|
5411
|
-
});
|
|
5412
|
-
}
|
|
5413
|
-
async function cleanup(maxAgeMs) {
|
|
5414
|
-
const allWorkflows = await workflowStateStore.list();
|
|
5415
|
-
let cleaned = 0;
|
|
5416
|
-
const cutoff = Date.now() - maxAgeMs;
|
|
5417
|
-
for (const runId of allWorkflows) {
|
|
5418
|
-
const state = await workflowStateStore.load(runId);
|
|
5419
|
-
if (state && state.updatedAt < cutoff && state.pendingApprovals.length === 0) {
|
|
5420
|
-
await workflowStateStore.delete(runId);
|
|
5421
|
-
cleaned++;
|
|
5422
|
-
}
|
|
5423
|
-
}
|
|
5424
|
-
logger(`Cleaned up ${cleaned} old workflow states`);
|
|
5425
|
-
return cleaned;
|
|
5426
|
-
}
|
|
5427
|
-
return {
|
|
5428
|
-
execute,
|
|
5429
|
-
resume,
|
|
5430
|
-
grantApproval: grantApprovalFn,
|
|
5431
|
-
rejectApproval: rejectApprovalFn,
|
|
5432
|
-
editApproval: editApprovalFn,
|
|
5433
|
-
pollApproval,
|
|
5434
|
-
getWorkflowStatus,
|
|
5435
|
-
listPendingWorkflows,
|
|
5436
|
-
cleanup
|
|
5437
|
-
};
|
|
5438
|
-
}
|
|
5439
|
-
function createApprovalWebhookHandler(store) {
|
|
5440
|
-
return async (request) => {
|
|
5441
|
-
const { key, action, value, originalValue, editedValue, reason, actorId } = request;
|
|
5442
|
-
try {
|
|
5443
|
-
switch (action) {
|
|
5444
|
-
case "approve":
|
|
5445
|
-
await store.grantApproval(key, value, { approvedBy: actorId });
|
|
5446
|
-
return {
|
|
5447
|
-
success: true,
|
|
5448
|
-
message: `Approval ${key} granted`,
|
|
5449
|
-
data: { key, action, timestamp: Date.now() }
|
|
5450
|
-
};
|
|
5451
|
-
case "reject":
|
|
5452
|
-
if (!reason) {
|
|
5453
|
-
return { success: false, message: "Reason is required for rejection" };
|
|
5454
|
-
}
|
|
5455
|
-
await store.rejectApproval(key, reason, { rejectedBy: actorId });
|
|
5456
|
-
return {
|
|
5457
|
-
success: true,
|
|
5458
|
-
message: `Approval ${key} rejected`,
|
|
5459
|
-
data: { key, action, timestamp: Date.now() }
|
|
5460
|
-
};
|
|
5461
|
-
case "edit":
|
|
5462
|
-
if (originalValue === void 0 || editedValue === void 0) {
|
|
5463
|
-
return { success: false, message: "Both originalValue and editedValue are required for edit" };
|
|
5464
|
-
}
|
|
5465
|
-
await store.editApproval(key, originalValue, editedValue, { editedBy: actorId });
|
|
5466
|
-
return {
|
|
5467
|
-
success: true,
|
|
5468
|
-
message: `Approval ${key} edited`,
|
|
5469
|
-
data: { key, action, timestamp: Date.now() }
|
|
5470
|
-
};
|
|
5471
|
-
case "cancel":
|
|
5472
|
-
await store.cancelApproval(key);
|
|
5473
|
-
return {
|
|
5474
|
-
success: true,
|
|
5475
|
-
message: `Approval ${key} cancelled`,
|
|
5476
|
-
data: { key, action, timestamp: Date.now() }
|
|
5477
|
-
};
|
|
5478
|
-
default:
|
|
5479
|
-
return { success: false, message: `Unknown action: ${action}` };
|
|
5480
|
-
}
|
|
5481
|
-
} catch (error) {
|
|
5482
|
-
return {
|
|
5483
|
-
success: false,
|
|
5484
|
-
message: error instanceof Error ? error.message : String(error)
|
|
5485
|
-
};
|
|
5486
|
-
}
|
|
5487
|
-
};
|
|
5488
|
-
}
|
|
5489
|
-
function createApprovalChecker(store) {
|
|
5490
|
-
return (key) => async () => {
|
|
5491
|
-
const status = await store.getApproval(key);
|
|
5492
|
-
switch (status.status) {
|
|
5493
|
-
case "pending":
|
|
5494
|
-
return { status: "pending" };
|
|
5495
|
-
case "approved":
|
|
5496
|
-
return { status: "approved", value: status.value };
|
|
5497
|
-
case "edited":
|
|
5498
|
-
return { status: "approved", value: status.editedValue };
|
|
5499
|
-
case "rejected":
|
|
5500
|
-
return { status: "rejected", reason: status.reason };
|
|
5501
|
-
case "expired":
|
|
5502
|
-
return { status: "rejected", reason: "Approval request expired" };
|
|
5503
|
-
default:
|
|
5504
|
-
return { status: "pending" };
|
|
5505
|
-
}
|
|
5506
|
-
};
|
|
5507
|
-
}
|
|
5508
|
-
|
|
5509
|
-
// src/saga.ts
|
|
5510
|
-
function isSagaCompensationError(error) {
|
|
5511
|
-
return typeof error === "object" && error !== null && error.type === "SAGA_COMPENSATION_ERROR";
|
|
5512
|
-
}
|
|
5513
|
-
function createSagaWorkflow(workflowName, deps, options) {
|
|
5514
|
-
return createWorkflow(workflowName, deps, options);
|
|
5515
|
-
}
|
|
5516
|
-
async function runSaga(fn, options) {
|
|
5517
|
-
const sagaId = crypto.randomUUID();
|
|
5518
|
-
const startTime = performance.now();
|
|
5519
|
-
const compensations = [];
|
|
5520
|
-
const emit = (e) => options?.onEvent?.(e);
|
|
5521
|
-
emit({ type: "saga_start", sagaId, ts: Date.now() });
|
|
5522
|
-
const stepFn = async (name, operation, stepOptions) => {
|
|
5523
|
-
if (typeof name !== "string" || name.length === 0) {
|
|
5524
|
-
throw new TypeError(
|
|
5525
|
-
"step(name, operation, options?): first argument must be a string."
|
|
5526
|
-
);
|
|
5527
|
-
}
|
|
5528
|
-
const result = await operation();
|
|
5529
|
-
if (result.ok) {
|
|
5530
|
-
if (stepOptions?.compensate) {
|
|
5531
|
-
compensations.push({
|
|
5532
|
-
name,
|
|
5533
|
-
value: result.value,
|
|
5534
|
-
compensate: stepOptions.compensate
|
|
5535
|
-
});
|
|
5536
|
-
}
|
|
5537
|
-
return result.value;
|
|
5538
|
-
}
|
|
5539
|
-
throw createEarlyExit(result.error, {
|
|
5540
|
-
origin: "result",
|
|
5541
|
-
resultCause: result.cause
|
|
5542
|
-
});
|
|
5543
|
-
};
|
|
5544
|
-
const stepTry = async (name, operation, opts) => {
|
|
5545
|
-
if (typeof name !== "string" || name.length === 0) {
|
|
5546
|
-
throw new TypeError(
|
|
5547
|
-
"step.try(name, operation, options): first argument must be a string."
|
|
5548
|
-
);
|
|
5549
|
-
}
|
|
5550
|
-
const mapToError = "error" in opts ? () => opts.error : opts.onError;
|
|
5551
|
-
try {
|
|
5552
|
-
const value = await operation();
|
|
5553
|
-
if (opts.compensate) {
|
|
5554
|
-
compensations.push({
|
|
5555
|
-
name,
|
|
5556
|
-
value,
|
|
5557
|
-
compensate: opts.compensate
|
|
5558
|
-
});
|
|
5559
|
-
}
|
|
5560
|
-
return value;
|
|
5561
|
-
} catch (thrown) {
|
|
5562
|
-
const mapped = mapToError(thrown);
|
|
5563
|
-
throw createEarlyExit(mapped, { origin: "throw", thrown });
|
|
5564
|
-
}
|
|
5565
|
-
};
|
|
5566
|
-
const step = Object.assign(stepFn, { try: stepTry });
|
|
5567
|
-
try {
|
|
5568
|
-
const value = await fn({ step });
|
|
5569
|
-
emit({
|
|
5570
|
-
type: "saga_success",
|
|
5571
|
-
sagaId,
|
|
5572
|
-
ts: Date.now(),
|
|
5573
|
-
durationMs: performance.now() - startTime
|
|
5574
|
-
});
|
|
5575
|
-
return ok2(value);
|
|
5576
|
-
} catch (thrown) {
|
|
5577
|
-
const durationMs = performance.now() - startTime;
|
|
5578
|
-
const originalError = isEarlyExit(thrown) ? thrown.error : thrown;
|
|
5579
|
-
emit({ type: "saga_error", sagaId, ts: Date.now(), durationMs, error: originalError });
|
|
5580
|
-
emit({
|
|
5581
|
-
type: "saga_compensation_start",
|
|
5582
|
-
sagaId,
|
|
5583
|
-
ts: Date.now(),
|
|
5584
|
-
stepCount: compensations.length
|
|
5585
|
-
});
|
|
5586
|
-
const compensationStart = performance.now();
|
|
5587
|
-
const compensationErrors = [];
|
|
5588
|
-
for (let i = compensations.length - 1; i >= 0; i--) {
|
|
5589
|
-
const comp = compensations[i];
|
|
5590
|
-
try {
|
|
5591
|
-
await comp.compensate(comp.value);
|
|
5592
|
-
emit({
|
|
5593
|
-
type: "saga_compensation_step",
|
|
5594
|
-
sagaId,
|
|
5595
|
-
stepName: comp.name,
|
|
5596
|
-
ts: Date.now(),
|
|
5597
|
-
success: true
|
|
5598
|
-
});
|
|
5599
|
-
} catch (error) {
|
|
5600
|
-
compensationErrors.push({ stepName: comp.name, error });
|
|
5601
|
-
emit({
|
|
5602
|
-
type: "saga_compensation_step",
|
|
5603
|
-
sagaId,
|
|
5604
|
-
stepName: comp.name,
|
|
5605
|
-
ts: Date.now(),
|
|
5606
|
-
success: false,
|
|
5607
|
-
error
|
|
5608
|
-
});
|
|
5609
|
-
}
|
|
5610
|
-
}
|
|
5611
|
-
emit({
|
|
5612
|
-
type: "saga_compensation_end",
|
|
5613
|
-
sagaId,
|
|
5614
|
-
ts: Date.now(),
|
|
5615
|
-
durationMs: performance.now() - compensationStart,
|
|
5616
|
-
success: compensationErrors.length === 0,
|
|
5617
|
-
failedCount: compensationErrors.length
|
|
5618
|
-
});
|
|
5619
|
-
if (compensationErrors.length > 0) {
|
|
5620
|
-
const sagaError = {
|
|
5621
|
-
type: "SAGA_COMPENSATION_ERROR",
|
|
5622
|
-
originalError,
|
|
5623
|
-
compensationErrors
|
|
5624
|
-
};
|
|
5625
|
-
options?.onError?.(sagaError);
|
|
5626
|
-
if (options?.throwOnCompensationFailure) throw sagaError;
|
|
5627
|
-
return err(sagaError);
|
|
5628
|
-
}
|
|
5629
|
-
options?.onError?.(originalError);
|
|
5630
|
-
if (!isEarlyExit(thrown)) {
|
|
5631
|
-
return err(new UnexpectedError({ cause: thrown }));
|
|
5632
|
-
}
|
|
5633
|
-
return err(originalError);
|
|
5634
|
-
}
|
|
5635
|
-
}
|
|
5636
|
-
|
|
5637
|
-
// src/streaming/stores/memory.ts
|
|
5638
|
-
function createMemoryStreamStore(options = {}) {
|
|
5639
|
-
const maxItemsPerStream = options.maxItemsPerStream ?? Infinity;
|
|
5640
|
-
const streams = /* @__PURE__ */ new Map();
|
|
5641
|
-
function getStreamKey(workflowId, namespace) {
|
|
5642
|
-
return `${workflowId}:${namespace}`;
|
|
5643
|
-
}
|
|
5644
|
-
function getOrCreateStream(workflowId, namespace) {
|
|
5645
|
-
const key = getStreamKey(workflowId, namespace);
|
|
5646
|
-
let data = streams.get(key);
|
|
5647
|
-
if (!data) {
|
|
5648
|
-
data = {
|
|
5649
|
-
metadata: {
|
|
5650
|
-
id: key,
|
|
5651
|
-
namespace,
|
|
5652
|
-
workflowId,
|
|
5653
|
-
length: 0,
|
|
5654
|
-
closed: false,
|
|
5655
|
-
createdAt: Date.now()
|
|
5656
|
-
},
|
|
5657
|
-
items: [],
|
|
5658
|
-
subscribers: /* @__PURE__ */ new Set()
|
|
5659
|
-
};
|
|
5660
|
-
streams.set(key, data);
|
|
5661
|
-
}
|
|
5662
|
-
return data;
|
|
5663
|
-
}
|
|
5664
|
-
return {
|
|
5665
|
-
async append(workflowId, namespace, item) {
|
|
5666
|
-
try {
|
|
5667
|
-
const data = getOrCreateStream(workflowId, namespace);
|
|
5668
|
-
if (data.metadata.closed) {
|
|
5669
|
-
return err(
|
|
5670
|
-
streamStoreError("write_error", "Cannot write to closed stream")
|
|
5671
|
-
);
|
|
5672
|
-
}
|
|
5673
|
-
if (data.items.length >= maxItemsPerStream) {
|
|
5674
|
-
return err(
|
|
5675
|
-
streamStoreError(
|
|
5676
|
-
"write_error",
|
|
5677
|
-
`Stream exceeded max items limit (${maxItemsPerStream})`
|
|
5678
|
-
)
|
|
5679
|
-
);
|
|
5680
|
-
}
|
|
5681
|
-
data.items.push(item);
|
|
5682
|
-
data.metadata.length = data.items.length;
|
|
5683
|
-
data.metadata.lastWriteAt = Date.now();
|
|
5684
|
-
for (const callback of data.subscribers) {
|
|
5685
|
-
try {
|
|
5686
|
-
callback(item);
|
|
5687
|
-
} catch {
|
|
5688
|
-
}
|
|
5689
|
-
}
|
|
5690
|
-
return ok2(void 0);
|
|
5691
|
-
} catch (cause) {
|
|
5692
|
-
return err(
|
|
5693
|
-
streamStoreError("write_error", "Failed to append item", cause)
|
|
5694
|
-
);
|
|
5695
|
-
}
|
|
5696
|
-
},
|
|
5697
|
-
async read(workflowId, namespace, startIndex, limit) {
|
|
5698
|
-
try {
|
|
5699
|
-
const key = getStreamKey(workflowId, namespace);
|
|
5700
|
-
const data = streams.get(key);
|
|
5701
|
-
if (!data) {
|
|
5702
|
-
return ok2([]);
|
|
5703
|
-
}
|
|
5704
|
-
const endIndex = limit !== void 0 ? Math.min(startIndex + limit, data.items.length) : data.items.length;
|
|
5705
|
-
const items = data.items.slice(startIndex, endIndex);
|
|
5706
|
-
return ok2(items);
|
|
5707
|
-
} catch (cause) {
|
|
5708
|
-
return err(streamStoreError("read_error", "Failed to read items", cause));
|
|
5709
|
-
}
|
|
5710
|
-
},
|
|
5711
|
-
async getMetadata(workflowId, namespace) {
|
|
5712
|
-
try {
|
|
5713
|
-
const key = getStreamKey(workflowId, namespace);
|
|
5714
|
-
const data = streams.get(key);
|
|
5715
|
-
return ok2(data?.metadata);
|
|
5716
|
-
} catch (cause) {
|
|
5717
|
-
return err(
|
|
5718
|
-
streamStoreError("metadata_error", "Failed to get metadata", cause)
|
|
5719
|
-
);
|
|
5720
|
-
}
|
|
5721
|
-
},
|
|
5722
|
-
async closeStream(workflowId, namespace) {
|
|
5723
|
-
try {
|
|
5724
|
-
const key = getStreamKey(workflowId, namespace);
|
|
5725
|
-
const data = streams.get(key);
|
|
5726
|
-
if (data) {
|
|
5727
|
-
data.metadata.closed = true;
|
|
5728
|
-
data.metadata.closedAt = Date.now();
|
|
5729
|
-
}
|
|
5730
|
-
return ok2(void 0);
|
|
5731
|
-
} catch (cause) {
|
|
5732
|
-
return err(
|
|
5733
|
-
streamStoreError("close_error", "Failed to close stream", cause)
|
|
5734
|
-
);
|
|
5735
|
-
}
|
|
5736
|
-
},
|
|
5737
|
-
subscribe(workflowId, namespace, callback) {
|
|
5738
|
-
const data = getOrCreateStream(workflowId, namespace);
|
|
5739
|
-
const typedCallback = callback;
|
|
5740
|
-
data.subscribers.add(typedCallback);
|
|
5741
|
-
return () => {
|
|
5742
|
-
data.subscribers.delete(typedCallback);
|
|
5743
|
-
};
|
|
5744
|
-
}
|
|
5745
|
-
};
|
|
5746
|
-
}
|
|
5747
|
-
function createTestableMemoryStreamStore(options = {}) {
|
|
5748
|
-
const streams = /* @__PURE__ */ new Map();
|
|
5749
|
-
const maxItemsPerStream = options.maxItemsPerStream ?? Infinity;
|
|
5750
|
-
function getStreamKey(workflowId, namespace) {
|
|
5751
|
-
return `${workflowId}:${namespace}`;
|
|
5752
|
-
}
|
|
5753
|
-
function getOrCreateStream(workflowId, namespace) {
|
|
5754
|
-
const key = getStreamKey(workflowId, namespace);
|
|
5755
|
-
let data = streams.get(key);
|
|
5756
|
-
if (!data) {
|
|
5757
|
-
data = {
|
|
5758
|
-
metadata: {
|
|
5759
|
-
id: key,
|
|
5760
|
-
namespace,
|
|
5761
|
-
workflowId,
|
|
5762
|
-
length: 0,
|
|
5763
|
-
closed: false,
|
|
5764
|
-
createdAt: Date.now()
|
|
5765
|
-
},
|
|
5766
|
-
items: [],
|
|
5767
|
-
subscribers: /* @__PURE__ */ new Set()
|
|
5768
|
-
};
|
|
5769
|
-
streams.set(key, data);
|
|
5770
|
-
}
|
|
5771
|
-
return data;
|
|
5772
|
-
}
|
|
5773
|
-
const store = createMemoryStreamStore(options);
|
|
5774
|
-
return {
|
|
5775
|
-
...store,
|
|
5776
|
-
// Override to use our local streams map
|
|
5777
|
-
async append(workflowId, namespace, item) {
|
|
5778
|
-
try {
|
|
5779
|
-
const data = getOrCreateStream(workflowId, namespace);
|
|
5780
|
-
if (data.metadata.closed) {
|
|
5781
|
-
return err(
|
|
5782
|
-
streamStoreError("write_error", "Cannot write to closed stream")
|
|
5783
|
-
);
|
|
5784
|
-
}
|
|
5785
|
-
if (data.items.length >= maxItemsPerStream) {
|
|
5786
|
-
return err(
|
|
5787
|
-
streamStoreError(
|
|
5788
|
-
"write_error",
|
|
5789
|
-
`Stream exceeded max items limit (${maxItemsPerStream})`
|
|
5790
|
-
)
|
|
5791
|
-
);
|
|
5792
|
-
}
|
|
5793
|
-
data.items.push(item);
|
|
5794
|
-
data.metadata.length = data.items.length;
|
|
5795
|
-
data.metadata.lastWriteAt = Date.now();
|
|
5796
|
-
for (const callback of data.subscribers) {
|
|
5797
|
-
try {
|
|
5798
|
-
callback(item);
|
|
5799
|
-
} catch {
|
|
5800
|
-
}
|
|
5801
|
-
}
|
|
5802
|
-
return ok2(void 0);
|
|
5803
|
-
} catch (cause) {
|
|
5804
|
-
return err(
|
|
5805
|
-
streamStoreError("write_error", "Failed to append item", cause)
|
|
5806
|
-
);
|
|
5807
|
-
}
|
|
5808
|
-
},
|
|
5809
|
-
async read(workflowId, namespace, startIndex, limit) {
|
|
5810
|
-
try {
|
|
5811
|
-
const key = getStreamKey(workflowId, namespace);
|
|
5812
|
-
const data = streams.get(key);
|
|
5813
|
-
if (!data) {
|
|
5814
|
-
return ok2([]);
|
|
5815
|
-
}
|
|
5816
|
-
const endIndex = limit !== void 0 ? Math.min(startIndex + limit, data.items.length) : data.items.length;
|
|
5817
|
-
const items = data.items.slice(startIndex, endIndex);
|
|
5818
|
-
return ok2(items);
|
|
5819
|
-
} catch (cause) {
|
|
5820
|
-
return err(streamStoreError("read_error", "Failed to read items", cause));
|
|
5821
|
-
}
|
|
5822
|
-
},
|
|
5823
|
-
async getMetadata(workflowId, namespace) {
|
|
5824
|
-
try {
|
|
5825
|
-
const key = getStreamKey(workflowId, namespace);
|
|
5826
|
-
const data = streams.get(key);
|
|
5827
|
-
return ok2(data?.metadata);
|
|
5828
|
-
} catch (cause) {
|
|
5829
|
-
return err(
|
|
5830
|
-
streamStoreError("metadata_error", "Failed to get metadata", cause)
|
|
5831
|
-
);
|
|
5832
|
-
}
|
|
5833
|
-
},
|
|
5834
|
-
async closeStream(workflowId, namespace) {
|
|
5835
|
-
try {
|
|
5836
|
-
const key = getStreamKey(workflowId, namespace);
|
|
5837
|
-
const data = streams.get(key);
|
|
5838
|
-
if (data) {
|
|
5839
|
-
data.metadata.closed = true;
|
|
5840
|
-
data.metadata.closedAt = Date.now();
|
|
5841
|
-
}
|
|
5842
|
-
return ok2(void 0);
|
|
5843
|
-
} catch (cause) {
|
|
5844
|
-
return err(
|
|
5845
|
-
streamStoreError("close_error", "Failed to close stream", cause)
|
|
5846
|
-
);
|
|
5847
|
-
}
|
|
5848
|
-
},
|
|
5849
|
-
subscribe(workflowId, namespace, callback) {
|
|
5850
|
-
const data = getOrCreateStream(workflowId, namespace);
|
|
5851
|
-
const typedCallback = callback;
|
|
5852
|
-
data.subscribers.add(typedCallback);
|
|
5853
|
-
return () => {
|
|
5854
|
-
data.subscribers.delete(typedCallback);
|
|
5855
|
-
};
|
|
5856
|
-
},
|
|
5857
|
-
clear() {
|
|
5858
|
-
streams.clear();
|
|
5859
|
-
},
|
|
5860
|
-
keys() {
|
|
5861
|
-
return Array.from(streams.keys());
|
|
5862
|
-
},
|
|
5863
|
-
has(workflowId, namespace) {
|
|
5864
|
-
return streams.has(getStreamKey(workflowId, namespace));
|
|
5865
|
-
},
|
|
5866
|
-
delete(workflowId, namespace) {
|
|
5867
|
-
return streams.delete(getStreamKey(workflowId, namespace));
|
|
5868
|
-
}
|
|
5869
|
-
};
|
|
5870
|
-
}
|
|
5871
|
-
|
|
5872
|
-
// src/streaming/stores/file.ts
|
|
5873
|
-
function createFileStreamStore(options) {
|
|
5874
|
-
const { directory, fs } = options;
|
|
5875
|
-
const subscribers = /* @__PURE__ */ new Map();
|
|
5876
|
-
function getStreamDir(workflowId, namespace) {
|
|
5877
|
-
return `${directory}/${workflowId}/${namespace}`;
|
|
5878
|
-
}
|
|
5879
|
-
function getMetadataPath(workflowId, namespace) {
|
|
5880
|
-
return `${getStreamDir(workflowId, namespace)}/metadata.json`;
|
|
5881
|
-
}
|
|
5882
|
-
function getItemsPath(workflowId, namespace) {
|
|
5883
|
-
return `${getStreamDir(workflowId, namespace)}/items.jsonl`;
|
|
5884
|
-
}
|
|
5885
|
-
function getSubscriberKey(workflowId, namespace) {
|
|
5886
|
-
return `${workflowId}:${namespace}`;
|
|
5887
|
-
}
|
|
5888
|
-
async function ensureDir(workflowId, namespace) {
|
|
5889
|
-
const dir = getStreamDir(workflowId, namespace);
|
|
5890
|
-
await fs.mkdir(dir, { recursive: true });
|
|
5891
|
-
}
|
|
5892
|
-
async function readMetadata(workflowId, namespace) {
|
|
5893
|
-
const metaPath = getMetadataPath(workflowId, namespace);
|
|
5894
|
-
if (!await fs.exists(metaPath)) {
|
|
5895
|
-
return void 0;
|
|
5896
|
-
}
|
|
5897
|
-
const content = await fs.readFile(metaPath);
|
|
5898
|
-
return JSON.parse(content);
|
|
5899
|
-
}
|
|
5900
|
-
async function writeMetadata(workflowId, namespace, metadata) {
|
|
5901
|
-
await ensureDir(workflowId, namespace);
|
|
5902
|
-
const metaPath = getMetadataPath(workflowId, namespace);
|
|
5903
|
-
await fs.writeFile(metaPath, JSON.stringify(metadata, null, 2));
|
|
5904
|
-
}
|
|
5905
|
-
async function appendItem(workflowId, namespace, item) {
|
|
5906
|
-
await ensureDir(workflowId, namespace);
|
|
5907
|
-
const itemsPath = getItemsPath(workflowId, namespace);
|
|
5908
|
-
const line = JSON.stringify(item) + "\n";
|
|
5909
|
-
let content = "";
|
|
5910
|
-
if (await fs.exists(itemsPath)) {
|
|
5911
|
-
content = await fs.readFile(itemsPath);
|
|
5912
|
-
}
|
|
5913
|
-
await fs.writeFile(itemsPath, content + line);
|
|
5914
|
-
}
|
|
5915
|
-
async function readItems(workflowId, namespace, startIndex, limit) {
|
|
5916
|
-
const itemsPath = getItemsPath(workflowId, namespace);
|
|
5917
|
-
if (!await fs.exists(itemsPath)) {
|
|
5918
|
-
return [];
|
|
5919
|
-
}
|
|
5920
|
-
const content = await fs.readFile(itemsPath);
|
|
5921
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
5922
|
-
const items = [];
|
|
5923
|
-
for (const line of lines) {
|
|
5924
|
-
const item = JSON.parse(line);
|
|
5925
|
-
if (item.position >= startIndex) {
|
|
5926
|
-
items.push(item);
|
|
5927
|
-
if (limit !== void 0 && items.length >= limit) {
|
|
5928
|
-
break;
|
|
5929
|
-
}
|
|
5930
|
-
}
|
|
5931
|
-
}
|
|
5932
|
-
return items;
|
|
5933
|
-
}
|
|
5934
|
-
return {
|
|
5935
|
-
async append(workflowId, namespace, item) {
|
|
5936
|
-
try {
|
|
5937
|
-
let metadata = await readMetadata(workflowId, namespace);
|
|
5938
|
-
if (!metadata) {
|
|
5939
|
-
metadata = {
|
|
5940
|
-
id: `${workflowId}:${namespace}`,
|
|
5941
|
-
namespace,
|
|
5942
|
-
workflowId,
|
|
5943
|
-
length: 0,
|
|
5944
|
-
closed: false,
|
|
5945
|
-
createdAt: Date.now()
|
|
5946
|
-
};
|
|
5947
|
-
}
|
|
5948
|
-
if (metadata.closed) {
|
|
5949
|
-
return err(
|
|
5950
|
-
streamStoreError("write_error", "Cannot write to closed stream")
|
|
5951
|
-
);
|
|
5952
|
-
}
|
|
5953
|
-
const serializedItem = {
|
|
5954
|
-
value: item.value,
|
|
5955
|
-
position: item.position,
|
|
5956
|
-
ts: item.ts
|
|
5957
|
-
};
|
|
5958
|
-
await appendItem(workflowId, namespace, serializedItem);
|
|
5959
|
-
metadata.length++;
|
|
5960
|
-
metadata.lastWriteAt = Date.now();
|
|
5961
|
-
await writeMetadata(workflowId, namespace, metadata);
|
|
5962
|
-
const key = getSubscriberKey(workflowId, namespace);
|
|
5963
|
-
const subs = subscribers.get(key);
|
|
5964
|
-
if (subs) {
|
|
5965
|
-
for (const callback of subs) {
|
|
5966
|
-
try {
|
|
5967
|
-
callback(item);
|
|
5968
|
-
} catch {
|
|
5969
|
-
}
|
|
5970
|
-
}
|
|
5971
|
-
}
|
|
5972
|
-
return ok2(void 0);
|
|
5973
|
-
} catch (cause) {
|
|
5974
|
-
return err(
|
|
5975
|
-
streamStoreError("write_error", "Failed to append item", cause)
|
|
5976
|
-
);
|
|
5977
|
-
}
|
|
5978
|
-
},
|
|
5979
|
-
async read(workflowId, namespace, startIndex, limit) {
|
|
5980
|
-
try {
|
|
5981
|
-
const serializedItems = await readItems(
|
|
5982
|
-
workflowId,
|
|
5983
|
-
namespace,
|
|
5984
|
-
startIndex,
|
|
5985
|
-
limit
|
|
5986
|
-
);
|
|
5987
|
-
const items = serializedItems.map((si) => ({
|
|
5988
|
-
value: si.value,
|
|
5989
|
-
position: si.position,
|
|
5990
|
-
ts: si.ts
|
|
5991
|
-
}));
|
|
5992
|
-
return ok2(items);
|
|
5993
|
-
} catch (cause) {
|
|
5994
|
-
return err(streamStoreError("read_error", "Failed to read items", cause));
|
|
5995
|
-
}
|
|
5996
|
-
},
|
|
5997
|
-
async getMetadata(workflowId, namespace) {
|
|
5998
|
-
try {
|
|
5999
|
-
const meta = await readMetadata(workflowId, namespace);
|
|
6000
|
-
if (!meta) {
|
|
6001
|
-
return ok2(void 0);
|
|
6002
|
-
}
|
|
6003
|
-
return ok2({
|
|
6004
|
-
id: meta.id,
|
|
6005
|
-
namespace: meta.namespace,
|
|
6006
|
-
workflowId: meta.workflowId,
|
|
6007
|
-
length: meta.length,
|
|
6008
|
-
closed: meta.closed,
|
|
6009
|
-
createdAt: meta.createdAt,
|
|
6010
|
-
lastWriteAt: meta.lastWriteAt,
|
|
6011
|
-
closedAt: meta.closedAt
|
|
6012
|
-
});
|
|
6013
|
-
} catch (cause) {
|
|
6014
|
-
return err(
|
|
6015
|
-
streamStoreError("metadata_error", "Failed to get metadata", cause)
|
|
6016
|
-
);
|
|
6017
|
-
}
|
|
6018
|
-
},
|
|
6019
|
-
async closeStream(workflowId, namespace) {
|
|
6020
|
-
try {
|
|
6021
|
-
const metadata = await readMetadata(workflowId, namespace);
|
|
6022
|
-
if (metadata) {
|
|
6023
|
-
metadata.closed = true;
|
|
6024
|
-
metadata.closedAt = Date.now();
|
|
6025
|
-
await writeMetadata(workflowId, namespace, metadata);
|
|
6026
|
-
}
|
|
6027
|
-
return ok2(void 0);
|
|
6028
|
-
} catch (cause) {
|
|
6029
|
-
return err(
|
|
6030
|
-
streamStoreError("close_error", "Failed to close stream", cause)
|
|
6031
|
-
);
|
|
6032
|
-
}
|
|
6033
|
-
},
|
|
6034
|
-
subscribe(workflowId, namespace, callback) {
|
|
6035
|
-
const key = getSubscriberKey(workflowId, namespace);
|
|
6036
|
-
let subs = subscribers.get(key);
|
|
6037
|
-
if (!subs) {
|
|
6038
|
-
subs = /* @__PURE__ */ new Set();
|
|
6039
|
-
subscribers.set(key, subs);
|
|
6040
|
-
}
|
|
6041
|
-
const typedCallback = callback;
|
|
6042
|
-
subs.add(typedCallback);
|
|
6043
|
-
return () => {
|
|
6044
|
-
subs?.delete(typedCallback);
|
|
6045
|
-
if (subs?.size === 0) {
|
|
6046
|
-
subscribers.delete(key);
|
|
6047
|
-
}
|
|
6048
|
-
};
|
|
6049
|
-
}
|
|
6050
|
-
};
|
|
6051
|
-
}
|
|
6052
|
-
|
|
6053
|
-
// src/streaming/transformers.ts
|
|
6054
|
-
async function* toAsyncIterable(reader) {
|
|
6055
|
-
while (reader.readable) {
|
|
6056
|
-
const result = await reader.read();
|
|
6057
|
-
if (!result.ok) {
|
|
6058
|
-
if (isStreamEnded(result.error)) {
|
|
6059
|
-
break;
|
|
6060
|
-
}
|
|
6061
|
-
throw result.error;
|
|
6062
|
-
}
|
|
6063
|
-
yield result.value;
|
|
6064
|
-
}
|
|
6065
|
-
}
|
|
6066
|
-
async function* map(source, fn) {
|
|
6067
|
-
const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6068
|
-
let index = 0;
|
|
6069
|
-
for await (const item of iterable) {
|
|
6070
|
-
yield await fn(item, index++);
|
|
6071
|
-
}
|
|
6072
|
-
}
|
|
6073
|
-
async function* filter(source, predicate) {
|
|
6074
|
-
const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6075
|
-
let index = 0;
|
|
6076
|
-
for await (const item of iterable) {
|
|
6077
|
-
if (await predicate(item, index++)) {
|
|
6078
|
-
yield item;
|
|
6079
|
-
}
|
|
6080
|
-
}
|
|
6081
|
-
}
|
|
6082
|
-
async function* chunk(source, size) {
|
|
6083
|
-
if (size < 1) {
|
|
6084
|
-
throw new Error("Chunk size must be at least 1");
|
|
6085
|
-
}
|
|
6086
|
-
const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6087
|
-
let currentChunk = [];
|
|
6088
|
-
for await (const item of iterable) {
|
|
6089
|
-
currentChunk.push(item);
|
|
6090
|
-
if (currentChunk.length >= size) {
|
|
6091
|
-
yield currentChunk;
|
|
6092
|
-
currentChunk = [];
|
|
6093
|
-
}
|
|
6094
|
-
}
|
|
6095
|
-
if (currentChunk.length > 0) {
|
|
6096
|
-
yield currentChunk;
|
|
6097
|
-
}
|
|
6098
|
-
}
|
|
6099
|
-
async function* flatMap(source, fn) {
|
|
6100
|
-
const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6101
|
-
let index = 0;
|
|
6102
|
-
for await (const item of iterable) {
|
|
6103
|
-
const mapped = fn(item, index++);
|
|
6104
|
-
if (Symbol.asyncIterator in mapped) {
|
|
6105
|
-
for await (const subItem of mapped) {
|
|
6106
|
-
yield subItem;
|
|
6107
|
-
}
|
|
6108
|
-
} else {
|
|
6109
|
-
for (const subItem of mapped) {
|
|
6110
|
-
yield subItem;
|
|
6111
|
-
}
|
|
6112
|
-
}
|
|
6113
|
-
}
|
|
6114
|
-
}
|
|
6115
|
-
async function* flatMapAsync(source, fn) {
|
|
6116
|
-
const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6117
|
-
let index = 0;
|
|
6118
|
-
for await (const item of iterable) {
|
|
6119
|
-
const result = await fn(item, index++);
|
|
6120
|
-
if (!result.ok) {
|
|
6121
|
-
yield err(result.error);
|
|
6122
|
-
return;
|
|
6123
|
-
}
|
|
6124
|
-
const mapped = result.value;
|
|
6125
|
-
if (Symbol.asyncIterator in mapped) {
|
|
6126
|
-
for await (const subItem of mapped) {
|
|
6127
|
-
yield ok2(subItem);
|
|
6128
|
-
}
|
|
6129
|
-
} else {
|
|
6130
|
-
for (const subItem of mapped) {
|
|
6131
|
-
yield ok2(subItem);
|
|
6132
|
-
}
|
|
6133
|
-
}
|
|
6134
|
-
}
|
|
6135
|
-
}
|
|
6136
|
-
async function* mapAsync(source, fn) {
|
|
6137
|
-
const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6138
|
-
let index = 0;
|
|
6139
|
-
for await (const item of iterable) {
|
|
6140
|
-
yield await fn(item, index++);
|
|
6141
|
-
}
|
|
6142
|
-
}
|
|
6143
|
-
async function* take(source, count) {
|
|
6144
|
-
if (count <= 0) return;
|
|
6145
|
-
const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6146
|
-
let taken = 0;
|
|
6147
|
-
for await (const item of iterable) {
|
|
6148
|
-
yield item;
|
|
6149
|
-
taken++;
|
|
6150
|
-
if (taken >= count) break;
|
|
6151
|
-
}
|
|
6152
|
-
}
|
|
6153
|
-
async function* skip(source, count) {
|
|
6154
|
-
const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6155
|
-
let skipped = 0;
|
|
6156
|
-
for await (const item of iterable) {
|
|
6157
|
-
if (skipped < count) {
|
|
6158
|
-
skipped++;
|
|
6159
|
-
continue;
|
|
6160
|
-
}
|
|
6161
|
-
yield item;
|
|
6162
|
-
}
|
|
6163
|
-
}
|
|
6164
|
-
async function* takeWhile(source, predicate) {
|
|
6165
|
-
const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6166
|
-
let index = 0;
|
|
6167
|
-
for await (const item of iterable) {
|
|
6168
|
-
if (!await predicate(item, index++)) break;
|
|
6169
|
-
yield item;
|
|
6170
|
-
}
|
|
6171
|
-
}
|
|
6172
|
-
async function* skipWhile(source, predicate) {
|
|
6173
|
-
const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6174
|
-
let skipping = true;
|
|
6175
|
-
let index = 0;
|
|
6176
|
-
for await (const item of iterable) {
|
|
6177
|
-
if (skipping && await predicate(item, index++)) {
|
|
6178
|
-
continue;
|
|
6179
|
-
}
|
|
6180
|
-
skipping = false;
|
|
6181
|
-
yield item;
|
|
6182
|
-
}
|
|
6183
|
-
}
|
|
6184
|
-
async function collect(source) {
|
|
6185
|
-
const items = [];
|
|
6186
|
-
const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6187
|
-
for await (const item of iterable) {
|
|
6188
|
-
items.push(item);
|
|
6189
|
-
}
|
|
6190
|
-
return items;
|
|
6191
|
-
}
|
|
6192
|
-
async function reduce(source, reducer, initial) {
|
|
6193
|
-
let accumulator = initial;
|
|
6194
|
-
const iterable = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6195
|
-
let index = 0;
|
|
6196
|
-
for await (const item of iterable) {
|
|
6197
|
-
accumulator = await reducer(accumulator, item, index++);
|
|
6198
|
-
}
|
|
6199
|
-
return accumulator;
|
|
6200
|
-
}
|
|
6201
|
-
function isStreamReader(value) {
|
|
6202
|
-
return typeof value === "object" && value !== null && "read" in value && typeof value.read === "function" && "readable" in value;
|
|
6203
|
-
}
|
|
6204
|
-
function pipe(source, ...transformers) {
|
|
6205
|
-
let result = isStreamReader(source) ? toAsyncIterable(source) : source;
|
|
6206
|
-
for (const transformer of transformers) {
|
|
6207
|
-
result = transformer(result);
|
|
6208
|
-
}
|
|
6209
|
-
return result;
|
|
6210
|
-
}
|
|
6211
|
-
|
|
6212
|
-
// src/streaming/index.ts
|
|
6213
|
-
function getStreamReader(options) {
|
|
6214
|
-
const {
|
|
6215
|
-
store,
|
|
6216
|
-
workflowId,
|
|
6217
|
-
namespace = "default",
|
|
6218
|
-
startIndex = 0,
|
|
6219
|
-
pollInterval = 100,
|
|
6220
|
-
pollTimeout = 3e4
|
|
6221
|
-
} = options;
|
|
6222
|
-
let position = startIndex;
|
|
6223
|
-
let readable = true;
|
|
6224
|
-
let closed = false;
|
|
6225
|
-
let bufferedItems = [];
|
|
6226
|
-
let bufferIndex = 0;
|
|
6227
|
-
const reader = {
|
|
6228
|
-
async read() {
|
|
6229
|
-
if (closed) {
|
|
6230
|
-
return err(streamReadError("closed", "Reader is closed"));
|
|
6231
|
-
}
|
|
6232
|
-
if (bufferIndex < bufferedItems.length) {
|
|
6233
|
-
const item = bufferedItems[bufferIndex++];
|
|
6234
|
-
position = item.position + 1;
|
|
6235
|
-
return ok2(item.value);
|
|
6236
|
-
}
|
|
6237
|
-
const pollStart = Date.now();
|
|
6238
|
-
while (Date.now() - pollStart < pollTimeout) {
|
|
6239
|
-
const result = await store.read(workflowId, namespace, position, 100);
|
|
6240
|
-
if (!result.ok) {
|
|
6241
|
-
return err(streamReadError("store_error", result.error.message, result.error));
|
|
6242
|
-
}
|
|
6243
|
-
const items = result.value;
|
|
6244
|
-
if (items.length > 0) {
|
|
6245
|
-
bufferedItems = items;
|
|
6246
|
-
bufferIndex = 1;
|
|
6247
|
-
const item = items[0];
|
|
6248
|
-
position = item.position + 1;
|
|
6249
|
-
return ok2(item.value);
|
|
6250
|
-
}
|
|
6251
|
-
const metaResult = await store.getMetadata(workflowId, namespace);
|
|
6252
|
-
if (metaResult.ok && metaResult.value?.closed) {
|
|
6253
|
-
readable = false;
|
|
6254
|
-
return err(streamEnded(position));
|
|
6255
|
-
}
|
|
6256
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
6257
|
-
}
|
|
6258
|
-
readable = false;
|
|
6259
|
-
return err(streamEnded(position));
|
|
6260
|
-
},
|
|
6261
|
-
close() {
|
|
6262
|
-
closed = true;
|
|
6263
|
-
readable = false;
|
|
6264
|
-
bufferedItems = [];
|
|
6265
|
-
},
|
|
6266
|
-
get readable() {
|
|
6267
|
-
return readable;
|
|
6268
|
-
},
|
|
6269
|
-
get position() {
|
|
6270
|
-
return position;
|
|
6271
|
-
},
|
|
6272
|
-
get namespace() {
|
|
6273
|
-
return namespace;
|
|
6274
|
-
}
|
|
6275
|
-
};
|
|
6276
|
-
return reader;
|
|
6277
|
-
}
|
|
6278
|
-
|
|
6279
|
-
// src/webhook/index.ts
|
|
6280
|
-
function isValidationError(e) {
|
|
6281
|
-
return typeof e === "object" && e !== null && e.type === "VALIDATION_ERROR";
|
|
6282
|
-
}
|
|
6283
|
-
function defaultValidationErrorMapper(error) {
|
|
6284
|
-
return {
|
|
6285
|
-
status: 400,
|
|
6286
|
-
body: {
|
|
6287
|
-
error: {
|
|
6288
|
-
type: error.type,
|
|
6289
|
-
message: error.message,
|
|
6290
|
-
details: error.field ? { field: error.field } : error.details
|
|
6291
|
-
}
|
|
6292
|
-
}
|
|
6293
|
-
};
|
|
6294
|
-
}
|
|
6295
|
-
function defaultUnexpectedErrorMapper(error) {
|
|
6296
|
-
return {
|
|
6297
|
-
status: 500,
|
|
6298
|
-
body: {
|
|
6299
|
-
error: {
|
|
6300
|
-
type: "INTERNAL_ERROR",
|
|
6301
|
-
message: error instanceof Error ? error.message : "An unexpected error occurred"
|
|
6302
|
-
}
|
|
6303
|
-
}
|
|
6304
|
-
};
|
|
6305
|
-
}
|
|
6306
|
-
function createWebhookHandler(workflow, workflowFn, config) {
|
|
6307
|
-
const {
|
|
6308
|
-
validateInput: validateInput2,
|
|
6309
|
-
mapResult,
|
|
6310
|
-
mapValidationError = defaultValidationErrorMapper,
|
|
6311
|
-
mapUnexpectedError = defaultUnexpectedErrorMapper,
|
|
6312
|
-
beforeValidation,
|
|
6313
|
-
afterResponse
|
|
6314
|
-
} = config;
|
|
6315
|
-
return async (req) => {
|
|
6316
|
-
try {
|
|
6317
|
-
const processedReq = beforeValidation ? await beforeValidation(req) : req;
|
|
6318
|
-
const validationResult = await validateInput2(processedReq);
|
|
6319
|
-
if (!isOk(validationResult)) {
|
|
6320
|
-
const response2 = mapValidationError(validationResult.error, processedReq);
|
|
6321
|
-
return afterResponse ? await afterResponse(response2, processedReq) : response2;
|
|
6322
|
-
}
|
|
6323
|
-
const args = validationResult.value;
|
|
6324
|
-
const workflowResult = await workflow.run(
|
|
6325
|
-
async ({ step, deps }) => workflowFn({ step, deps, args })
|
|
6326
|
-
);
|
|
6327
|
-
const response = mapResult(workflowResult, processedReq);
|
|
6328
|
-
return afterResponse ? await afterResponse(response, processedReq) : response;
|
|
6329
|
-
} catch (error) {
|
|
6330
|
-
const response = mapUnexpectedError(error, req);
|
|
6331
|
-
return afterResponse ? await afterResponse(response, req) : response;
|
|
6332
|
-
}
|
|
6333
|
-
};
|
|
6334
|
-
}
|
|
6335
|
-
function createSimpleHandler(config) {
|
|
6336
|
-
const {
|
|
6337
|
-
validateInput: validateInput2,
|
|
6338
|
-
handler,
|
|
6339
|
-
mapResult,
|
|
6340
|
-
mapValidationError = defaultValidationErrorMapper,
|
|
6341
|
-
mapUnexpectedError = defaultUnexpectedErrorMapper
|
|
6342
|
-
} = config;
|
|
6343
|
-
return async (req) => {
|
|
6344
|
-
try {
|
|
6345
|
-
const validationResult = await validateInput2(req);
|
|
6346
|
-
if (!isOk(validationResult)) {
|
|
6347
|
-
return mapValidationError(validationResult.error, req);
|
|
6348
|
-
}
|
|
6349
|
-
const result = await handler(validationResult.value, req);
|
|
6350
|
-
return mapResult(result, req);
|
|
6351
|
-
} catch (error) {
|
|
6352
|
-
return mapUnexpectedError(error, req);
|
|
6353
|
-
}
|
|
6354
|
-
};
|
|
6355
|
-
}
|
|
6356
|
-
function createResultMapper(mappings, options = {}) {
|
|
6357
|
-
const { defaultStatus = 500, successStatus = 200 } = options;
|
|
6358
|
-
const errorMap = /* @__PURE__ */ new Map();
|
|
6359
|
-
for (const mapping of mappings) {
|
|
6360
|
-
errorMap.set(mapping.error, mapping);
|
|
6361
|
-
}
|
|
6362
|
-
return (result) => {
|
|
6363
|
-
if (result.ok) {
|
|
6364
|
-
return {
|
|
6365
|
-
status: successStatus,
|
|
6366
|
-
body: result.value
|
|
6367
|
-
};
|
|
6368
|
-
}
|
|
6369
|
-
if (isUnexpectedError(result.error)) {
|
|
6370
|
-
return {
|
|
6371
|
-
status: 500,
|
|
6372
|
-
body: {
|
|
6373
|
-
error: {
|
|
6374
|
-
type: "INTERNAL_ERROR",
|
|
6375
|
-
message: result.error instanceof Error ? result.error.message : "An unexpected error occurred"
|
|
6376
|
-
}
|
|
6377
|
-
}
|
|
6378
|
-
};
|
|
6379
|
-
}
|
|
6380
|
-
const mapping = errorMap.get(result.error);
|
|
6381
|
-
if (mapping) {
|
|
6382
|
-
return {
|
|
6383
|
-
status: mapping.status,
|
|
6384
|
-
body: {
|
|
6385
|
-
error: {
|
|
6386
|
-
type: String(mapping.error),
|
|
6387
|
-
message: mapping.message
|
|
6388
|
-
}
|
|
6389
|
-
}
|
|
6390
|
-
};
|
|
6391
|
-
}
|
|
6392
|
-
return {
|
|
6393
|
-
status: defaultStatus,
|
|
6394
|
-
body: {
|
|
6395
|
-
error: {
|
|
6396
|
-
type: String(result.error)
|
|
6397
|
-
}
|
|
6398
|
-
}
|
|
6399
|
-
};
|
|
6400
|
-
};
|
|
6401
|
-
}
|
|
6402
|
-
function toWebhookRequest(req) {
|
|
6403
|
-
return {
|
|
6404
|
-
method: req.method,
|
|
6405
|
-
path: req.path,
|
|
6406
|
-
headers: req.headers,
|
|
6407
|
-
body: req.body,
|
|
6408
|
-
query: req.query,
|
|
6409
|
-
params: req.params,
|
|
6410
|
-
raw: req
|
|
6411
|
-
};
|
|
6412
|
-
}
|
|
6413
|
-
function sendWebhookResponse(res, response) {
|
|
6414
|
-
if (response.headers) {
|
|
6415
|
-
res.set(response.headers);
|
|
6416
|
-
}
|
|
6417
|
-
res.status(response.status).json(response.body);
|
|
6418
|
-
}
|
|
6419
|
-
function createExpressHandler(handler) {
|
|
6420
|
-
return async (req, res) => {
|
|
6421
|
-
const webhookReq = toWebhookRequest(req);
|
|
6422
|
-
const response = await handler(webhookReq);
|
|
6423
|
-
sendWebhookResponse(res, response);
|
|
6424
|
-
};
|
|
6425
|
-
}
|
|
6426
|
-
function validationError(message, field, details) {
|
|
6427
|
-
return {
|
|
6428
|
-
type: "VALIDATION_ERROR",
|
|
6429
|
-
message,
|
|
6430
|
-
field,
|
|
6431
|
-
details
|
|
6432
|
-
};
|
|
6433
|
-
}
|
|
6434
|
-
function requireFields(fields) {
|
|
6435
|
-
return (body) => {
|
|
6436
|
-
for (const field of fields) {
|
|
6437
|
-
if (body[field] === void 0 || body[field] === null || body[field] === "") {
|
|
6438
|
-
return err(validationError(`Missing required field: ${field}`, field));
|
|
6439
|
-
}
|
|
6440
|
-
}
|
|
6441
|
-
return ok2(void 0);
|
|
6442
|
-
};
|
|
6443
|
-
}
|
|
6444
|
-
function composeValidators(...validators) {
|
|
6445
|
-
return (input) => {
|
|
6446
|
-
for (const validator of validators) {
|
|
6447
|
-
const result = validator(input);
|
|
6448
|
-
if (!result.ok) return result;
|
|
6449
|
-
}
|
|
6450
|
-
return ok2(void 0);
|
|
6451
|
-
};
|
|
6452
|
-
}
|
|
6453
|
-
function createEventHandler(workflow, workflowFn, config) {
|
|
6454
|
-
const { validatePayload, mapResult } = config;
|
|
6455
|
-
return async (event) => {
|
|
6456
|
-
try {
|
|
6457
|
-
const validationResult = validatePayload(event);
|
|
6458
|
-
if (!isOk(validationResult)) {
|
|
6459
|
-
return {
|
|
6460
|
-
success: false,
|
|
6461
|
-
ack: true,
|
|
6462
|
-
// Don't retry validation errors
|
|
6463
|
-
error: {
|
|
6464
|
-
type: validationResult.error.type,
|
|
6465
|
-
message: validationResult.error.message,
|
|
6466
|
-
retryable: false
|
|
6467
|
-
}
|
|
6468
|
-
};
|
|
6469
|
-
}
|
|
6470
|
-
const args = validationResult.value;
|
|
6471
|
-
const workflowResult = await workflow.run(
|
|
6472
|
-
async ({ step, deps }) => workflowFn({ step, deps, args })
|
|
6473
|
-
);
|
|
6474
|
-
return mapResult(workflowResult, event);
|
|
6475
|
-
} catch (error) {
|
|
6476
|
-
return {
|
|
6477
|
-
success: false,
|
|
6478
|
-
ack: false,
|
|
6479
|
-
// Retry unexpected errors
|
|
6480
|
-
error: {
|
|
6481
|
-
type: "UnexpectedError",
|
|
6482
|
-
message: error instanceof Error ? error.message : String(error),
|
|
6483
|
-
retryable: true
|
|
6484
|
-
}
|
|
6485
|
-
};
|
|
6486
|
-
}
|
|
6487
|
-
};
|
|
6488
|
-
}
|
|
6489
|
-
|
|
6490
|
-
// src/engine/index.ts
|
|
6491
|
-
var import_node_crypto = require("crypto");
|
|
6492
|
-
function createEngine(options) {
|
|
6493
|
-
const {
|
|
6494
|
-
store,
|
|
6495
|
-
workflows,
|
|
6496
|
-
concurrency = 5,
|
|
6497
|
-
onEvent,
|
|
6498
|
-
onError
|
|
6499
|
-
} = options;
|
|
6500
|
-
const schedules = /* @__PURE__ */ new Map();
|
|
6501
|
-
let pollTimer;
|
|
6502
|
-
let running = false;
|
|
6503
|
-
let tickInFlight = false;
|
|
6504
|
-
function emit(event) {
|
|
6505
|
-
try {
|
|
6506
|
-
onEvent?.(event);
|
|
6507
|
-
} catch {
|
|
6508
|
-
}
|
|
6509
|
-
}
|
|
6510
|
-
async function enqueue(name, opts) {
|
|
6511
|
-
const wf = workflows[name];
|
|
6512
|
-
if (!wf) {
|
|
6513
|
-
throw new Error(`Unknown workflow: '${name}'. Registered: ${Object.keys(workflows).join(", ")}`);
|
|
6514
|
-
}
|
|
6515
|
-
const id = opts?.id ?? `${name}:${(0, import_node_crypto.randomUUID)()}`;
|
|
6516
|
-
const snapshot = {
|
|
6517
|
-
formatVersion: 1,
|
|
6518
|
-
workflowName: name,
|
|
6519
|
-
steps: {},
|
|
6520
|
-
execution: {
|
|
6521
|
-
status: "running",
|
|
6522
|
-
// "running" so durable.run can resume it
|
|
6523
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
6524
|
-
},
|
|
6525
|
-
metadata: {
|
|
6526
|
-
engineState: "queued",
|
|
6527
|
-
workflowName: name,
|
|
6528
|
-
input: opts?.input ?? null,
|
|
6529
|
-
idempotencyKey: opts?.idempotencyKey,
|
|
6530
|
-
enqueuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6531
|
-
...opts?.metadata
|
|
6532
|
-
}
|
|
6533
|
-
};
|
|
6534
|
-
await store.save(id, snapshot);
|
|
6535
|
-
emit({ type: "workflow_enqueued", workflowName: name, id, ts: Date.now() });
|
|
6536
|
-
return id;
|
|
6537
|
-
}
|
|
6538
|
-
async function tick() {
|
|
6539
|
-
if (tickInFlight) return 0;
|
|
6540
|
-
tickInFlight = true;
|
|
6541
|
-
try {
|
|
6542
|
-
const entries = await store.list({ limit: concurrency * 2 });
|
|
6543
|
-
let processed = 0;
|
|
6544
|
-
const queued = [];
|
|
6545
|
-
for (const entry of entries) {
|
|
6546
|
-
if (queued.length >= concurrency) break;
|
|
6547
|
-
const snapshot = await store.load(entry.id);
|
|
6548
|
-
if (snapshot && snapshot.metadata?.engineState === "queued" && typeof snapshot.metadata?.workflowName === "string") {
|
|
6549
|
-
queued.push({ id: entry.id, workflowName: snapshot.metadata.workflowName, snapshot });
|
|
6550
|
-
}
|
|
6551
|
-
}
|
|
6552
|
-
const results = await Promise.allSettled(
|
|
6553
|
-
queued.map(async ({ id, workflowName, snapshot }) => {
|
|
6554
|
-
const wf = workflows[workflowName];
|
|
6555
|
-
if (!wf) return;
|
|
6556
|
-
const processingSnapshot = {
|
|
6557
|
-
...snapshot,
|
|
6558
|
-
metadata: {
|
|
6559
|
-
...snapshot.metadata,
|
|
6560
|
-
engineState: "processing"
|
|
6561
|
-
}
|
|
6562
|
-
};
|
|
6563
|
-
await store.save(id, processingSnapshot);
|
|
6564
|
-
emit({ type: "workflow_started", workflowName, id, ts: Date.now() });
|
|
6565
|
-
const result = await durable.run(wf.deps, wf.fn, {
|
|
6566
|
-
id,
|
|
6567
|
-
store,
|
|
6568
|
-
idempotencyKey: snapshot.metadata?.idempotencyKey,
|
|
6569
|
-
input: snapshot.metadata?.input,
|
|
6570
|
-
...wf.durableDefaults
|
|
6571
|
-
});
|
|
6572
|
-
if (result.ok) {
|
|
6573
|
-
emit({ type: "workflow_completed", workflowName, id, ts: Date.now() });
|
|
6574
|
-
} else {
|
|
6575
|
-
emit({ type: "workflow_failed", workflowName, id, error: result.error, ts: Date.now() });
|
|
6576
|
-
}
|
|
6577
|
-
processed++;
|
|
6578
|
-
})
|
|
6579
|
-
);
|
|
6580
|
-
for (const r of results) {
|
|
6581
|
-
if (r.status === "rejected") {
|
|
6582
|
-
try {
|
|
6583
|
-
onError?.(r.reason);
|
|
6584
|
-
} catch {
|
|
6585
|
-
}
|
|
6586
|
-
}
|
|
6587
|
-
}
|
|
6588
|
-
emit({ type: "engine_tick", ts: Date.now(), processed });
|
|
6589
|
-
return processed;
|
|
6590
|
-
} finally {
|
|
6591
|
-
tickInFlight = false;
|
|
6592
|
-
}
|
|
6593
|
-
}
|
|
6594
|
-
function schedule(name, opts) {
|
|
6595
|
-
const scheduleId = opts?.id ? `schedule:${opts.id}` : `schedule:${(0, import_node_crypto.randomUUID)()}`;
|
|
6596
|
-
if (opts.immediate) {
|
|
6597
|
-
void enqueue(name, opts).catch((e) => {
|
|
6598
|
-
try {
|
|
6599
|
-
onError?.(e);
|
|
6600
|
-
} catch {
|
|
6601
|
-
}
|
|
6602
|
-
});
|
|
6603
|
-
}
|
|
6604
|
-
const interval = setInterval(() => {
|
|
6605
|
-
void enqueue(name, opts).catch((e) => {
|
|
6606
|
-
try {
|
|
6607
|
-
onError?.(e);
|
|
6608
|
-
} catch {
|
|
6609
|
-
}
|
|
6610
|
-
});
|
|
6611
|
-
}, opts.intervalMs);
|
|
6612
|
-
schedules.set(scheduleId, interval);
|
|
6613
|
-
emit({ type: "schedule_created", workflowName: name, scheduleId, intervalMs: opts.intervalMs, ts: Date.now() });
|
|
6614
|
-
return scheduleId;
|
|
6615
|
-
}
|
|
6616
|
-
function unschedule(scheduleId) {
|
|
6617
|
-
const interval = schedules.get(scheduleId);
|
|
6618
|
-
if (!interval) return false;
|
|
6619
|
-
clearInterval(interval);
|
|
6620
|
-
schedules.delete(scheduleId);
|
|
6621
|
-
emit({ type: "schedule_removed", scheduleId, ts: Date.now() });
|
|
6622
|
-
return true;
|
|
6623
|
-
}
|
|
6624
|
-
function start(pollIntervalMs = 1e3) {
|
|
6625
|
-
if (running) return;
|
|
6626
|
-
running = true;
|
|
6627
|
-
emit({ type: "engine_start", ts: Date.now() });
|
|
6628
|
-
void tick().catch((e) => {
|
|
6629
|
-
try {
|
|
6630
|
-
onError?.(e);
|
|
6631
|
-
} catch {
|
|
6632
|
-
}
|
|
6633
|
-
});
|
|
6634
|
-
pollTimer = setInterval(() => {
|
|
6635
|
-
void tick().catch((e) => {
|
|
6636
|
-
try {
|
|
6637
|
-
onError?.(e);
|
|
6638
|
-
} catch {
|
|
6639
|
-
}
|
|
6640
|
-
});
|
|
6641
|
-
}, pollIntervalMs);
|
|
6642
|
-
}
|
|
6643
|
-
async function stop() {
|
|
6644
|
-
if (!running) return;
|
|
6645
|
-
running = false;
|
|
6646
|
-
if (pollTimer) {
|
|
6647
|
-
clearInterval(pollTimer);
|
|
6648
|
-
pollTimer = void 0;
|
|
6649
|
-
}
|
|
6650
|
-
for (const [id, interval] of schedules) {
|
|
6651
|
-
clearInterval(interval);
|
|
6652
|
-
schedules.delete(id);
|
|
6653
|
-
}
|
|
6654
|
-
while (tickInFlight) {
|
|
6655
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
6656
|
-
}
|
|
6657
|
-
emit({ type: "engine_stop", ts: Date.now() });
|
|
6658
|
-
}
|
|
6659
|
-
return {
|
|
6660
|
-
enqueue,
|
|
6661
|
-
schedule,
|
|
6662
|
-
unschedule,
|
|
6663
|
-
start,
|
|
6664
|
-
stop,
|
|
6665
|
-
tick,
|
|
6666
|
-
status: () => ({ running, pendingSchedules: schedules.size })
|
|
6667
|
-
};
|
|
6668
|
-
}
|
|
6669
|
-
|
|
6670
4137
|
// src/resource.ts
|
|
6671
4138
|
function isResourceCleanupError(error) {
|
|
6672
4139
|
return typeof error === "object" && error !== null && error.type === "RESOURCE_CLEANUP_ERROR";
|
|
@@ -6959,66 +4426,26 @@ var batchPresets = {
|
|
|
6959
4426
|
Duration,
|
|
6960
4427
|
HOOK_STEP_KEY_PREFIX,
|
|
6961
4428
|
STEP_TIMEOUT_MARKER,
|
|
6962
|
-
STREAM_BACKPRESSURE_ERROR,
|
|
6963
|
-
STREAM_CLOSE_ERROR,
|
|
6964
|
-
STREAM_ENDED,
|
|
6965
|
-
STREAM_READ_ERROR,
|
|
6966
|
-
STREAM_STORE_ERROR,
|
|
6967
|
-
STREAM_WRITE_ERROR,
|
|
6968
4429
|
SnapshotDecodeError,
|
|
6969
4430
|
SnapshotFormatError,
|
|
6970
4431
|
SnapshotMismatchError,
|
|
6971
4432
|
UnexpectedError,
|
|
6972
4433
|
assertValidSnapshot,
|
|
6973
4434
|
batchPresets,
|
|
6974
|
-
chunk,
|
|
6975
4435
|
clearStep,
|
|
6976
|
-
collect,
|
|
6977
|
-
composeMigrations,
|
|
6978
|
-
composeValidators,
|
|
6979
|
-
createApprovalChecker,
|
|
6980
4436
|
createApprovalStateCollector,
|
|
6981
4437
|
createApprovalStep,
|
|
6982
|
-
createApprovalWebhookHandler,
|
|
6983
|
-
createBackpressureController,
|
|
6984
|
-
createEngine,
|
|
6985
|
-
createEventHandler,
|
|
6986
|
-
createExpressHandler,
|
|
6987
|
-
createFileStreamStore,
|
|
6988
|
-
createHITLOrchestrator,
|
|
6989
4438
|
createHook,
|
|
6990
|
-
createKeyRemoveMigration,
|
|
6991
|
-
createKeyRenameMigration,
|
|
6992
|
-
createMemoryApprovalStore,
|
|
6993
|
-
createMemoryCache,
|
|
6994
|
-
createMemoryStreamStore,
|
|
6995
|
-
createMemoryWorkflowStateStore,
|
|
6996
4439
|
createResource,
|
|
6997
4440
|
createResourceScope,
|
|
6998
|
-
createResultMapper,
|
|
6999
4441
|
createResumeStateCollector,
|
|
7000
|
-
createSagaWorkflow,
|
|
7001
|
-
createSimpleHandler,
|
|
7002
|
-
createTestableMemoryStreamStore,
|
|
7003
|
-
createValueTransformMigration,
|
|
7004
|
-
createVersionedState,
|
|
7005
|
-
createVersionedStateLoader,
|
|
7006
|
-
createWebhookHandler,
|
|
7007
4442
|
createWorkflow,
|
|
7008
4443
|
days,
|
|
7009
|
-
defaultUnexpectedErrorMapper,
|
|
7010
|
-
defaultValidationErrorMapper,
|
|
7011
|
-
deserializeCauseNew,
|
|
7012
4444
|
deserializeResumeState,
|
|
7013
|
-
durable,
|
|
7014
|
-
filter,
|
|
7015
|
-
flatMap,
|
|
7016
|
-
flatMapAsync,
|
|
7017
4445
|
gatedStep,
|
|
7018
4446
|
getPendingApprovals,
|
|
7019
4447
|
getPendingHooks,
|
|
7020
4448
|
getStepTimeoutMeta,
|
|
7021
|
-
getStreamReader,
|
|
7022
4449
|
hasPendingApproval,
|
|
7023
4450
|
hasPendingHook,
|
|
7024
4451
|
hours,
|
|
@@ -7026,75 +4453,36 @@ var batchPresets = {
|
|
|
7026
4453
|
injectHook,
|
|
7027
4454
|
isApprovalRejected,
|
|
7028
4455
|
isBatchProcessingError,
|
|
7029
|
-
isConcurrentExecution,
|
|
7030
4456
|
isDuration,
|
|
7031
|
-
isIdempotencyConflict,
|
|
7032
4457
|
isInputValidationError,
|
|
7033
4458
|
isInvalidBatchConfigError,
|
|
7034
|
-
isLeaseExpired,
|
|
7035
|
-
isMigrationError,
|
|
7036
4459
|
isPendingApproval,
|
|
7037
4460
|
isPendingHook,
|
|
7038
|
-
isPersistenceError,
|
|
7039
4461
|
isResourceCleanupError,
|
|
7040
4462
|
isResumeState,
|
|
7041
|
-
isSagaCompensationError,
|
|
7042
4463
|
isSerializedResumeState,
|
|
7043
4464
|
isStepComplete,
|
|
7044
4465
|
isStepTimeoutError,
|
|
7045
|
-
isStreamBackpressureError,
|
|
7046
|
-
isStreamEnded,
|
|
7047
|
-
isStreamReadError,
|
|
7048
|
-
isStreamStoreError,
|
|
7049
|
-
isStreamWriteError,
|
|
7050
|
-
isValidationError,
|
|
7051
|
-
isVersionIncompatibleError,
|
|
7052
|
-
isVersionMismatch,
|
|
7053
4466
|
isWorkflowCancelled,
|
|
7054
4467
|
isWorkflowSnapshot,
|
|
7055
4468
|
looksLikeWorkflowSnapshot,
|
|
7056
|
-
map,
|
|
7057
|
-
mapAsync,
|
|
7058
4469
|
mergeSnapshots,
|
|
7059
4470
|
millis,
|
|
7060
4471
|
minutes,
|
|
7061
|
-
parseVersionedState,
|
|
7062
4472
|
pendingApproval,
|
|
7063
4473
|
pendingHook,
|
|
7064
|
-
pipe,
|
|
7065
4474
|
processInBatches,
|
|
7066
|
-
reduce,
|
|
7067
|
-
requireFields,
|
|
7068
4475
|
run,
|
|
7069
|
-
runSaga,
|
|
7070
4476
|
seconds,
|
|
7071
|
-
sendWebhookResponse,
|
|
7072
|
-
serializeError,
|
|
7073
4477
|
serializeResumeState,
|
|
7074
|
-
serializeThrown,
|
|
7075
|
-
shouldApplyBackpressure,
|
|
7076
|
-
skip,
|
|
7077
|
-
skipWhile,
|
|
7078
|
-
streamBackpressureError,
|
|
7079
|
-
streamCloseError,
|
|
7080
|
-
streamEnded,
|
|
7081
|
-
streamReadError,
|
|
7082
|
-
streamStoreError,
|
|
7083
|
-
streamWriteError,
|
|
7084
|
-
stringifyVersionedState,
|
|
7085
|
-
take,
|
|
7086
|
-
takeWhile,
|
|
7087
|
-
toAsyncIterable,
|
|
7088
4478
|
toDays,
|
|
7089
4479
|
toHours,
|
|
7090
4480
|
toMillis,
|
|
7091
4481
|
toMinutes,
|
|
7092
4482
|
toResumeState,
|
|
7093
4483
|
toSeconds,
|
|
7094
|
-
toWebhookRequest,
|
|
7095
4484
|
validateInput,
|
|
7096
4485
|
validateSnapshot,
|
|
7097
|
-
validationError,
|
|
7098
4486
|
withDeps,
|
|
7099
4487
|
withScope
|
|
7100
4488
|
});
|