@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.
@@ -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 entryIds = [];
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
- entryIds.push(entry.id);
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
- await driver.deletePrefix(buildHistoryPrefix(prefixLocation));
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 DEFAULT_LOOP_COMMIT_INTERVAL = 20;
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 = /* @__PURE__ */ new Set();
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.logger
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 maxRetries = config2.maxRetries ?? DEFAULT_MAX_RETRIES;
1794
- if (metadata2.attempts >= maxRetries) {
1928
+ const maxRetries2 = config2.maxRetries ?? DEFAULT_MAX_RETRIES;
1929
+ if (metadata2.attempts > maxRetries2) {
1795
1930
  const lastError = stepData.error ?? metadata2.error;
1796
- throw new StepExhaustedError(config2.name, lastError);
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", { msg: "executing new step", step: config2.name, key });
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", { msg: "flushing step", step: config2.name, key });
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", { msg: "step completed", step: config2.name, key });
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
- throw new CriticalError(error.message);
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
- throw error;
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.status = "failed";
2039
+ const willRetry = metadata.attempts <= maxRetries;
2040
+ metadata.status = willRetry ? "failed" : "exhausted";
1870
2041
  metadata.error = String(error);
1871
2042
  await this.flushStorage();
1872
- throw new StepFailedError(config2.name, error, metadata.attempts);
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(this.storage, this.driver, existing.id);
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 commitInterval = config2.commitInterval ?? DEFAULT_LOOP_COMMIT_INTERVAL;
2012
- const historyEvery = config2.historyEvery ?? config2.commitInterval ?? DEFAULT_LOOP_HISTORY_EVERY;
2013
- const historyKeep = config2.historyKeep ?? config2.commitInterval ?? DEFAULT_LOOP_HISTORY_KEEP;
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
- await this.flushStorage();
2050
- await this.forgetOldIterations(
2254
+ const deletions = this.collectLoopPruning(
2051
2255
  location,
2052
2256
  iteration + 1,
2053
- historyEvery,
2054
- historyKeep
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 (iteration % commitInterval === 0) {
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
- await this.flushStorage();
2074
- await this.forgetOldIterations(
2278
+ }
2279
+ if (iteration % historyPruneInterval === 0) {
2280
+ const deletions = this.collectLoopPruning(
2075
2281
  location,
2076
2282
  iteration,
2077
- historyEvery,
2078
- historyKeep
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
- * Delete old loop iteration entries to save storage space.
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
- * ctx.loop("outer") location: [outerIndex]
2091
- * iteration 0 → location: [{ loop: outerIndex, iteration: 0 }]
2092
- * ctx.loop("inner") location: [{ loop: outerIndex, iteration: 0 }, innerIndex]
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
- async forgetOldIterations(loopLocation, currentIteration, historyEvery, historyKeep) {
2098
- if (historyEvery <= 0 || historyKeep <= 0) {
2099
- return;
2298
+ collectLoopPruning(loopLocation, currentIteration, historySize, fromIteration) {
2299
+ if (currentIteration <= historySize) {
2300
+ return void 0;
2100
2301
  }
2101
- if (currentIteration === 0 || currentIteration % historyEvery !== 0) {
2102
- return;
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
- for (let i = 0; i < keepFrom; i++) {
2110
- const iterationLocation = [
2111
- ...loopLocation,
2112
- { loop: loopSegment, iteration: i }
2113
- ];
2114
- await deleteEntriesWithPrefix(
2115
- this.storage,
2116
- this.driver,
2117
- iterationLocation,
2118
- this.historyNotifier
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 executeWorkflow(workflowId, workflowFn, input, driver, messageDriver, abortController, onHistoryUpdated, logger) {
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
- throw error;
3397
- }
3398
- }
3399
- function extractErrorInfo(error) {
3400
- if (error instanceof Error) {
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
- if (Object.keys(metadata).length > 0) {
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
- DEFAULT_LOOP_COMMIT_INTERVAL,
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-JTLDEP6X.js.map
3808
+ //# sourceMappingURL=chunk-4ME2JBMC.js.map