@rivetkit/workflow-engine 2.3.0-rc.1 → 2.3.0-rc.11
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/tsup/{chunk-UMFB2AR3.js → chunk-DFNXCZ47.js} +60 -36
- package/dist/tsup/chunk-DFNXCZ47.js.map +1 -0
- package/dist/tsup/{chunk-4SWXLWKL.cjs → chunk-U2W3KHJC.cjs} +60 -36
- package/dist/tsup/chunk-U2W3KHJC.cjs.map +1 -0
- package/dist/tsup/index.cjs +2 -2
- package/dist/tsup/index.d.cts +17 -7
- package/dist/tsup/index.d.ts +17 -7
- package/dist/tsup/index.js +1 -1
- package/dist/tsup/testing.cjs +23 -23
- package/dist/tsup/testing.js +1 -1
- package/package.json +1 -1
- package/schemas/serde.ts +1 -3
- package/src/context.ts +33 -41
- package/src/index.ts +8 -8
- package/src/location.ts +1 -1
- package/src/storage.ts +50 -3
- package/src/types.ts +10 -6
- package/dist/tsup/chunk-4SWXLWKL.cjs.map +0 -1
- package/dist/tsup/chunk-UMFB2AR3.js.map +0 -1
package/dist/tsup/index.d.cts
CHANGED
|
@@ -345,6 +345,8 @@ interface StepConfig<T> {
|
|
|
345
345
|
retryBackoffMax?: number;
|
|
346
346
|
/** Timeout in ms for step execution (default: 30000). Set to 0 to disable. */
|
|
347
347
|
timeout?: number;
|
|
348
|
+
/** If true, step timeouts retry like any other error instead of failing immediately as critical. Default: false. */
|
|
349
|
+
retryOnTimeout?: boolean;
|
|
348
350
|
}
|
|
349
351
|
type TryStepCatchKind = "critical" | "timeout" | "exhausted" | "rollback";
|
|
350
352
|
interface TryStepFailure {
|
|
@@ -398,7 +400,7 @@ type LoopResult<S, T> = {
|
|
|
398
400
|
* Stateless loops (state = undefined) may return void/undefined, which is treated as
|
|
399
401
|
* `Loop.continue(undefined)`.
|
|
400
402
|
*/
|
|
401
|
-
type LoopIterationResult<S, T> = Promise<LoopResult<S, T> | (S extends undefined ?
|
|
403
|
+
type LoopIterationResult<S, T> = Promise<LoopResult<S, T> | (S extends undefined ? undefined : never)>;
|
|
402
404
|
/**
|
|
403
405
|
* Configuration for a loop.
|
|
404
406
|
*/
|
|
@@ -410,6 +412,12 @@ interface LoopConfig<S, T> {
|
|
|
410
412
|
historyPruneInterval?: number;
|
|
411
413
|
/** Number of past iterations to retain when pruning. Defaults to historyPruneInterval. */
|
|
412
414
|
historySize?: number;
|
|
415
|
+
/** @deprecated Use historyPruneInterval. */
|
|
416
|
+
commitInterval?: number;
|
|
417
|
+
/** @deprecated Use historyPruneInterval. */
|
|
418
|
+
historyEvery?: number;
|
|
419
|
+
/** @deprecated Use historySize. */
|
|
420
|
+
historyKeep?: number;
|
|
413
421
|
}
|
|
414
422
|
/**
|
|
415
423
|
* Configuration for a branch in join/race.
|
|
@@ -707,7 +715,9 @@ declare class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
707
715
|
*
|
|
708
716
|
* Note: This does NOT cancel the underlying operation. JavaScript Promises
|
|
709
717
|
* cannot be cancelled once started. When a timeout occurs:
|
|
710
|
-
* - The step is
|
|
718
|
+
* - The step is rejected with StepTimeoutError. By default this is treated
|
|
719
|
+
* as a critical failure with no retry. Set retryOnTimeout: true on the
|
|
720
|
+
* step config to retry timeouts like any other error.
|
|
711
721
|
* - The underlying async operation continues running in the background
|
|
712
722
|
* - Any side effects from the operation may still occur
|
|
713
723
|
*
|
|
@@ -773,6 +783,11 @@ declare class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
773
783
|
private executeRemoved;
|
|
774
784
|
}
|
|
775
785
|
|
|
786
|
+
/**
|
|
787
|
+
* Extract structured error information from an error.
|
|
788
|
+
*/
|
|
789
|
+
declare function extractErrorInfo(error: unknown): WorkflowError;
|
|
790
|
+
|
|
776
791
|
/**
|
|
777
792
|
* Thrown from steps to prevent retry.
|
|
778
793
|
* Use this when an error is unrecoverable and retrying would be pointless.
|
|
@@ -877,11 +892,6 @@ declare class EntryInProgressError extends Error {
|
|
|
877
892
|
constructor();
|
|
878
893
|
}
|
|
879
894
|
|
|
880
|
-
/**
|
|
881
|
-
* Extract structured error information from an error.
|
|
882
|
-
*/
|
|
883
|
-
declare function extractErrorInfo(error: unknown): WorkflowError;
|
|
884
|
-
|
|
885
895
|
/**
|
|
886
896
|
* Check if a path segment is a loop iteration marker.
|
|
887
897
|
*/
|
package/dist/tsup/index.d.ts
CHANGED
|
@@ -345,6 +345,8 @@ interface StepConfig<T> {
|
|
|
345
345
|
retryBackoffMax?: number;
|
|
346
346
|
/** Timeout in ms for step execution (default: 30000). Set to 0 to disable. */
|
|
347
347
|
timeout?: number;
|
|
348
|
+
/** If true, step timeouts retry like any other error instead of failing immediately as critical. Default: false. */
|
|
349
|
+
retryOnTimeout?: boolean;
|
|
348
350
|
}
|
|
349
351
|
type TryStepCatchKind = "critical" | "timeout" | "exhausted" | "rollback";
|
|
350
352
|
interface TryStepFailure {
|
|
@@ -398,7 +400,7 @@ type LoopResult<S, T> = {
|
|
|
398
400
|
* Stateless loops (state = undefined) may return void/undefined, which is treated as
|
|
399
401
|
* `Loop.continue(undefined)`.
|
|
400
402
|
*/
|
|
401
|
-
type LoopIterationResult<S, T> = Promise<LoopResult<S, T> | (S extends undefined ?
|
|
403
|
+
type LoopIterationResult<S, T> = Promise<LoopResult<S, T> | (S extends undefined ? undefined : never)>;
|
|
402
404
|
/**
|
|
403
405
|
* Configuration for a loop.
|
|
404
406
|
*/
|
|
@@ -410,6 +412,12 @@ interface LoopConfig<S, T> {
|
|
|
410
412
|
historyPruneInterval?: number;
|
|
411
413
|
/** Number of past iterations to retain when pruning. Defaults to historyPruneInterval. */
|
|
412
414
|
historySize?: number;
|
|
415
|
+
/** @deprecated Use historyPruneInterval. */
|
|
416
|
+
commitInterval?: number;
|
|
417
|
+
/** @deprecated Use historyPruneInterval. */
|
|
418
|
+
historyEvery?: number;
|
|
419
|
+
/** @deprecated Use historySize. */
|
|
420
|
+
historyKeep?: number;
|
|
413
421
|
}
|
|
414
422
|
/**
|
|
415
423
|
* Configuration for a branch in join/race.
|
|
@@ -707,7 +715,9 @@ declare class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
707
715
|
*
|
|
708
716
|
* Note: This does NOT cancel the underlying operation. JavaScript Promises
|
|
709
717
|
* cannot be cancelled once started. When a timeout occurs:
|
|
710
|
-
* - The step is
|
|
718
|
+
* - The step is rejected with StepTimeoutError. By default this is treated
|
|
719
|
+
* as a critical failure with no retry. Set retryOnTimeout: true on the
|
|
720
|
+
* step config to retry timeouts like any other error.
|
|
711
721
|
* - The underlying async operation continues running in the background
|
|
712
722
|
* - Any side effects from the operation may still occur
|
|
713
723
|
*
|
|
@@ -773,6 +783,11 @@ declare class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
773
783
|
private executeRemoved;
|
|
774
784
|
}
|
|
775
785
|
|
|
786
|
+
/**
|
|
787
|
+
* Extract structured error information from an error.
|
|
788
|
+
*/
|
|
789
|
+
declare function extractErrorInfo(error: unknown): WorkflowError;
|
|
790
|
+
|
|
776
791
|
/**
|
|
777
792
|
* Thrown from steps to prevent retry.
|
|
778
793
|
* Use this when an error is unrecoverable and retrying would be pointless.
|
|
@@ -877,11 +892,6 @@ declare class EntryInProgressError extends Error {
|
|
|
877
892
|
constructor();
|
|
878
893
|
}
|
|
879
894
|
|
|
880
|
-
/**
|
|
881
|
-
* Extract structured error information from an error.
|
|
882
|
-
*/
|
|
883
|
-
declare function extractErrorInfo(error: unknown): WorkflowError;
|
|
884
|
-
|
|
885
895
|
/**
|
|
886
896
|
* Check if a path segment is a loop iteration marker.
|
|
887
897
|
*/
|
package/dist/tsup/index.js
CHANGED
package/dist/tsup/testing.cjs
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
var
|
|
50
|
+
var _chunkU2W3KHJCcjs = require('./chunk-U2W3KHJC.cjs');
|
|
51
51
|
|
|
52
52
|
// src/testing.ts
|
|
53
53
|
var InMemoryWorkflowMessageDriver = class {
|
|
@@ -97,7 +97,7 @@ var InMemoryWorkflowMessageDriver = class {
|
|
|
97
97
|
}
|
|
98
98
|
async waitForMessages(messageNames, abortSignal) {
|
|
99
99
|
if (abortSignal.aborted) {
|
|
100
|
-
throw new (0,
|
|
100
|
+
throw new (0, _chunkU2W3KHJCcjs.EvictedError)();
|
|
101
101
|
}
|
|
102
102
|
const nameSet = messageNames.length > 0 ? new Set(messageNames) : void 0;
|
|
103
103
|
if (this.#messages.some(
|
|
@@ -118,7 +118,7 @@ var InMemoryWorkflowMessageDriver = class {
|
|
|
118
118
|
},
|
|
119
119
|
abortSignal,
|
|
120
120
|
onAbort: () => {
|
|
121
|
-
waiter.reject(new (0,
|
|
121
|
+
waiter.reject(new (0, _chunkU2W3KHJCcjs.EvictedError)());
|
|
122
122
|
}
|
|
123
123
|
};
|
|
124
124
|
abortSignal.addEventListener("abort", waiter.onAbort, {
|
|
@@ -158,56 +158,56 @@ var InMemoryDriver = (_class = class {constructor() { _class.prototype.__init.ca
|
|
|
158
158
|
__init4() {this.workerPollInterval = 100}
|
|
159
159
|
__init5() {this.messageDriver = this.#inMemoryMessageDriver}
|
|
160
160
|
async get(key) {
|
|
161
|
-
await
|
|
162
|
-
const entry = this.kv.get(
|
|
161
|
+
await _chunkU2W3KHJCcjs.sleep.call(void 0, this.latency);
|
|
162
|
+
const entry = this.kv.get(_chunkU2W3KHJCcjs.keyToHex.call(void 0, key));
|
|
163
163
|
return _nullishCoalesce((entry == null ? void 0 : entry.value), () => ( null));
|
|
164
164
|
}
|
|
165
165
|
async set(key, value) {
|
|
166
|
-
await
|
|
167
|
-
this.kv.set(
|
|
166
|
+
await _chunkU2W3KHJCcjs.sleep.call(void 0, this.latency);
|
|
167
|
+
this.kv.set(_chunkU2W3KHJCcjs.keyToHex.call(void 0, key), { key, value });
|
|
168
168
|
}
|
|
169
169
|
async delete(key) {
|
|
170
|
-
await
|
|
171
|
-
this.kv.delete(
|
|
170
|
+
await _chunkU2W3KHJCcjs.sleep.call(void 0, this.latency);
|
|
171
|
+
this.kv.delete(_chunkU2W3KHJCcjs.keyToHex.call(void 0, key));
|
|
172
172
|
}
|
|
173
173
|
async deletePrefix(prefix) {
|
|
174
|
-
await
|
|
174
|
+
await _chunkU2W3KHJCcjs.sleep.call(void 0, this.latency);
|
|
175
175
|
for (const [hexKey, entry] of this.kv) {
|
|
176
|
-
if (
|
|
176
|
+
if (_chunkU2W3KHJCcjs.keyStartsWith.call(void 0, entry.key, prefix)) {
|
|
177
177
|
this.kv.delete(hexKey);
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
async deleteRange(start, end) {
|
|
182
|
-
await
|
|
182
|
+
await _chunkU2W3KHJCcjs.sleep.call(void 0, this.latency);
|
|
183
183
|
for (const [hexKey, entry] of this.kv) {
|
|
184
|
-
if (
|
|
184
|
+
if (_chunkU2W3KHJCcjs.compareKeys.call(void 0, entry.key, start) >= 0 && _chunkU2W3KHJCcjs.compareKeys.call(void 0, entry.key, end) < 0) {
|
|
185
185
|
this.kv.delete(hexKey);
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
async list(prefix) {
|
|
190
|
-
await
|
|
190
|
+
await _chunkU2W3KHJCcjs.sleep.call(void 0, this.latency);
|
|
191
191
|
const results = [];
|
|
192
192
|
for (const entry of this.kv.values()) {
|
|
193
|
-
if (
|
|
193
|
+
if (_chunkU2W3KHJCcjs.keyStartsWith.call(void 0, entry.key, prefix)) {
|
|
194
194
|
results.push({ key: entry.key, value: entry.value });
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
|
-
return results.sort((a, b) =>
|
|
197
|
+
return results.sort((a, b) => _chunkU2W3KHJCcjs.compareKeys.call(void 0, a.key, b.key));
|
|
198
198
|
}
|
|
199
199
|
async batch(writes) {
|
|
200
|
-
await
|
|
200
|
+
await _chunkU2W3KHJCcjs.sleep.call(void 0, this.latency);
|
|
201
201
|
for (const { key, value } of writes) {
|
|
202
|
-
this.kv.set(
|
|
202
|
+
this.kv.set(_chunkU2W3KHJCcjs.keyToHex.call(void 0, key), { key, value });
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
async setAlarm(workflowId, wakeAt) {
|
|
206
|
-
await
|
|
206
|
+
await _chunkU2W3KHJCcjs.sleep.call(void 0, this.latency);
|
|
207
207
|
this.alarms.set(workflowId, wakeAt);
|
|
208
208
|
}
|
|
209
209
|
async clearAlarm(workflowId) {
|
|
210
|
-
await
|
|
210
|
+
await _chunkU2W3KHJCcjs.sleep.call(void 0, this.latency);
|
|
211
211
|
this.alarms.delete(workflowId);
|
|
212
212
|
}
|
|
213
213
|
async waitForMessages(messageNames, abortSignal) {
|
|
@@ -218,7 +218,7 @@ var InMemoryDriver = (_class = class {constructor() { _class.prototype.__init.ca
|
|
|
218
218
|
}
|
|
219
219
|
while (true) {
|
|
220
220
|
if (abortSignal.aborted) {
|
|
221
|
-
throw new (0,
|
|
221
|
+
throw new (0, _chunkU2W3KHJCcjs.EvictedError)();
|
|
222
222
|
}
|
|
223
223
|
const messages = await this.messageDriver.receiveMessages({
|
|
224
224
|
names: messageNames.length > 0 ? messageNames : void 0,
|
|
@@ -228,7 +228,7 @@ var InMemoryDriver = (_class = class {constructor() { _class.prototype.__init.ca
|
|
|
228
228
|
if (messages.length > 0) {
|
|
229
229
|
return;
|
|
230
230
|
}
|
|
231
|
-
await
|
|
231
|
+
await _chunkU2W3KHJCcjs.sleep.call(void 0, Math.max(1, this.latency));
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
/**
|
|
@@ -324,5 +324,5 @@ var InMemoryDriver = (_class = class {constructor() { _class.prototype.__init.ca
|
|
|
324
324
|
|
|
325
325
|
|
|
326
326
|
|
|
327
|
-
exports.CancelledError =
|
|
327
|
+
exports.CancelledError = _chunkU2W3KHJCcjs.CancelledError; exports.CriticalError = _chunkU2W3KHJCcjs.CriticalError; exports.DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL = _chunkU2W3KHJCcjs.DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL; exports.DEFAULT_MAX_RETRIES = _chunkU2W3KHJCcjs.DEFAULT_MAX_RETRIES; exports.DEFAULT_RETRY_BACKOFF_BASE = _chunkU2W3KHJCcjs.DEFAULT_RETRY_BACKOFF_BASE; exports.DEFAULT_RETRY_BACKOFF_MAX = _chunkU2W3KHJCcjs.DEFAULT_RETRY_BACKOFF_MAX; exports.DEFAULT_STEP_TIMEOUT = _chunkU2W3KHJCcjs.DEFAULT_STEP_TIMEOUT; exports.EntryInProgressError = _chunkU2W3KHJCcjs.EntryInProgressError; exports.EvictedError = _chunkU2W3KHJCcjs.EvictedError; exports.HistoryDivergedError = _chunkU2W3KHJCcjs.HistoryDivergedError; exports.InMemoryDriver = InMemoryDriver; exports.JoinError = _chunkU2W3KHJCcjs.JoinError; exports.Loop = _chunkU2W3KHJCcjs.Loop; exports.MessageWaitError = _chunkU2W3KHJCcjs.MessageWaitError; exports.RaceError = _chunkU2W3KHJCcjs.RaceError; exports.RollbackCheckpointError = _chunkU2W3KHJCcjs.RollbackCheckpointError; exports.RollbackError = _chunkU2W3KHJCcjs.RollbackError; exports.SleepError = _chunkU2W3KHJCcjs.SleepError; exports.StepExhaustedError = _chunkU2W3KHJCcjs.StepExhaustedError; exports.StepFailedError = _chunkU2W3KHJCcjs.StepFailedError; exports.WorkflowContextImpl = _chunkU2W3KHJCcjs.WorkflowContextImpl; exports.appendLoopIteration = _chunkU2W3KHJCcjs.appendLoopIteration; exports.appendName = _chunkU2W3KHJCcjs.appendName; exports.createEntry = _chunkU2W3KHJCcjs.createEntry; exports.createHistorySnapshot = _chunkU2W3KHJCcjs.createHistorySnapshot; exports.createStorage = _chunkU2W3KHJCcjs.createStorage; exports.deleteEntriesWithPrefix = _chunkU2W3KHJCcjs.deleteEntriesWithPrefix; exports.emptyLocation = _chunkU2W3KHJCcjs.emptyLocation; exports.extractErrorInfo = _chunkU2W3KHJCcjs.extractErrorInfo; exports.flush = _chunkU2W3KHJCcjs.flush; exports.generateId = _chunkU2W3KHJCcjs.generateId; exports.getEntry = _chunkU2W3KHJCcjs.getEntry; exports.getOrCreateMetadata = _chunkU2W3KHJCcjs.getOrCreateMetadata; exports.isLocationPrefix = _chunkU2W3KHJCcjs.isLocationPrefix; exports.isLoopIterationMarker = _chunkU2W3KHJCcjs.isLoopIterationMarker; exports.loadMetadata = _chunkU2W3KHJCcjs.loadMetadata; exports.loadStorage = _chunkU2W3KHJCcjs.loadStorage; exports.locationToKey = _chunkU2W3KHJCcjs.locationToKey; exports.locationsEqual = _chunkU2W3KHJCcjs.locationsEqual; exports.parentLocation = _chunkU2W3KHJCcjs.parentLocation; exports.registerName = _chunkU2W3KHJCcjs.registerName; exports.replayWorkflowFromStep = _chunkU2W3KHJCcjs.replayWorkflowFromStep; exports.resolveName = _chunkU2W3KHJCcjs.resolveName; exports.runWorkflow = _chunkU2W3KHJCcjs.runWorkflow; exports.setEntry = _chunkU2W3KHJCcjs.setEntry;
|
|
328
328
|
//# sourceMappingURL=testing.cjs.map
|
package/dist/tsup/testing.js
CHANGED
package/package.json
CHANGED
package/schemas/serde.ts
CHANGED
|
@@ -18,7 +18,6 @@ import type {
|
|
|
18
18
|
EntryMetadata as InternalEntryMetadata,
|
|
19
19
|
EntryStatus as InternalEntryStatus,
|
|
20
20
|
Location as InternalLocation,
|
|
21
|
-
LoopIterationMarker as InternalLoopIterationMarker,
|
|
22
21
|
PathSegment as InternalPathSegment,
|
|
23
22
|
SleepState as InternalSleepState,
|
|
24
23
|
WorkflowState as InternalWorkflowState,
|
|
@@ -27,7 +26,6 @@ import {
|
|
|
27
26
|
CURRENT_VERSION,
|
|
28
27
|
ENTRY_METADATA_VERSIONED,
|
|
29
28
|
ENTRY_VERSIONED,
|
|
30
|
-
WORKFLOW_METADATA_VERSIONED,
|
|
31
29
|
} from "./versioned.js";
|
|
32
30
|
|
|
33
31
|
// === Helper: ArrayBuffer to/from utilities ===
|
|
@@ -74,7 +72,7 @@ function assertString(
|
|
|
74
72
|
/**
|
|
75
73
|
* Validate that a value is a number.
|
|
76
74
|
*/
|
|
77
|
-
function
|
|
75
|
+
function _assertNumber(
|
|
78
76
|
value: unknown,
|
|
79
77
|
context: string,
|
|
80
78
|
): asserts value is number {
|
package/src/context.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
StepExhaustedError,
|
|
22
22
|
StepFailedError,
|
|
23
23
|
} from "./errors.js";
|
|
24
|
-
import {
|
|
24
|
+
import { buildEntryMetadataKey, buildLoopIterationRange } from "./keys.js";
|
|
25
25
|
import {
|
|
26
26
|
appendLoopIteration,
|
|
27
27
|
appendName,
|
|
@@ -36,13 +36,12 @@ import {
|
|
|
36
36
|
flush,
|
|
37
37
|
getOrCreateMetadata,
|
|
38
38
|
loadMetadata,
|
|
39
|
-
setEntry,
|
|
40
39
|
type PendingDeletions,
|
|
40
|
+
setEntry,
|
|
41
41
|
} from "./storage.js";
|
|
42
42
|
import type {
|
|
43
43
|
BranchConfig,
|
|
44
44
|
BranchOutput,
|
|
45
|
-
BranchStatus,
|
|
46
45
|
Entry,
|
|
47
46
|
EntryKindType,
|
|
48
47
|
EntryMetadata,
|
|
@@ -66,11 +65,11 @@ import type {
|
|
|
66
65
|
WorkflowError,
|
|
67
66
|
WorkflowErrorEvent,
|
|
68
67
|
WorkflowErrorHandler,
|
|
68
|
+
WorkflowMessageDriver,
|
|
69
69
|
WorkflowQueue,
|
|
70
70
|
WorkflowQueueMessage,
|
|
71
71
|
WorkflowQueueNextBatchOptions,
|
|
72
72
|
WorkflowQueueNextOptions,
|
|
73
|
-
WorkflowMessageDriver,
|
|
74
73
|
} from "./types.js";
|
|
75
74
|
import { sleep } from "./utils.js";
|
|
76
75
|
|
|
@@ -505,8 +504,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
505
504
|
* Throws HistoryDivergedError if duplicate detected.
|
|
506
505
|
*/
|
|
507
506
|
private checkDuplicateName(name: string): void {
|
|
508
|
-
const fullKey =
|
|
509
|
-
locationToKey(this.storage, this.currentLocation) + "/" + name;
|
|
507
|
+
const fullKey = `${locationToKey(this.storage, this.currentLocation)}/${name}`;
|
|
510
508
|
if (this.usedNamesInExecution.has(fullKey)) {
|
|
511
509
|
throw new HistoryDivergedError(
|
|
512
510
|
`Duplicate entry name "${name}" at location "${locationToKey(this.storage, this.currentLocation)}". ` +
|
|
@@ -580,7 +578,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
580
578
|
const isUnderPrefix =
|
|
581
579
|
prefix === ""
|
|
582
580
|
? true // Root: all keys are children
|
|
583
|
-
: key.startsWith(prefix
|
|
581
|
+
: key.startsWith(`${prefix}/`) || key === prefix;
|
|
584
582
|
|
|
585
583
|
if (isUnderPrefix) {
|
|
586
584
|
if (!this.visitedKeys.has(key)) {
|
|
@@ -825,10 +823,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
825
823
|
const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
826
824
|
|
|
827
825
|
if (metadata.attempts > maxRetries) {
|
|
828
|
-
|
|
829
|
-
// driver implementations may persist metadata without the history
|
|
830
|
-
// entry error (e.g. partial writes/crashes between attempts).
|
|
831
|
-
const lastError = stepData.error ?? metadata.error;
|
|
826
|
+
const lastError = metadata.error;
|
|
832
827
|
const exhaustedError = new StepExhaustedError(
|
|
833
828
|
config.name,
|
|
834
829
|
lastError,
|
|
@@ -941,28 +936,26 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
941
936
|
});
|
|
942
937
|
return output;
|
|
943
938
|
} catch (error) {
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
939
|
+
if (entry.kind.type === "step") {
|
|
940
|
+
entry.kind.data.error = String(error);
|
|
941
|
+
}
|
|
942
|
+
entry.dirty = true;
|
|
943
|
+
|
|
944
|
+
// Timeout errors are treated as critical by default. Steps opt
|
|
945
|
+
// into retrying on timeout with retryOnTimeout: true.
|
|
946
|
+
if (error instanceof StepTimeoutError && !config.retryOnTimeout) {
|
|
950
947
|
metadata.status = "exhausted";
|
|
951
948
|
metadata.error = String(error);
|
|
952
|
-
await this.flushStorage();
|
|
953
949
|
await this.notifyStepError(config, metadata.attempts, error, {
|
|
954
950
|
willRetry: false,
|
|
955
951
|
});
|
|
956
952
|
throw markErrorReported(
|
|
957
|
-
attachTryStepFailure(
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
error: extractErrorInfo(error),
|
|
964
|
-
},
|
|
965
|
-
),
|
|
953
|
+
attachTryStepFailure(new CriticalError(error.message), {
|
|
954
|
+
kind: "timeout",
|
|
955
|
+
stepName: config.name,
|
|
956
|
+
attempts: metadata.attempts,
|
|
957
|
+
error: extractErrorInfo(error),
|
|
958
|
+
}),
|
|
966
959
|
);
|
|
967
960
|
}
|
|
968
961
|
|
|
@@ -970,13 +963,8 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
970
963
|
error instanceof CriticalError ||
|
|
971
964
|
error instanceof RollbackError
|
|
972
965
|
) {
|
|
973
|
-
if (entry.kind.type === "step") {
|
|
974
|
-
entry.kind.data.error = String(error);
|
|
975
|
-
}
|
|
976
|
-
entry.dirty = true;
|
|
977
966
|
metadata.status = "exhausted";
|
|
978
967
|
metadata.error = String(error);
|
|
979
|
-
await this.flushStorage();
|
|
980
968
|
await this.notifyStepError(config, metadata.attempts, error, {
|
|
981
969
|
willRetry: false,
|
|
982
970
|
});
|
|
@@ -993,15 +981,10 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
993
981
|
);
|
|
994
982
|
}
|
|
995
983
|
|
|
996
|
-
if (entry.kind.type === "step") {
|
|
997
|
-
entry.kind.data.error = String(error);
|
|
998
|
-
}
|
|
999
|
-
entry.dirty = true;
|
|
1000
984
|
const willRetry = metadata.attempts <= maxRetries;
|
|
1001
985
|
metadata.status = willRetry ? "failed" : "exhausted";
|
|
1002
986
|
metadata.error = String(error);
|
|
1003
987
|
|
|
1004
|
-
await this.flushStorage();
|
|
1005
988
|
if (willRetry) {
|
|
1006
989
|
const retryDelay = calculateBackoff(
|
|
1007
990
|
metadata.attempts,
|
|
@@ -1026,7 +1009,10 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
1026
1009
|
attachTryStepFailure(
|
|
1027
1010
|
new StepExhaustedError(config.name, String(error)),
|
|
1028
1011
|
{
|
|
1029
|
-
kind:
|
|
1012
|
+
kind:
|
|
1013
|
+
error instanceof StepTimeoutError
|
|
1014
|
+
? "timeout"
|
|
1015
|
+
: "exhausted",
|
|
1030
1016
|
stepName: config.name,
|
|
1031
1017
|
attempts: metadata.attempts,
|
|
1032
1018
|
error: extractErrorInfo(error),
|
|
@@ -1045,7 +1031,9 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
1045
1031
|
*
|
|
1046
1032
|
* Note: This does NOT cancel the underlying operation. JavaScript Promises
|
|
1047
1033
|
* cannot be cancelled once started. When a timeout occurs:
|
|
1048
|
-
* - The step is
|
|
1034
|
+
* - The step is rejected with StepTimeoutError. By default this is treated
|
|
1035
|
+
* as a critical failure with no retry. Set retryOnTimeout: true on the
|
|
1036
|
+
* step config to retry timeouts like any other error.
|
|
1049
1037
|
* - The underlying async operation continues running in the background
|
|
1050
1038
|
* - Any side effects from the operation may still occur
|
|
1051
1039
|
*
|
|
@@ -1224,8 +1212,12 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
1224
1212
|
}
|
|
1225
1213
|
|
|
1226
1214
|
const historyPruneInterval =
|
|
1227
|
-
config.historyPruneInterval ??
|
|
1228
|
-
|
|
1215
|
+
config.historyPruneInterval ??
|
|
1216
|
+
config.commitInterval ??
|
|
1217
|
+
config.historyEvery ??
|
|
1218
|
+
DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL;
|
|
1219
|
+
const historySize =
|
|
1220
|
+
config.historySize ?? config.historyKeep ?? historyPruneInterval;
|
|
1229
1221
|
|
|
1230
1222
|
// Track the last iteration we pruned up to so we only delete
|
|
1231
1223
|
// newly-expired iterations instead of re-scanning from 0.
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export {
|
|
|
13
13
|
} from "./context.js";
|
|
14
14
|
// Driver
|
|
15
15
|
export type { EngineDriver, KVEntry, KVWrite } from "./driver.js";
|
|
16
|
+
export { extractErrorInfo } from "./error-utils.js";
|
|
16
17
|
// Errors
|
|
17
18
|
export {
|
|
18
19
|
CancelledError,
|
|
@@ -29,7 +30,6 @@ export {
|
|
|
29
30
|
StepExhaustedError,
|
|
30
31
|
StepFailedError,
|
|
31
32
|
} from "./errors.js";
|
|
32
|
-
export { extractErrorInfo } from "./error-utils.js";
|
|
33
33
|
|
|
34
34
|
// Location utilities
|
|
35
35
|
export {
|
|
@@ -82,9 +82,6 @@ export type {
|
|
|
82
82
|
PathSegment,
|
|
83
83
|
RaceEntry,
|
|
84
84
|
RemovedEntry,
|
|
85
|
-
WorkflowEntryMetadataSnapshot,
|
|
86
|
-
WorkflowHistoryEntry,
|
|
87
|
-
WorkflowHistorySnapshot,
|
|
88
85
|
RollbackCheckpointEntry,
|
|
89
86
|
RollbackContextInterface,
|
|
90
87
|
RunWorkflowOptions,
|
|
@@ -102,20 +99,23 @@ export type {
|
|
|
102
99
|
TryStepFailure,
|
|
103
100
|
TryStepResult,
|
|
104
101
|
WorkflowContextInterface,
|
|
102
|
+
WorkflowEntryMetadataSnapshot,
|
|
105
103
|
WorkflowError,
|
|
106
104
|
WorkflowErrorEvent,
|
|
107
105
|
WorkflowErrorHandler,
|
|
108
106
|
WorkflowFunction,
|
|
109
107
|
WorkflowHandle,
|
|
110
|
-
|
|
108
|
+
WorkflowHistoryEntry,
|
|
109
|
+
WorkflowHistorySnapshot,
|
|
110
|
+
WorkflowMessageDriver,
|
|
111
111
|
WorkflowQueue,
|
|
112
112
|
WorkflowQueueMessage,
|
|
113
113
|
WorkflowQueueNextBatchOptions,
|
|
114
114
|
WorkflowQueueNextOptions,
|
|
115
|
-
WorkflowMessageDriver,
|
|
116
115
|
WorkflowResult,
|
|
117
|
-
|
|
116
|
+
WorkflowRollbackErrorEvent,
|
|
118
117
|
WorkflowRunErrorEvent,
|
|
118
|
+
WorkflowRunMode,
|
|
119
119
|
WorkflowState,
|
|
120
120
|
WorkflowStepErrorEvent,
|
|
121
121
|
} from "./types.js";
|
|
@@ -185,9 +185,9 @@ import type {
|
|
|
185
185
|
RunWorkflowOptions,
|
|
186
186
|
Storage,
|
|
187
187
|
WorkflowErrorEvent,
|
|
188
|
-
WorkflowHistorySnapshot,
|
|
189
188
|
WorkflowFunction,
|
|
190
189
|
WorkflowHandle,
|
|
190
|
+
WorkflowHistorySnapshot,
|
|
191
191
|
WorkflowMessageDriver,
|
|
192
192
|
WorkflowResult,
|
|
193
193
|
WorkflowRunMode,
|
package/src/location.ts
CHANGED
|
@@ -159,7 +159,7 @@ export function getChildEntries(
|
|
|
159
159
|
const isChild =
|
|
160
160
|
parentKey === ""
|
|
161
161
|
? true
|
|
162
|
-
: key.startsWith(parentKey
|
|
162
|
+
: key.startsWith(`${parentKey}/`) || key === parentKey;
|
|
163
163
|
|
|
164
164
|
if (isChild) {
|
|
165
165
|
// Return the actual entry's location, not the parent location
|
package/src/storage.ts
CHANGED
|
@@ -40,6 +40,9 @@ import type {
|
|
|
40
40
|
WorkflowHistorySnapshot,
|
|
41
41
|
} from "./types.js";
|
|
42
42
|
|
|
43
|
+
export const MAX_KV_BATCH_ENTRIES = 128;
|
|
44
|
+
export const MAX_KV_BATCH_PAYLOAD_BYTES = 976 * 1024;
|
|
45
|
+
|
|
43
46
|
/**
|
|
44
47
|
* Create an empty storage instance.
|
|
45
48
|
*/
|
|
@@ -238,6 +241,8 @@ export async function flush(
|
|
|
238
241
|
pendingDeletions?: PendingDeletions,
|
|
239
242
|
): Promise<void> {
|
|
240
243
|
const writes: KVWrite[] = [];
|
|
244
|
+
const dirtyEntries: Entry[] = [];
|
|
245
|
+
const dirtyMetadata: EntryMetadata[] = [];
|
|
241
246
|
let historyUpdated = false;
|
|
242
247
|
|
|
243
248
|
// Flush only new names (those added since last flush)
|
|
@@ -263,7 +268,7 @@ export async function flush(
|
|
|
263
268
|
key: buildHistoryKey(entry.location),
|
|
264
269
|
value: serializeEntry(entry),
|
|
265
270
|
});
|
|
266
|
-
entry
|
|
271
|
+
dirtyEntries.push(entry);
|
|
267
272
|
historyUpdated = true;
|
|
268
273
|
}
|
|
269
274
|
}
|
|
@@ -275,7 +280,7 @@ export async function flush(
|
|
|
275
280
|
key: buildEntryMetadataKey(id),
|
|
276
281
|
value: serializeEntryMetadata(metadata),
|
|
277
282
|
});
|
|
278
|
-
metadata
|
|
283
|
+
dirtyMetadata.push(metadata);
|
|
279
284
|
historyUpdated = true;
|
|
280
285
|
}
|
|
281
286
|
}
|
|
@@ -313,7 +318,9 @@ export async function flush(
|
|
|
313
318
|
}
|
|
314
319
|
|
|
315
320
|
if (writes.length > 0) {
|
|
316
|
-
|
|
321
|
+
for (const chunk of splitBatchWrites(writes)) {
|
|
322
|
+
await driver.batch(chunk);
|
|
323
|
+
}
|
|
317
324
|
}
|
|
318
325
|
|
|
319
326
|
// Apply pending deletions after the batch write. These are collected
|
|
@@ -336,6 +343,12 @@ export async function flush(
|
|
|
336
343
|
}
|
|
337
344
|
|
|
338
345
|
// Update flushed tracking after successful write
|
|
346
|
+
for (const entry of dirtyEntries) {
|
|
347
|
+
entry.dirty = false;
|
|
348
|
+
}
|
|
349
|
+
for (const metadata of dirtyMetadata) {
|
|
350
|
+
metadata.dirty = false;
|
|
351
|
+
}
|
|
339
352
|
storage.flushedNameCount = storage.nameRegistry.length;
|
|
340
353
|
storage.flushedState = storage.state;
|
|
341
354
|
storage.flushedOutput = storage.output;
|
|
@@ -346,6 +359,40 @@ export async function flush(
|
|
|
346
359
|
}
|
|
347
360
|
}
|
|
348
361
|
|
|
362
|
+
function splitBatchWrites(writes: KVWrite[]): KVWrite[][] {
|
|
363
|
+
const chunks: KVWrite[][] = [];
|
|
364
|
+
let chunk: KVWrite[] = [];
|
|
365
|
+
let chunkBytes = 0;
|
|
366
|
+
|
|
367
|
+
for (const write of writes) {
|
|
368
|
+
const writeBytes = write.key.byteLength + write.value.byteLength;
|
|
369
|
+
if (writeBytes > MAX_KV_BATCH_PAYLOAD_BYTES) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
`KV batch write is ${writeBytes} bytes, exceeding the ${MAX_KV_BATCH_PAYLOAD_BYTES} byte limit`,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (
|
|
376
|
+
chunk.length >= MAX_KV_BATCH_ENTRIES ||
|
|
377
|
+
(chunk.length > 0 &&
|
|
378
|
+
chunkBytes + writeBytes > MAX_KV_BATCH_PAYLOAD_BYTES)
|
|
379
|
+
) {
|
|
380
|
+
chunks.push(chunk);
|
|
381
|
+
chunk = [];
|
|
382
|
+
chunkBytes = 0;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
chunk.push(write);
|
|
386
|
+
chunkBytes += writeBytes;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (chunk.length > 0) {
|
|
390
|
+
chunks.push(chunk);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return chunks;
|
|
394
|
+
}
|
|
395
|
+
|
|
349
396
|
/**
|
|
350
397
|
* Delete entries with a given location prefix (used for loop forgetting).
|
|
351
398
|
* Also cleans up associated metadata from both memory and driver.
|