@rivetkit/workflow-engine 2.1.6 → 2.1.8
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-JTLDEP6X.js → chunk-4ME2JBMC.js} +542 -210
- package/dist/tsup/chunk-4ME2JBMC.js.map +1 -0
- package/dist/tsup/{chunk-KQO2TD7T.cjs → chunk-OYYWSC77.cjs} +539 -207
- package/dist/tsup/chunk-OYYWSC77.cjs.map +1 -0
- package/dist/tsup/index.cjs +2 -2
- package/dist/tsup/index.cjs.map +1 -1
- package/dist/tsup/index.d.cts +93 -25
- package/dist/tsup/index.d.ts +93 -25
- package/dist/tsup/index.js +7 -7
- package/dist/tsup/testing.cjs +35 -23
- package/dist/tsup/testing.cjs.map +1 -1
- package/dist/tsup/testing.d.cts +2 -1
- package/dist/tsup/testing.d.ts +2 -1
- package/dist/tsup/testing.js +21 -9
- package/dist/tsup/testing.js.map +1 -1
- package/package.json +1 -1
- package/src/context.ts +289 -113
- package/src/driver.ts +5 -0
- package/src/error-utils.ts +87 -0
- package/src/errors.ts +1 -0
- package/src/index.ts +214 -55
- package/src/keys.ts +26 -0
- package/src/location.ts +4 -1
- package/src/storage.ts +73 -20
- package/src/testing.ts +25 -4
- package/src/types.ts +48 -11
- package/dist/tsup/chunk-JTLDEP6X.js.map +0 -1
- package/dist/tsup/chunk-KQO2TD7T.cjs.map +0 -1
|
@@ -1,3 +1,53 @@
|
|
|
1
|
+
// src/error-utils.ts
|
|
2
|
+
var WORKFLOW_ERROR_REPORTED_SYMBOL = /* @__PURE__ */ Symbol("workflow.error.reported");
|
|
3
|
+
function extractErrorInfo(error) {
|
|
4
|
+
if (error instanceof Error) {
|
|
5
|
+
const result = {
|
|
6
|
+
name: error.name,
|
|
7
|
+
message: error.message,
|
|
8
|
+
stack: error.stack
|
|
9
|
+
};
|
|
10
|
+
const metadata = {};
|
|
11
|
+
for (const key of Object.keys(error)) {
|
|
12
|
+
if (key !== "name" && key !== "message" && key !== "stack") {
|
|
13
|
+
const value = error[key];
|
|
14
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
15
|
+
metadata[key] = value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (Object.keys(metadata).length > 0) {
|
|
20
|
+
result.metadata = metadata;
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
name: "Error",
|
|
26
|
+
message: String(error)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function markErrorReported(error) {
|
|
30
|
+
error[WORKFLOW_ERROR_REPORTED_SYMBOL] = true;
|
|
31
|
+
return error;
|
|
32
|
+
}
|
|
33
|
+
function isErrorReported(error) {
|
|
34
|
+
if (!(error instanceof Error)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return Boolean(
|
|
38
|
+
error[WORKFLOW_ERROR_REPORTED_SYMBOL]
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
function getErrorEventTag(event) {
|
|
42
|
+
if ("step" in event) {
|
|
43
|
+
return "step";
|
|
44
|
+
}
|
|
45
|
+
if ("rollback" in event) {
|
|
46
|
+
return "rollback";
|
|
47
|
+
}
|
|
48
|
+
return "workflow";
|
|
49
|
+
}
|
|
50
|
+
|
|
1
51
|
// src/errors.ts
|
|
2
52
|
var CriticalError = class extends Error {
|
|
3
53
|
constructor(message) {
|
|
@@ -63,11 +113,12 @@ var StepExhaustedError = class extends Error {
|
|
|
63
113
|
}
|
|
64
114
|
};
|
|
65
115
|
var StepFailedError = class extends Error {
|
|
66
|
-
constructor(stepName, originalError, attempts) {
|
|
116
|
+
constructor(stepName, originalError, attempts, retryAt) {
|
|
67
117
|
super(`Step "${stepName}" failed (attempt ${attempts})`);
|
|
68
118
|
this.stepName = stepName;
|
|
69
119
|
this.originalError = originalError;
|
|
70
120
|
this.attempts = attempts;
|
|
121
|
+
this.retryAt = retryAt;
|
|
71
122
|
this.name = "StepFailedError";
|
|
72
123
|
this.cause = originalError;
|
|
73
124
|
}
|
|
@@ -101,6 +152,133 @@ var EntryInProgressError = class extends Error {
|
|
|
101
152
|
}
|
|
102
153
|
};
|
|
103
154
|
|
|
155
|
+
// src/keys.ts
|
|
156
|
+
import * as tuple from "fdb-tuple";
|
|
157
|
+
var KEY_PREFIX = {
|
|
158
|
+
NAMES: 1,
|
|
159
|
+
// Name registry: [1, index]
|
|
160
|
+
HISTORY: 2,
|
|
161
|
+
// History entries: [2, ...locationSegments]
|
|
162
|
+
WORKFLOW: 3,
|
|
163
|
+
// Workflow metadata: [3, field]
|
|
164
|
+
ENTRY_METADATA: 4
|
|
165
|
+
// Entry metadata: [4, entryId]
|
|
166
|
+
};
|
|
167
|
+
var WORKFLOW_FIELD = {
|
|
168
|
+
STATE: 1,
|
|
169
|
+
OUTPUT: 2,
|
|
170
|
+
ERROR: 3,
|
|
171
|
+
INPUT: 4
|
|
172
|
+
};
|
|
173
|
+
function segmentToTuple(segment) {
|
|
174
|
+
if (typeof segment === "number") {
|
|
175
|
+
return segment;
|
|
176
|
+
}
|
|
177
|
+
return [segment.loop, segment.iteration];
|
|
178
|
+
}
|
|
179
|
+
function locationToTupleElements(location) {
|
|
180
|
+
return location.map(segmentToTuple);
|
|
181
|
+
}
|
|
182
|
+
function bufferToUint8Array(buf) {
|
|
183
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
184
|
+
}
|
|
185
|
+
function uint8ArrayToBuffer(arr) {
|
|
186
|
+
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
187
|
+
}
|
|
188
|
+
function pack2(items) {
|
|
189
|
+
const buf = tuple.pack(items);
|
|
190
|
+
return bufferToUint8Array(buf);
|
|
191
|
+
}
|
|
192
|
+
function unpack2(data) {
|
|
193
|
+
const buf = uint8ArrayToBuffer(data);
|
|
194
|
+
return tuple.unpack(buf);
|
|
195
|
+
}
|
|
196
|
+
function buildNameKey(index) {
|
|
197
|
+
return pack2([KEY_PREFIX.NAMES, index]);
|
|
198
|
+
}
|
|
199
|
+
function buildNamePrefix() {
|
|
200
|
+
return pack2([KEY_PREFIX.NAMES]);
|
|
201
|
+
}
|
|
202
|
+
function buildHistoryKey(location) {
|
|
203
|
+
return pack2([KEY_PREFIX.HISTORY, ...locationToTupleElements(location)]);
|
|
204
|
+
}
|
|
205
|
+
function buildHistoryPrefix(location) {
|
|
206
|
+
return pack2([KEY_PREFIX.HISTORY, ...locationToTupleElements(location)]);
|
|
207
|
+
}
|
|
208
|
+
function buildLoopIterationRange(loopLocation, loopSegment, fromIteration, toIteration) {
|
|
209
|
+
const loopLocationSegments = locationToTupleElements(loopLocation);
|
|
210
|
+
return {
|
|
211
|
+
start: pack2([
|
|
212
|
+
KEY_PREFIX.HISTORY,
|
|
213
|
+
...loopLocationSegments,
|
|
214
|
+
[loopSegment, fromIteration]
|
|
215
|
+
]),
|
|
216
|
+
end: pack2([
|
|
217
|
+
KEY_PREFIX.HISTORY,
|
|
218
|
+
...loopLocationSegments,
|
|
219
|
+
[loopSegment, toIteration]
|
|
220
|
+
])
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function buildHistoryPrefixAll() {
|
|
224
|
+
return pack2([KEY_PREFIX.HISTORY]);
|
|
225
|
+
}
|
|
226
|
+
function buildWorkflowStateKey() {
|
|
227
|
+
return pack2([KEY_PREFIX.WORKFLOW, WORKFLOW_FIELD.STATE]);
|
|
228
|
+
}
|
|
229
|
+
function buildWorkflowOutputKey() {
|
|
230
|
+
return pack2([KEY_PREFIX.WORKFLOW, WORKFLOW_FIELD.OUTPUT]);
|
|
231
|
+
}
|
|
232
|
+
function buildWorkflowErrorKey() {
|
|
233
|
+
return pack2([KEY_PREFIX.WORKFLOW, WORKFLOW_FIELD.ERROR]);
|
|
234
|
+
}
|
|
235
|
+
function buildWorkflowInputKey() {
|
|
236
|
+
return pack2([KEY_PREFIX.WORKFLOW, WORKFLOW_FIELD.INPUT]);
|
|
237
|
+
}
|
|
238
|
+
function buildEntryMetadataKey(entryId) {
|
|
239
|
+
return pack2([KEY_PREFIX.ENTRY_METADATA, entryId]);
|
|
240
|
+
}
|
|
241
|
+
function buildEntryMetadataPrefix() {
|
|
242
|
+
return pack2([KEY_PREFIX.ENTRY_METADATA]);
|
|
243
|
+
}
|
|
244
|
+
function parseNameKey(key) {
|
|
245
|
+
const elements = unpack2(key);
|
|
246
|
+
if (elements.length !== 2 || elements[0] !== KEY_PREFIX.NAMES) {
|
|
247
|
+
throw new Error("Invalid name key");
|
|
248
|
+
}
|
|
249
|
+
return elements[1];
|
|
250
|
+
}
|
|
251
|
+
function parseEntryMetadataKey(key) {
|
|
252
|
+
const elements = unpack2(key);
|
|
253
|
+
if (elements.length !== 2 || elements[0] !== KEY_PREFIX.ENTRY_METADATA) {
|
|
254
|
+
throw new Error("Invalid entry metadata key");
|
|
255
|
+
}
|
|
256
|
+
return elements[1];
|
|
257
|
+
}
|
|
258
|
+
function keyStartsWith(key, prefix) {
|
|
259
|
+
if (key.length < prefix.length) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
263
|
+
if (key[i] !== prefix[i]) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
function compareKeys(a, b) {
|
|
270
|
+
const minLen = Math.min(a.length, b.length);
|
|
271
|
+
for (let i = 0; i < minLen; i++) {
|
|
272
|
+
if (a[i] !== b[i]) {
|
|
273
|
+
return a[i] - b[i];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return a.length - b.length;
|
|
277
|
+
}
|
|
278
|
+
function keyToHex(key) {
|
|
279
|
+
return Array.from(key).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
280
|
+
}
|
|
281
|
+
|
|
104
282
|
// src/location.ts
|
|
105
283
|
function isLoopIterationMarker(segment) {
|
|
106
284
|
return typeof segment === "object" && "loop" in segment;
|
|
@@ -1223,111 +1401,6 @@ function deserializeName(bytes) {
|
|
|
1223
1401
|
return decoder.decode(bytes);
|
|
1224
1402
|
}
|
|
1225
1403
|
|
|
1226
|
-
// src/keys.ts
|
|
1227
|
-
import * as tuple from "fdb-tuple";
|
|
1228
|
-
var KEY_PREFIX = {
|
|
1229
|
-
NAMES: 1,
|
|
1230
|
-
// Name registry: [1, index]
|
|
1231
|
-
HISTORY: 2,
|
|
1232
|
-
// History entries: [2, ...locationSegments]
|
|
1233
|
-
WORKFLOW: 3,
|
|
1234
|
-
// Workflow metadata: [3, field]
|
|
1235
|
-
ENTRY_METADATA: 4
|
|
1236
|
-
// Entry metadata: [4, entryId]
|
|
1237
|
-
};
|
|
1238
|
-
var WORKFLOW_FIELD = {
|
|
1239
|
-
STATE: 1,
|
|
1240
|
-
OUTPUT: 2,
|
|
1241
|
-
ERROR: 3,
|
|
1242
|
-
INPUT: 4
|
|
1243
|
-
};
|
|
1244
|
-
function segmentToTuple(segment) {
|
|
1245
|
-
if (typeof segment === "number") {
|
|
1246
|
-
return segment;
|
|
1247
|
-
}
|
|
1248
|
-
return [segment.loop, segment.iteration];
|
|
1249
|
-
}
|
|
1250
|
-
function locationToTupleElements(location) {
|
|
1251
|
-
return location.map(segmentToTuple);
|
|
1252
|
-
}
|
|
1253
|
-
function bufferToUint8Array(buf) {
|
|
1254
|
-
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
1255
|
-
}
|
|
1256
|
-
function uint8ArrayToBuffer(arr) {
|
|
1257
|
-
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
1258
|
-
}
|
|
1259
|
-
function pack2(items) {
|
|
1260
|
-
const buf = tuple.pack(items);
|
|
1261
|
-
return bufferToUint8Array(buf);
|
|
1262
|
-
}
|
|
1263
|
-
function unpack2(data) {
|
|
1264
|
-
const buf = uint8ArrayToBuffer(data);
|
|
1265
|
-
return tuple.unpack(buf);
|
|
1266
|
-
}
|
|
1267
|
-
function buildNameKey(index) {
|
|
1268
|
-
return pack2([KEY_PREFIX.NAMES, index]);
|
|
1269
|
-
}
|
|
1270
|
-
function buildNamePrefix() {
|
|
1271
|
-
return pack2([KEY_PREFIX.NAMES]);
|
|
1272
|
-
}
|
|
1273
|
-
function buildHistoryKey(location) {
|
|
1274
|
-
return pack2([KEY_PREFIX.HISTORY, ...locationToTupleElements(location)]);
|
|
1275
|
-
}
|
|
1276
|
-
function buildHistoryPrefix(location) {
|
|
1277
|
-
return pack2([KEY_PREFIX.HISTORY, ...locationToTupleElements(location)]);
|
|
1278
|
-
}
|
|
1279
|
-
function buildHistoryPrefixAll() {
|
|
1280
|
-
return pack2([KEY_PREFIX.HISTORY]);
|
|
1281
|
-
}
|
|
1282
|
-
function buildWorkflowStateKey() {
|
|
1283
|
-
return pack2([KEY_PREFIX.WORKFLOW, WORKFLOW_FIELD.STATE]);
|
|
1284
|
-
}
|
|
1285
|
-
function buildWorkflowOutputKey() {
|
|
1286
|
-
return pack2([KEY_PREFIX.WORKFLOW, WORKFLOW_FIELD.OUTPUT]);
|
|
1287
|
-
}
|
|
1288
|
-
function buildWorkflowErrorKey() {
|
|
1289
|
-
return pack2([KEY_PREFIX.WORKFLOW, WORKFLOW_FIELD.ERROR]);
|
|
1290
|
-
}
|
|
1291
|
-
function buildWorkflowInputKey() {
|
|
1292
|
-
return pack2([KEY_PREFIX.WORKFLOW, WORKFLOW_FIELD.INPUT]);
|
|
1293
|
-
}
|
|
1294
|
-
function buildEntryMetadataKey(entryId) {
|
|
1295
|
-
return pack2([KEY_PREFIX.ENTRY_METADATA, entryId]);
|
|
1296
|
-
}
|
|
1297
|
-
function buildEntryMetadataPrefix() {
|
|
1298
|
-
return pack2([KEY_PREFIX.ENTRY_METADATA]);
|
|
1299
|
-
}
|
|
1300
|
-
function parseNameKey(key) {
|
|
1301
|
-
const elements = unpack2(key);
|
|
1302
|
-
if (elements.length !== 2 || elements[0] !== KEY_PREFIX.NAMES) {
|
|
1303
|
-
throw new Error("Invalid name key");
|
|
1304
|
-
}
|
|
1305
|
-
return elements[1];
|
|
1306
|
-
}
|
|
1307
|
-
function keyStartsWith(key, prefix) {
|
|
1308
|
-
if (key.length < prefix.length) {
|
|
1309
|
-
return false;
|
|
1310
|
-
}
|
|
1311
|
-
for (let i = 0; i < prefix.length; i++) {
|
|
1312
|
-
if (key[i] !== prefix[i]) {
|
|
1313
|
-
return false;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
return true;
|
|
1317
|
-
}
|
|
1318
|
-
function compareKeys(a, b) {
|
|
1319
|
-
const minLen = Math.min(a.length, b.length);
|
|
1320
|
-
for (let i = 0; i < minLen; i++) {
|
|
1321
|
-
if (a[i] !== b[i]) {
|
|
1322
|
-
return a[i] - b[i];
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
return a.length - b.length;
|
|
1326
|
-
}
|
|
1327
|
-
function keyToHex(key) {
|
|
1328
|
-
return Array.from(key).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
1404
|
// src/storage.ts
|
|
1332
1405
|
function createStorage() {
|
|
1333
1406
|
return {
|
|
@@ -1406,6 +1479,13 @@ async function loadStorage(driver) {
|
|
|
1406
1479
|
const key = locationToKey(storage, parsed.location);
|
|
1407
1480
|
storage.history.entries.set(key, parsed);
|
|
1408
1481
|
}
|
|
1482
|
+
const metadataEntries = await driver.list(buildEntryMetadataPrefix());
|
|
1483
|
+
for (const entry of metadataEntries) {
|
|
1484
|
+
const entryId = parseEntryMetadataKey(entry.key);
|
|
1485
|
+
const metadata = deserializeEntryMetadata(entry.value);
|
|
1486
|
+
metadata.dirty = false;
|
|
1487
|
+
storage.entryMetadata.set(entryId, metadata);
|
|
1488
|
+
}
|
|
1409
1489
|
const stateValue = await driver.get(buildWorkflowStateKey());
|
|
1410
1490
|
if (stateValue) {
|
|
1411
1491
|
storage.state = deserializeWorkflowState(stateValue);
|
|
@@ -1437,7 +1517,7 @@ async function loadMetadata(storage, driver, entryId) {
|
|
|
1437
1517
|
}
|
|
1438
1518
|
return getOrCreateMetadata(storage, entryId);
|
|
1439
1519
|
}
|
|
1440
|
-
async function flush(storage, driver, onHistoryUpdated) {
|
|
1520
|
+
async function flush(storage, driver, onHistoryUpdated, pendingDeletions) {
|
|
1441
1521
|
const writes = [];
|
|
1442
1522
|
let historyUpdated = false;
|
|
1443
1523
|
for (let i = storage.flushedNameCount; i < storage.nameRegistry.length; i++) {
|
|
@@ -1492,6 +1572,22 @@ async function flush(storage, driver, onHistoryUpdated) {
|
|
|
1492
1572
|
if (writes.length > 0) {
|
|
1493
1573
|
await driver.batch(writes);
|
|
1494
1574
|
}
|
|
1575
|
+
if (pendingDeletions) {
|
|
1576
|
+
const deleteOps = [];
|
|
1577
|
+
for (const prefix of pendingDeletions.prefixes) {
|
|
1578
|
+
deleteOps.push(driver.deletePrefix(prefix));
|
|
1579
|
+
}
|
|
1580
|
+
for (const range of pendingDeletions.ranges) {
|
|
1581
|
+
deleteOps.push(driver.deleteRange(range.start, range.end));
|
|
1582
|
+
}
|
|
1583
|
+
for (const key of pendingDeletions.keys) {
|
|
1584
|
+
deleteOps.push(driver.delete(key));
|
|
1585
|
+
}
|
|
1586
|
+
if (deleteOps.length > 0) {
|
|
1587
|
+
await Promise.all(deleteOps);
|
|
1588
|
+
historyUpdated = true;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1495
1591
|
storage.flushedNameCount = storage.nameRegistry.length;
|
|
1496
1592
|
storage.flushedState = storage.state;
|
|
1497
1593
|
storage.flushedOutput = storage.output;
|
|
@@ -1501,21 +1597,27 @@ async function flush(storage, driver, onHistoryUpdated) {
|
|
|
1501
1597
|
}
|
|
1502
1598
|
}
|
|
1503
1599
|
async function deleteEntriesWithPrefix(storage, driver, prefixLocation, onHistoryUpdated) {
|
|
1504
|
-
const
|
|
1600
|
+
const deletions = collectDeletionsForPrefix(storage, prefixLocation);
|
|
1601
|
+
await driver.deletePrefix(deletions.prefixes[0]);
|
|
1602
|
+
await Promise.all(deletions.keys.map((key) => driver.delete(key)));
|
|
1603
|
+
if (deletions.keys.length > 0 && onHistoryUpdated) {
|
|
1604
|
+
onHistoryUpdated();
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
function collectDeletionsForPrefix(storage, prefixLocation) {
|
|
1608
|
+
const pending = {
|
|
1609
|
+
prefixes: [buildHistoryPrefix(prefixLocation)],
|
|
1610
|
+
keys: [],
|
|
1611
|
+
ranges: []
|
|
1612
|
+
};
|
|
1505
1613
|
for (const [key, entry] of storage.history.entries) {
|
|
1506
1614
|
if (isLocationPrefix(prefixLocation, entry.location)) {
|
|
1507
|
-
|
|
1615
|
+
pending.keys.push(buildEntryMetadataKey(entry.id));
|
|
1508
1616
|
storage.entryMetadata.delete(entry.id);
|
|
1509
1617
|
storage.history.entries.delete(key);
|
|
1510
1618
|
}
|
|
1511
1619
|
}
|
|
1512
|
-
|
|
1513
|
-
await Promise.all(
|
|
1514
|
-
entryIds.map((id) => driver.delete(buildEntryMetadataKey(id)))
|
|
1515
|
-
);
|
|
1516
|
-
if (entryIds.length > 0 && onHistoryUpdated) {
|
|
1517
|
-
onHistoryUpdated();
|
|
1518
|
-
}
|
|
1620
|
+
return pending;
|
|
1519
1621
|
}
|
|
1520
1622
|
function getEntry(storage, location) {
|
|
1521
1623
|
const key = locationToKey(storage, location);
|
|
@@ -1554,9 +1656,7 @@ function setLongTimeout(listener, after) {
|
|
|
1554
1656
|
var DEFAULT_MAX_RETRIES = 3;
|
|
1555
1657
|
var DEFAULT_RETRY_BACKOFF_BASE = 100;
|
|
1556
1658
|
var DEFAULT_RETRY_BACKOFF_MAX = 3e4;
|
|
1557
|
-
var
|
|
1558
|
-
var DEFAULT_LOOP_HISTORY_EVERY = 20;
|
|
1559
|
-
var DEFAULT_LOOP_HISTORY_KEEP = 20;
|
|
1659
|
+
var DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL = 20;
|
|
1560
1660
|
var DEFAULT_STEP_TIMEOUT = 3e4;
|
|
1561
1661
|
var QUEUE_HISTORY_MESSAGE_MARKER = "__rivetWorkflowQueueMessage";
|
|
1562
1662
|
function calculateBackoff(attempts, base, max) {
|
|
@@ -1571,7 +1671,7 @@ var StepTimeoutError = class extends Error {
|
|
|
1571
1671
|
}
|
|
1572
1672
|
};
|
|
1573
1673
|
var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
1574
|
-
constructor(workflowId, storage, driver, messageDriver, location = emptyLocation(), abortController, mode = "forward", rollbackActions, rollbackCheckpointSet = false, historyNotifier, logger) {
|
|
1674
|
+
constructor(workflowId, storage, driver, messageDriver, location = emptyLocation(), abortController, mode = "forward", rollbackActions, rollbackCheckpointSet = false, historyNotifier, onError, logger, visitedKeys) {
|
|
1575
1675
|
this.workflowId = workflowId;
|
|
1576
1676
|
this.storage = storage;
|
|
1577
1677
|
this.driver = driver;
|
|
@@ -1582,12 +1682,14 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
1582
1682
|
this.rollbackActions = rollbackActions;
|
|
1583
1683
|
this.rollbackCheckpointSet = rollbackCheckpointSet;
|
|
1584
1684
|
this.historyNotifier = historyNotifier;
|
|
1685
|
+
this.onError = onError;
|
|
1585
1686
|
this.logger = logger;
|
|
1687
|
+
this.visitedKeys = visitedKeys ?? /* @__PURE__ */ new Set();
|
|
1586
1688
|
}
|
|
1587
1689
|
entryInProgress = false;
|
|
1588
1690
|
abortController;
|
|
1589
1691
|
currentLocation;
|
|
1590
|
-
visitedKeys
|
|
1692
|
+
visitedKeys;
|
|
1591
1693
|
mode;
|
|
1592
1694
|
rollbackActions;
|
|
1593
1695
|
rollbackCheckpointSet;
|
|
@@ -1595,6 +1697,7 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
1595
1697
|
usedNamesInExecution = /* @__PURE__ */ new Set();
|
|
1596
1698
|
pendingCompletableMessageIds = /* @__PURE__ */ new Set();
|
|
1597
1699
|
historyNotifier;
|
|
1700
|
+
onError;
|
|
1598
1701
|
logger;
|
|
1599
1702
|
get abortSignal() {
|
|
1600
1703
|
return this.abortController.signal;
|
|
@@ -1637,7 +1740,9 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
1637
1740
|
this.rollbackActions,
|
|
1638
1741
|
this.rollbackCheckpointSet,
|
|
1639
1742
|
this.historyNotifier,
|
|
1640
|
-
this.
|
|
1743
|
+
this.onError,
|
|
1744
|
+
this.logger,
|
|
1745
|
+
this.visitedKeys
|
|
1641
1746
|
);
|
|
1642
1747
|
}
|
|
1643
1748
|
/**
|
|
@@ -1647,6 +1752,36 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
1647
1752
|
if (!this.logger) return;
|
|
1648
1753
|
this.logger[level](data);
|
|
1649
1754
|
}
|
|
1755
|
+
async notifyError(event) {
|
|
1756
|
+
if (!this.onError) {
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
try {
|
|
1760
|
+
await this.onError(event);
|
|
1761
|
+
} catch (error) {
|
|
1762
|
+
this.log("warn", {
|
|
1763
|
+
msg: "workflow error hook failed",
|
|
1764
|
+
hookEventType: getErrorEventTag(event),
|
|
1765
|
+
error: extractErrorInfo(error)
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
async notifyStepError(config2, attempt, error, opts) {
|
|
1770
|
+
const maxRetries = config2.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
1771
|
+
await this.notifyError({
|
|
1772
|
+
step: {
|
|
1773
|
+
workflowId: this.workflowId,
|
|
1774
|
+
stepName: config2.name,
|
|
1775
|
+
attempt,
|
|
1776
|
+
maxRetries,
|
|
1777
|
+
remainingRetries: Math.max(0, maxRetries - (attempt - 1)),
|
|
1778
|
+
willRetry: opts.willRetry,
|
|
1779
|
+
retryDelay: opts.retryDelay,
|
|
1780
|
+
retryAt: opts.retryAt,
|
|
1781
|
+
error: extractErrorInfo(error)
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1650
1785
|
/**
|
|
1651
1786
|
* Mark a key as visited.
|
|
1652
1787
|
*/
|
|
@@ -1790,10 +1925,24 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
1790
1925
|
if (metadata2.status === "completed" || stepData.output !== void 0) {
|
|
1791
1926
|
return stepData.output;
|
|
1792
1927
|
}
|
|
1793
|
-
const
|
|
1794
|
-
if (metadata2.attempts
|
|
1928
|
+
const maxRetries2 = config2.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
1929
|
+
if (metadata2.attempts > maxRetries2) {
|
|
1795
1930
|
const lastError = stepData.error ?? metadata2.error;
|
|
1796
|
-
|
|
1931
|
+
const exhaustedError = markErrorReported(
|
|
1932
|
+
new StepExhaustedError(config2.name, lastError)
|
|
1933
|
+
);
|
|
1934
|
+
if (metadata2.status !== "exhausted") {
|
|
1935
|
+
metadata2.status = "exhausted";
|
|
1936
|
+
metadata2.dirty = true;
|
|
1937
|
+
await this.flushStorage();
|
|
1938
|
+
await this.notifyStepError(
|
|
1939
|
+
config2,
|
|
1940
|
+
metadata2.attempts,
|
|
1941
|
+
exhaustedError,
|
|
1942
|
+
{ willRetry: false }
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
throw exhaustedError;
|
|
1797
1946
|
}
|
|
1798
1947
|
const backoffDelay = calculateBackoff(
|
|
1799
1948
|
metadata2.attempts,
|
|
@@ -1808,7 +1957,11 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
1808
1957
|
}
|
|
1809
1958
|
const entry = existing ?? createEntry(location, { type: "step", data: {} });
|
|
1810
1959
|
if (!existing) {
|
|
1811
|
-
this.log("debug", {
|
|
1960
|
+
this.log("debug", {
|
|
1961
|
+
msg: "executing new step",
|
|
1962
|
+
step: config2.name,
|
|
1963
|
+
key
|
|
1964
|
+
});
|
|
1812
1965
|
const nameIndex = registerName(this.storage, config2.name);
|
|
1813
1966
|
entry.location = [...location];
|
|
1814
1967
|
entry.location[entry.location.length - 1] = nameIndex;
|
|
@@ -1817,6 +1970,9 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
1817
1970
|
this.log("debug", { msg: "retrying step", step: config2.name, key });
|
|
1818
1971
|
}
|
|
1819
1972
|
const metadata = getOrCreateMetadata(this.storage, entry.id);
|
|
1973
|
+
const maxRetries = config2.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
1974
|
+
const retryBackoffBase = config2.retryBackoffBase ?? DEFAULT_RETRY_BACKOFF_BASE;
|
|
1975
|
+
const retryBackoffMax = config2.retryBackoffMax ?? DEFAULT_RETRY_BACKOFF_MAX;
|
|
1820
1976
|
metadata.status = "running";
|
|
1821
1977
|
metadata.attempts++;
|
|
1822
1978
|
metadata.lastAttemptAt = Date.now();
|
|
@@ -1836,10 +1992,18 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
1836
1992
|
metadata.error = void 0;
|
|
1837
1993
|
metadata.completedAt = Date.now();
|
|
1838
1994
|
if (!config2.ephemeral) {
|
|
1839
|
-
this.log("debug", {
|
|
1995
|
+
this.log("debug", {
|
|
1996
|
+
msg: "flushing step",
|
|
1997
|
+
step: config2.name,
|
|
1998
|
+
key
|
|
1999
|
+
});
|
|
1840
2000
|
await this.flushStorage();
|
|
1841
2001
|
}
|
|
1842
|
-
this.log("debug", {
|
|
2002
|
+
this.log("debug", {
|
|
2003
|
+
msg: "step completed",
|
|
2004
|
+
step: config2.name,
|
|
2005
|
+
key
|
|
2006
|
+
});
|
|
1843
2007
|
return output;
|
|
1844
2008
|
} catch (error) {
|
|
1845
2009
|
if (error instanceof StepTimeoutError) {
|
|
@@ -1850,7 +2014,10 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
1850
2014
|
metadata.status = "exhausted";
|
|
1851
2015
|
metadata.error = String(error);
|
|
1852
2016
|
await this.flushStorage();
|
|
1853
|
-
|
|
2017
|
+
await this.notifyStepError(config2, metadata.attempts, error, {
|
|
2018
|
+
willRetry: false
|
|
2019
|
+
});
|
|
2020
|
+
throw markErrorReported(new CriticalError(error.message));
|
|
1854
2021
|
}
|
|
1855
2022
|
if (error instanceof CriticalError || error instanceof RollbackError) {
|
|
1856
2023
|
if (entry.kind.type === "step") {
|
|
@@ -1860,16 +2027,45 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
1860
2027
|
metadata.status = "exhausted";
|
|
1861
2028
|
metadata.error = String(error);
|
|
1862
2029
|
await this.flushStorage();
|
|
1863
|
-
|
|
2030
|
+
await this.notifyStepError(config2, metadata.attempts, error, {
|
|
2031
|
+
willRetry: false
|
|
2032
|
+
});
|
|
2033
|
+
throw markErrorReported(error);
|
|
1864
2034
|
}
|
|
1865
2035
|
if (entry.kind.type === "step") {
|
|
1866
2036
|
entry.kind.data.error = String(error);
|
|
1867
2037
|
}
|
|
1868
2038
|
entry.dirty = true;
|
|
1869
|
-
metadata.
|
|
2039
|
+
const willRetry = metadata.attempts <= maxRetries;
|
|
2040
|
+
metadata.status = willRetry ? "failed" : "exhausted";
|
|
1870
2041
|
metadata.error = String(error);
|
|
1871
2042
|
await this.flushStorage();
|
|
1872
|
-
|
|
2043
|
+
if (willRetry) {
|
|
2044
|
+
const retryDelay = calculateBackoff(
|
|
2045
|
+
metadata.attempts,
|
|
2046
|
+
retryBackoffBase,
|
|
2047
|
+
retryBackoffMax
|
|
2048
|
+
);
|
|
2049
|
+
const retryAt = metadata.lastAttemptAt + retryDelay;
|
|
2050
|
+
await this.notifyStepError(config2, metadata.attempts, error, {
|
|
2051
|
+
willRetry: true,
|
|
2052
|
+
retryDelay,
|
|
2053
|
+
retryAt
|
|
2054
|
+
});
|
|
2055
|
+
throw new StepFailedError(
|
|
2056
|
+
config2.name,
|
|
2057
|
+
error,
|
|
2058
|
+
metadata.attempts,
|
|
2059
|
+
retryAt
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
const exhaustedError = markErrorReported(
|
|
2063
|
+
new StepExhaustedError(config2.name, String(error))
|
|
2064
|
+
);
|
|
2065
|
+
await this.notifyStepError(config2, metadata.attempts, error, {
|
|
2066
|
+
willRetry: false
|
|
2067
|
+
});
|
|
2068
|
+
throw exhaustedError;
|
|
1873
2069
|
}
|
|
1874
2070
|
}
|
|
1875
2071
|
/**
|
|
@@ -1970,7 +2166,11 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
1970
2166
|
);
|
|
1971
2167
|
}
|
|
1972
2168
|
const loopData = existing.kind.data;
|
|
1973
|
-
metadata = await loadMetadata(
|
|
2169
|
+
metadata = await loadMetadata(
|
|
2170
|
+
this.storage,
|
|
2171
|
+
this.driver,
|
|
2172
|
+
existing.id
|
|
2173
|
+
);
|
|
1974
2174
|
if (rollbackMode) {
|
|
1975
2175
|
if (loopData.output !== void 0) {
|
|
1976
2176
|
return loopData.output;
|
|
@@ -2008,10 +2208,15 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
2008
2208
|
metadata.error = void 0;
|
|
2009
2209
|
metadata.dirty = true;
|
|
2010
2210
|
}
|
|
2011
|
-
const
|
|
2012
|
-
const
|
|
2013
|
-
|
|
2211
|
+
const historyPruneInterval = config2.historyPruneInterval ?? DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL;
|
|
2212
|
+
const historySize = config2.historySize ?? historyPruneInterval;
|
|
2213
|
+
let lastPrunedUpTo = 0;
|
|
2214
|
+
let deferredFlush = null;
|
|
2014
2215
|
while (true) {
|
|
2216
|
+
if (deferredFlush) {
|
|
2217
|
+
await deferredFlush;
|
|
2218
|
+
deferredFlush = null;
|
|
2219
|
+
}
|
|
2015
2220
|
if (rollbackMode && rollbackSingleIteration) {
|
|
2016
2221
|
if (rollbackIterationRan) {
|
|
2017
2222
|
return rollbackOutput;
|
|
@@ -2046,13 +2251,13 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
2046
2251
|
metadata.completedAt = Date.now();
|
|
2047
2252
|
metadata.dirty = true;
|
|
2048
2253
|
}
|
|
2049
|
-
|
|
2050
|
-
await this.forgetOldIterations(
|
|
2254
|
+
const deletions = this.collectLoopPruning(
|
|
2051
2255
|
location,
|
|
2052
2256
|
iteration + 1,
|
|
2053
|
-
|
|
2054
|
-
|
|
2257
|
+
historySize,
|
|
2258
|
+
lastPrunedUpTo
|
|
2055
2259
|
);
|
|
2260
|
+
await this.flushStorageWithDeletions(deletions);
|
|
2056
2261
|
if (rollbackMode && rollbackSingleIteration) {
|
|
2057
2262
|
rollbackOutput = result.value;
|
|
2058
2263
|
rollbackIterationRan = true;
|
|
@@ -2064,60 +2269,75 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
|
|
|
2064
2269
|
state = result.state;
|
|
2065
2270
|
}
|
|
2066
2271
|
iteration++;
|
|
2067
|
-
if (
|
|
2272
|
+
if (!rollbackMode) {
|
|
2068
2273
|
if (entry.kind.type === "loop") {
|
|
2069
2274
|
entry.kind.data.state = state;
|
|
2070
2275
|
entry.kind.data.iteration = iteration;
|
|
2071
2276
|
}
|
|
2072
2277
|
entry.dirty = true;
|
|
2073
|
-
|
|
2074
|
-
|
|
2278
|
+
}
|
|
2279
|
+
if (iteration % historyPruneInterval === 0) {
|
|
2280
|
+
const deletions = this.collectLoopPruning(
|
|
2075
2281
|
location,
|
|
2076
2282
|
iteration,
|
|
2077
|
-
|
|
2078
|
-
|
|
2283
|
+
historySize,
|
|
2284
|
+
lastPrunedUpTo
|
|
2079
2285
|
);
|
|
2286
|
+
lastPrunedUpTo = Math.max(0, iteration - historySize);
|
|
2287
|
+
deferredFlush = this.flushStorageWithDeletions(deletions);
|
|
2080
2288
|
}
|
|
2081
2289
|
}
|
|
2082
2290
|
}
|
|
2083
2291
|
/**
|
|
2084
|
-
*
|
|
2085
|
-
*
|
|
2086
|
-
* Loop locations always end with a NameIndex (number) because loops are
|
|
2087
|
-
* created via appendName(). Even for nested loops, the innermost loop's
|
|
2088
|
-
* location ends with its name index:
|
|
2292
|
+
* Collect pending deletions for loop history pruning.
|
|
2089
2293
|
*
|
|
2090
|
-
*
|
|
2091
|
-
*
|
|
2092
|
-
*
|
|
2093
|
-
*
|
|
2094
|
-
* This function removes iterations older than (currentIteration - historyKeep)
|
|
2095
|
-
* every historyEvery iterations.
|
|
2294
|
+
* Only deletes iterations in the range [fromIteration, keepFrom) where
|
|
2295
|
+
* keepFrom = currentIteration - historySize. This avoids re-scanning
|
|
2296
|
+
* already-deleted iterations.
|
|
2096
2297
|
*/
|
|
2097
|
-
|
|
2098
|
-
if (
|
|
2099
|
-
return;
|
|
2298
|
+
collectLoopPruning(loopLocation, currentIteration, historySize, fromIteration) {
|
|
2299
|
+
if (currentIteration <= historySize) {
|
|
2300
|
+
return void 0;
|
|
2100
2301
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2302
|
+
const keepFrom = Math.max(0, currentIteration - historySize);
|
|
2303
|
+
if (fromIteration >= keepFrom) {
|
|
2304
|
+
return void 0;
|
|
2103
2305
|
}
|
|
2104
|
-
const keepFrom = Math.max(0, currentIteration - historyKeep);
|
|
2105
2306
|
const loopSegment = loopLocation[loopLocation.length - 1];
|
|
2106
2307
|
if (typeof loopSegment !== "number") {
|
|
2107
2308
|
throw new Error("Expected loop location to end with a name index");
|
|
2108
2309
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2310
|
+
const range = buildLoopIterationRange(
|
|
2311
|
+
loopLocation,
|
|
2312
|
+
loopSegment,
|
|
2313
|
+
fromIteration,
|
|
2314
|
+
keepFrom
|
|
2315
|
+
);
|
|
2316
|
+
const metadataKeys = [];
|
|
2317
|
+
for (const [key, entry] of this.storage.history.entries) {
|
|
2318
|
+
if (!isLocationPrefix(loopLocation, entry.location)) {
|
|
2319
|
+
continue;
|
|
2320
|
+
}
|
|
2321
|
+
const iterationSegment = entry.location[loopLocation.length];
|
|
2322
|
+
if (!iterationSegment || typeof iterationSegment === "number" || iterationSegment.loop !== loopSegment || iterationSegment.iteration < fromIteration || iterationSegment.iteration >= keepFrom) {
|
|
2323
|
+
continue;
|
|
2324
|
+
}
|
|
2325
|
+
metadataKeys.push(buildEntryMetadataKey(entry.id));
|
|
2326
|
+
this.storage.entryMetadata.delete(entry.id);
|
|
2327
|
+
this.storage.history.entries.delete(key);
|
|
2120
2328
|
}
|
|
2329
|
+
return {
|
|
2330
|
+
prefixes: [],
|
|
2331
|
+
keys: metadataKeys,
|
|
2332
|
+
ranges: [range]
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Flush storage with optional pending deletions so pruning
|
|
2337
|
+
* happens alongside the state write.
|
|
2338
|
+
*/
|
|
2339
|
+
async flushStorageWithDeletions(deletions) {
|
|
2340
|
+
await flush(this.storage, this.driver, this.historyNotifier, deletions);
|
|
2121
2341
|
}
|
|
2122
2342
|
// === Sleep ===
|
|
2123
2343
|
async sleep(name, durationMs) {
|
|
@@ -2945,7 +3165,7 @@ async function awaitWithEviction(promise, abortSignal) {
|
|
|
2945
3165
|
cleanup();
|
|
2946
3166
|
}
|
|
2947
3167
|
}
|
|
2948
|
-
async function executeRollback(workflowId, workflowFn, input, driver, messageDriver, abortController, storage, historyNotifier, logger) {
|
|
3168
|
+
async function executeRollback(workflowId, workflowFn, input, driver, messageDriver, abortController, storage, historyNotifier, onError, logger) {
|
|
2949
3169
|
const rollbackActions = [];
|
|
2950
3170
|
const ctx = new WorkflowContextImpl(
|
|
2951
3171
|
workflowId,
|
|
@@ -2958,6 +3178,7 @@ async function executeRollback(workflowId, workflowFn, input, driver, messageDri
|
|
|
2958
3178
|
rollbackActions,
|
|
2959
3179
|
false,
|
|
2960
3180
|
historyNotifier,
|
|
3181
|
+
onError,
|
|
2961
3182
|
logger
|
|
2962
3183
|
);
|
|
2963
3184
|
try {
|
|
@@ -2983,6 +3204,7 @@ async function executeRollback(workflowId, workflowFn, input, driver, messageDri
|
|
|
2983
3204
|
if (metadata.rollbackCompletedAt !== void 0) {
|
|
2984
3205
|
continue;
|
|
2985
3206
|
}
|
|
3207
|
+
let rollbackEvent;
|
|
2986
3208
|
try {
|
|
2987
3209
|
await awaitWithEviction(
|
|
2988
3210
|
action.rollback(rollbackContext, action.output),
|
|
@@ -2995,13 +3217,39 @@ async function executeRollback(workflowId, workflowFn, input, driver, messageDri
|
|
|
2995
3217
|
throw error;
|
|
2996
3218
|
}
|
|
2997
3219
|
metadata.rollbackError = error instanceof Error ? error.message : String(error);
|
|
3220
|
+
if (onError) {
|
|
3221
|
+
rollbackEvent = {
|
|
3222
|
+
rollback: {
|
|
3223
|
+
workflowId,
|
|
3224
|
+
stepName: action.name,
|
|
3225
|
+
error: extractErrorInfo(error)
|
|
3226
|
+
}
|
|
3227
|
+
};
|
|
3228
|
+
}
|
|
3229
|
+
if (error instanceof Error) {
|
|
3230
|
+
markErrorReported(error);
|
|
3231
|
+
}
|
|
2998
3232
|
throw error;
|
|
2999
3233
|
} finally {
|
|
3000
3234
|
metadata.dirty = true;
|
|
3001
3235
|
await flush(storage, driver, historyNotifier);
|
|
3236
|
+
if (rollbackEvent && onError) {
|
|
3237
|
+
await notifyError(onError, logger, rollbackEvent);
|
|
3238
|
+
}
|
|
3002
3239
|
}
|
|
3003
3240
|
}
|
|
3004
3241
|
}
|
|
3242
|
+
async function notifyError(onError, logger, event) {
|
|
3243
|
+
try {
|
|
3244
|
+
await onError(event);
|
|
3245
|
+
} catch (error) {
|
|
3246
|
+
logger == null ? void 0 : logger.warn({
|
|
3247
|
+
msg: "workflow error hook failed",
|
|
3248
|
+
hookEventType: getErrorEventTag(event),
|
|
3249
|
+
error: extractErrorInfo(error)
|
|
3250
|
+
});
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3005
3253
|
async function setSleepState(storage, driver, workflowId, deadline, messageNames, historyNotifier) {
|
|
3006
3254
|
storage.state = "sleeping";
|
|
3007
3255
|
await flush(storage, driver, historyNotifier);
|
|
@@ -3021,10 +3269,9 @@ async function setEvictedState(storage, driver, historyNotifier) {
|
|
|
3021
3269
|
await flush(storage, driver, historyNotifier);
|
|
3022
3270
|
return { state: storage.state };
|
|
3023
3271
|
}
|
|
3024
|
-
async function setRetryState(storage, driver, workflowId, historyNotifier) {
|
|
3272
|
+
async function setRetryState(storage, driver, workflowId, retryAt, historyNotifier) {
|
|
3025
3273
|
storage.state = "sleeping";
|
|
3026
3274
|
await flush(storage, driver, historyNotifier);
|
|
3027
|
-
const retryAt = Date.now() + 100;
|
|
3028
3275
|
await driver.setAlarm(workflowId, retryAt);
|
|
3029
3276
|
return { state: "sleeping", sleepUntil: retryAt };
|
|
3030
3277
|
}
|
|
@@ -3065,7 +3312,7 @@ async function waitForSleep(runtime, deadline, abortSignal) {
|
|
|
3065
3312
|
}
|
|
3066
3313
|
}
|
|
3067
3314
|
}
|
|
3068
|
-
async function executeLiveWorkflow(workflowId, workflowFn, input, driver, messageDriver, abortController, runtime, onHistoryUpdated, logger) {
|
|
3315
|
+
async function executeLiveWorkflow(workflowId, workflowFn, input, driver, messageDriver, abortController, runtime, onHistoryUpdated, onError, logger) {
|
|
3069
3316
|
let lastResult;
|
|
3070
3317
|
while (true) {
|
|
3071
3318
|
const result = await executeWorkflow(
|
|
@@ -3076,6 +3323,7 @@ async function executeLiveWorkflow(workflowId, workflowFn, input, driver, messag
|
|
|
3076
3323
|
messageDriver,
|
|
3077
3324
|
abortController,
|
|
3078
3325
|
onHistoryUpdated,
|
|
3326
|
+
onError,
|
|
3079
3327
|
logger
|
|
3080
3328
|
);
|
|
3081
3329
|
lastResult = result;
|
|
@@ -3157,6 +3405,7 @@ function runWorkflow(workflowId, workflowFn, input, driver, options = {}) {
|
|
|
3157
3405
|
abortController,
|
|
3158
3406
|
liveRuntime,
|
|
3159
3407
|
options.onHistoryUpdated,
|
|
3408
|
+
options.onError,
|
|
3160
3409
|
logger
|
|
3161
3410
|
) : executeWorkflow(
|
|
3162
3411
|
workflowId,
|
|
@@ -3166,6 +3415,7 @@ function runWorkflow(workflowId, workflowFn, input, driver, options = {}) {
|
|
|
3166
3415
|
messageDriver,
|
|
3167
3416
|
abortController,
|
|
3168
3417
|
options.onHistoryUpdated,
|
|
3418
|
+
options.onError,
|
|
3169
3419
|
logger
|
|
3170
3420
|
);
|
|
3171
3421
|
return {
|
|
@@ -3257,7 +3507,92 @@ function runWorkflow(workflowId, workflowFn, input, driver, options = {}) {
|
|
|
3257
3507
|
}
|
|
3258
3508
|
};
|
|
3259
3509
|
}
|
|
3260
|
-
async function
|
|
3510
|
+
async function replayWorkflowFromStep(workflowId, driver, entryId, options) {
|
|
3511
|
+
const storage = await loadStorage(driver);
|
|
3512
|
+
const entries = await Promise.all(
|
|
3513
|
+
Array.from(storage.history.entries.entries()).map(
|
|
3514
|
+
async ([key, entry]) => ({
|
|
3515
|
+
key,
|
|
3516
|
+
entry,
|
|
3517
|
+
metadata: await loadMetadata(storage, driver, entry.id)
|
|
3518
|
+
})
|
|
3519
|
+
)
|
|
3520
|
+
);
|
|
3521
|
+
const ordered = [...entries].sort((a, b) => {
|
|
3522
|
+
if (a.metadata.createdAt !== b.metadata.createdAt) {
|
|
3523
|
+
return a.metadata.createdAt - b.metadata.createdAt;
|
|
3524
|
+
}
|
|
3525
|
+
return a.key.localeCompare(b.key);
|
|
3526
|
+
});
|
|
3527
|
+
let entriesToDelete = ordered;
|
|
3528
|
+
if (entryId !== void 0) {
|
|
3529
|
+
const target = entries.find(({ entry }) => entry.id === entryId);
|
|
3530
|
+
if (!target) {
|
|
3531
|
+
throw new Error(`Workflow step not found: ${entryId}`);
|
|
3532
|
+
}
|
|
3533
|
+
if (target.entry.kind.type !== "step") {
|
|
3534
|
+
throw new Error("Workflow replay target must be a step");
|
|
3535
|
+
}
|
|
3536
|
+
const replayBoundary = findReplayBoundaryEntry(entries, target);
|
|
3537
|
+
const targetIndex = ordered.findIndex(
|
|
3538
|
+
({ entry }) => entry.id === replayBoundary.entry.id
|
|
3539
|
+
);
|
|
3540
|
+
entriesToDelete = ordered.slice(targetIndex);
|
|
3541
|
+
}
|
|
3542
|
+
const entryIdsToDelete = new Set(
|
|
3543
|
+
entriesToDelete.map(({ entry }) => entry.id)
|
|
3544
|
+
);
|
|
3545
|
+
if (entries.some(
|
|
3546
|
+
({ entry, metadata }) => metadata.status === "running" && !entryIdsToDelete.has(entry.id)
|
|
3547
|
+
)) {
|
|
3548
|
+
throw new Error(
|
|
3549
|
+
"Cannot replay a workflow while a step is currently running"
|
|
3550
|
+
);
|
|
3551
|
+
}
|
|
3552
|
+
await Promise.all(
|
|
3553
|
+
entriesToDelete.flatMap(({ entry }) => [
|
|
3554
|
+
driver.delete(buildHistoryKey(entry.location)),
|
|
3555
|
+
driver.delete(buildEntryMetadataKey(entry.id))
|
|
3556
|
+
])
|
|
3557
|
+
);
|
|
3558
|
+
for (const { key, entry } of entriesToDelete) {
|
|
3559
|
+
storage.history.entries.delete(key);
|
|
3560
|
+
storage.entryMetadata.delete(entry.id);
|
|
3561
|
+
}
|
|
3562
|
+
storage.output = void 0;
|
|
3563
|
+
storage.flushedOutput = void 0;
|
|
3564
|
+
storage.error = void 0;
|
|
3565
|
+
storage.flushedError = void 0;
|
|
3566
|
+
storage.state = "sleeping";
|
|
3567
|
+
storage.flushedState = "sleeping";
|
|
3568
|
+
await Promise.all([
|
|
3569
|
+
driver.delete(buildWorkflowOutputKey()),
|
|
3570
|
+
driver.delete(buildWorkflowErrorKey()),
|
|
3571
|
+
driver.set(buildWorkflowStateKey(), serializeWorkflowState("sleeping"))
|
|
3572
|
+
]);
|
|
3573
|
+
if ((options == null ? void 0 : options.scheduleAlarm) ?? true) {
|
|
3574
|
+
await driver.setAlarm(workflowId, Date.now());
|
|
3575
|
+
}
|
|
3576
|
+
return createHistorySnapshot(storage);
|
|
3577
|
+
}
|
|
3578
|
+
function findReplayBoundaryEntry(entries, target) {
|
|
3579
|
+
let boundary = target;
|
|
3580
|
+
let boundaryDepth = -1;
|
|
3581
|
+
for (const candidate of entries) {
|
|
3582
|
+
if (candidate.entry.kind.type !== "loop") {
|
|
3583
|
+
continue;
|
|
3584
|
+
}
|
|
3585
|
+
if (candidate.entry.location.length >= target.entry.location.length || !isLocationPrefix(candidate.entry.location, target.entry.location)) {
|
|
3586
|
+
continue;
|
|
3587
|
+
}
|
|
3588
|
+
if (candidate.entry.location.length > boundaryDepth) {
|
|
3589
|
+
boundary = candidate;
|
|
3590
|
+
boundaryDepth = candidate.entry.location.length;
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
return boundary;
|
|
3594
|
+
}
|
|
3595
|
+
async function executeWorkflow(workflowId, workflowFn, input, driver, messageDriver, abortController, onHistoryUpdated, onError, logger) {
|
|
3261
3596
|
var _a;
|
|
3262
3597
|
const storage = await loadStorage(driver);
|
|
3263
3598
|
const historyNotifier = onHistoryUpdated ? () => onHistoryUpdated(createHistorySnapshot(storage)) : void 0;
|
|
@@ -3299,6 +3634,7 @@ async function executeWorkflow(workflowId, workflowFn, input, driver, messageDri
|
|
|
3299
3634
|
abortController,
|
|
3300
3635
|
storage,
|
|
3301
3636
|
historyNotifier,
|
|
3637
|
+
onError,
|
|
3302
3638
|
logger
|
|
3303
3639
|
);
|
|
3304
3640
|
} catch (error) {
|
|
@@ -3326,6 +3662,7 @@ async function executeWorkflow(workflowId, workflowFn, input, driver, messageDri
|
|
|
3326
3662
|
void 0,
|
|
3327
3663
|
false,
|
|
3328
3664
|
historyNotifier,
|
|
3665
|
+
onError,
|
|
3329
3666
|
logger
|
|
3330
3667
|
);
|
|
3331
3668
|
storage.state = "running";
|
|
@@ -3363,11 +3700,20 @@ async function executeWorkflow(workflowId, workflowFn, input, driver, messageDri
|
|
|
3363
3700
|
storage,
|
|
3364
3701
|
driver,
|
|
3365
3702
|
workflowId,
|
|
3703
|
+
error.retryAt,
|
|
3366
3704
|
historyNotifier
|
|
3367
3705
|
);
|
|
3368
3706
|
}
|
|
3369
3707
|
if (error instanceof RollbackCheckpointError) {
|
|
3370
3708
|
await setFailedState(storage, driver, error, historyNotifier);
|
|
3709
|
+
if (onError && !isErrorReported(error)) {
|
|
3710
|
+
await notifyError(onError, logger, {
|
|
3711
|
+
workflow: {
|
|
3712
|
+
workflowId,
|
|
3713
|
+
error: extractErrorInfo(error)
|
|
3714
|
+
}
|
|
3715
|
+
});
|
|
3716
|
+
}
|
|
3371
3717
|
throw error;
|
|
3372
3718
|
}
|
|
3373
3719
|
storage.error = extractErrorInfo(error);
|
|
@@ -3383,6 +3729,7 @@ async function executeWorkflow(workflowId, workflowFn, input, driver, messageDri
|
|
|
3383
3729
|
abortController,
|
|
3384
3730
|
storage,
|
|
3385
3731
|
historyNotifier,
|
|
3732
|
+
onError,
|
|
3386
3733
|
logger
|
|
3387
3734
|
);
|
|
3388
3735
|
} catch (rollbackError) {
|
|
@@ -3393,37 +3740,23 @@ async function executeWorkflow(workflowId, workflowFn, input, driver, messageDri
|
|
|
3393
3740
|
}
|
|
3394
3741
|
storage.state = "failed";
|
|
3395
3742
|
await flush(storage, driver, historyNotifier);
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
const result = {
|
|
3402
|
-
name: error.name,
|
|
3403
|
-
message: error.message,
|
|
3404
|
-
stack: error.stack
|
|
3405
|
-
};
|
|
3406
|
-
const metadata = {};
|
|
3407
|
-
for (const key of Object.keys(error)) {
|
|
3408
|
-
if (key !== "name" && key !== "message" && key !== "stack") {
|
|
3409
|
-
const value = error[key];
|
|
3410
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
3411
|
-
metadata[key] = value;
|
|
3743
|
+
if (onError && !isErrorReported(error)) {
|
|
3744
|
+
await notifyError(onError, logger, {
|
|
3745
|
+
workflow: {
|
|
3746
|
+
workflowId,
|
|
3747
|
+
error: extractErrorInfo(error)
|
|
3412
3748
|
}
|
|
3749
|
+
});
|
|
3750
|
+
if (error instanceof CriticalError || error instanceof RollbackError || error instanceof StepExhaustedError) {
|
|
3751
|
+
markErrorReported(error);
|
|
3413
3752
|
}
|
|
3414
3753
|
}
|
|
3415
|
-
|
|
3416
|
-
result.metadata = metadata;
|
|
3417
|
-
}
|
|
3418
|
-
return result;
|
|
3754
|
+
throw error;
|
|
3419
3755
|
}
|
|
3420
|
-
return {
|
|
3421
|
-
name: "Error",
|
|
3422
|
-
message: String(error)
|
|
3423
|
-
};
|
|
3424
3756
|
}
|
|
3425
3757
|
|
|
3426
3758
|
export {
|
|
3759
|
+
extractErrorInfo,
|
|
3427
3760
|
CriticalError,
|
|
3428
3761
|
RollbackError,
|
|
3429
3762
|
RollbackCheckpointError,
|
|
@@ -3437,6 +3770,9 @@ export {
|
|
|
3437
3770
|
RaceError,
|
|
3438
3771
|
CancelledError,
|
|
3439
3772
|
EntryInProgressError,
|
|
3773
|
+
keyStartsWith,
|
|
3774
|
+
compareKeys,
|
|
3775
|
+
keyToHex,
|
|
3440
3776
|
isLoopIterationMarker,
|
|
3441
3777
|
registerName,
|
|
3442
3778
|
resolveName,
|
|
@@ -3447,9 +3783,6 @@ export {
|
|
|
3447
3783
|
parentLocation,
|
|
3448
3784
|
isLocationPrefix,
|
|
3449
3785
|
locationsEqual,
|
|
3450
|
-
keyStartsWith,
|
|
3451
|
-
compareKeys,
|
|
3452
|
-
keyToHex,
|
|
3453
3786
|
createStorage,
|
|
3454
3787
|
createHistorySnapshot,
|
|
3455
3788
|
generateId,
|
|
@@ -3465,12 +3798,11 @@ export {
|
|
|
3465
3798
|
DEFAULT_MAX_RETRIES,
|
|
3466
3799
|
DEFAULT_RETRY_BACKOFF_BASE,
|
|
3467
3800
|
DEFAULT_RETRY_BACKOFF_MAX,
|
|
3468
|
-
|
|
3469
|
-
DEFAULT_LOOP_HISTORY_EVERY,
|
|
3470
|
-
DEFAULT_LOOP_HISTORY_KEEP,
|
|
3801
|
+
DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL,
|
|
3471
3802
|
DEFAULT_STEP_TIMEOUT,
|
|
3472
3803
|
WorkflowContextImpl,
|
|
3473
3804
|
Loop,
|
|
3474
|
-
runWorkflow
|
|
3805
|
+
runWorkflow,
|
|
3806
|
+
replayWorkflowFromStep
|
|
3475
3807
|
};
|
|
3476
|
-
//# sourceMappingURL=chunk-
|
|
3808
|
+
//# sourceMappingURL=chunk-4ME2JBMC.js.map
|