@rivetkit/workflow-engine 0.0.0-main.14140ce → 0.0.0-main.17f6330

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.
@@ -1402,6 +1402,8 @@ function deserializeName(bytes) {
1402
1402
  }
1403
1403
 
1404
1404
  // src/storage.ts
1405
+ var MAX_KV_BATCH_ENTRIES = 128;
1406
+ var MAX_KV_BATCH_PAYLOAD_BYTES = 976 * 1024;
1405
1407
  function createStorage() {
1406
1408
  return {
1407
1409
  nameRegistry: [],
@@ -1519,6 +1521,8 @@ async function loadMetadata(storage, driver, entryId) {
1519
1521
  }
1520
1522
  async function flush(storage, driver, onHistoryUpdated, pendingDeletions) {
1521
1523
  const writes = [];
1524
+ const dirtyEntries = [];
1525
+ const dirtyMetadata = [];
1522
1526
  let historyUpdated = false;
1523
1527
  for (let i = storage.flushedNameCount; i < storage.nameRegistry.length; i++) {
1524
1528
  const name = storage.nameRegistry[i];
@@ -1536,7 +1540,7 @@ async function flush(storage, driver, onHistoryUpdated, pendingDeletions) {
1536
1540
  key: buildHistoryKey(entry.location),
1537
1541
  value: serializeEntry(entry)
1538
1542
  });
1539
- entry.dirty = false;
1543
+ dirtyEntries.push(entry);
1540
1544
  historyUpdated = true;
1541
1545
  }
1542
1546
  }
@@ -1546,7 +1550,7 @@ async function flush(storage, driver, onHistoryUpdated, pendingDeletions) {
1546
1550
  key: buildEntryMetadataKey(id),
1547
1551
  value: serializeEntryMetadata(metadata)
1548
1552
  });
1549
- metadata.dirty = false;
1553
+ dirtyMetadata.push(metadata);
1550
1554
  historyUpdated = true;
1551
1555
  }
1552
1556
  }
@@ -1570,7 +1574,9 @@ async function flush(storage, driver, onHistoryUpdated, pendingDeletions) {
1570
1574
  });
1571
1575
  }
1572
1576
  if (writes.length > 0) {
1573
- await driver.batch(writes);
1577
+ for (const chunk of splitBatchWrites(writes)) {
1578
+ await driver.batch(chunk);
1579
+ }
1574
1580
  }
1575
1581
  if (pendingDeletions) {
1576
1582
  const deleteOps = [];
@@ -1588,6 +1594,12 @@ async function flush(storage, driver, onHistoryUpdated, pendingDeletions) {
1588
1594
  historyUpdated = true;
1589
1595
  }
1590
1596
  }
1597
+ for (const entry of dirtyEntries) {
1598
+ entry.dirty = false;
1599
+ }
1600
+ for (const metadata of dirtyMetadata) {
1601
+ metadata.dirty = false;
1602
+ }
1591
1603
  storage.flushedNameCount = storage.nameRegistry.length;
1592
1604
  storage.flushedState = storage.state;
1593
1605
  storage.flushedOutput = storage.output;
@@ -1596,6 +1608,30 @@ async function flush(storage, driver, onHistoryUpdated, pendingDeletions) {
1596
1608
  onHistoryUpdated();
1597
1609
  }
1598
1610
  }
1611
+ function splitBatchWrites(writes) {
1612
+ const chunks = [];
1613
+ let chunk = [];
1614
+ let chunkBytes = 0;
1615
+ for (const write of writes) {
1616
+ const writeBytes = write.key.byteLength + write.value.byteLength;
1617
+ if (writeBytes > MAX_KV_BATCH_PAYLOAD_BYTES) {
1618
+ throw new Error(
1619
+ `KV batch write is ${writeBytes} bytes, exceeding the ${MAX_KV_BATCH_PAYLOAD_BYTES} byte limit`
1620
+ );
1621
+ }
1622
+ if (chunk.length >= MAX_KV_BATCH_ENTRIES || chunk.length > 0 && chunkBytes + writeBytes > MAX_KV_BATCH_PAYLOAD_BYTES) {
1623
+ chunks.push(chunk);
1624
+ chunk = [];
1625
+ chunkBytes = 0;
1626
+ }
1627
+ chunk.push(write);
1628
+ chunkBytes += writeBytes;
1629
+ }
1630
+ if (chunk.length > 0) {
1631
+ chunks.push(chunk);
1632
+ }
1633
+ return chunks;
1634
+ }
1599
1635
  async function deleteEntriesWithPrefix(storage, driver, prefixLocation, onHistoryUpdated) {
1600
1636
  const deletions = collectDeletionsForPrefix(storage, prefixLocation);
1601
1637
  await driver.deletePrefix(deletions.prefixes[0]);
@@ -1628,30 +1664,6 @@ function setEntry(storage, location, entry) {
1628
1664
  storage.history.entries.set(key, entry);
1629
1665
  }
1630
1666
 
1631
- // src/utils.ts
1632
- function sleep(ms) {
1633
- return new Promise((resolve) => setTimeout(resolve, ms));
1634
- }
1635
- var TIMEOUT_MAX = 2147483647;
1636
- function setLongTimeout(listener, after) {
1637
- let timeout;
1638
- function start(remaining) {
1639
- if (remaining <= TIMEOUT_MAX) {
1640
- timeout = setTimeout(listener, remaining);
1641
- } else {
1642
- timeout = setTimeout(() => {
1643
- start(remaining - TIMEOUT_MAX);
1644
- }, TIMEOUT_MAX);
1645
- }
1646
- }
1647
- start(after);
1648
- return {
1649
- abort: () => {
1650
- if (timeout !== void 0) clearTimeout(timeout);
1651
- }
1652
- };
1653
- }
1654
-
1655
1667
  // src/context.ts
1656
1668
  var DEFAULT_MAX_RETRIES = 3;
1657
1669
  var DEFAULT_RETRY_BACKOFF_BASE = 100;
@@ -1927,7 +1939,7 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
1927
1939
  * Throws HistoryDivergedError if duplicate detected.
1928
1940
  */
1929
1941
  checkDuplicateName(name) {
1930
- const fullKey = locationToKey(this.storage, this.currentLocation) + "/" + name;
1942
+ const fullKey = `${locationToKey(this.storage, this.currentLocation)}/${name}`;
1931
1943
  if (this.usedNamesInExecution.has(fullKey)) {
1932
1944
  throw new HistoryDivergedError(
1933
1945
  `Duplicate entry name "${name}" at location "${locationToKey(this.storage, this.currentLocation)}". Each step/loop/sleep/queue.next/join/race must have a unique name within its scope.`
@@ -1981,7 +1993,7 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
1981
1993
  validateComplete() {
1982
1994
  const prefix = locationToKey(this.storage, this.currentLocation);
1983
1995
  for (const key of this.storage.history.entries.keys()) {
1984
- const isUnderPrefix = prefix === "" ? true : key.startsWith(prefix + "/") || key === prefix;
1996
+ const isUnderPrefix = prefix === "" ? true : key.startsWith(`${prefix}/`) || key === prefix;
1985
1997
  if (isUnderPrefix) {
1986
1998
  if (!this.visitedKeys.has(key)) {
1987
1999
  throw new HistoryDivergedError(
@@ -1998,25 +2010,33 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
1998
2010
  this.abortController.abort(new EvictedError());
1999
2011
  }
2000
2012
  /**
2001
- * Wait for eviction message.
2002
- *
2003
- * The event listener uses { once: true } to auto-remove after firing,
2004
- * preventing memory leaks if this method is called multiple times.
2013
+ * Wait for `ms`, rejecting early with EvictedError if the workflow is
2014
+ * evicted. Both the timer and the abort listener are torn down on either
2015
+ * outcome, so a completed sleep never leaves a dangling listener on the
2016
+ * long-lived run abort signal.
2005
2017
  */
2006
- waitForEviction() {
2007
- return new Promise((_, reject) => {
2008
- if (this.abortSignal.aborted) {
2009
- reject(new EvictedError());
2010
- return;
2018
+ async sleepOrEvict(ms) {
2019
+ if (this.abortSignal.aborted) {
2020
+ throw new EvictedError();
2021
+ }
2022
+ let timer;
2023
+ let onAbort;
2024
+ try {
2025
+ await new Promise((resolve, reject) => {
2026
+ timer = setTimeout(resolve, ms);
2027
+ onAbort = () => reject(new EvictedError());
2028
+ this.abortSignal.addEventListener("abort", onAbort, {
2029
+ once: true
2030
+ });
2031
+ });
2032
+ } finally {
2033
+ if (timer !== void 0) {
2034
+ clearTimeout(timer);
2011
2035
  }
2012
- this.abortSignal.addEventListener(
2013
- "abort",
2014
- () => {
2015
- reject(new EvictedError());
2016
- },
2017
- { once: true }
2018
- );
2019
- });
2036
+ if (onAbort) {
2037
+ this.abortSignal.removeEventListener("abort", onAbort);
2038
+ }
2039
+ }
2020
2040
  }
2021
2041
  // === Step ===
2022
2042
  async step(nameOrConfig, run) {
@@ -2165,7 +2185,7 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
2165
2185
  }
2166
2186
  const maxRetries2 = config2.maxRetries ?? DEFAULT_MAX_RETRIES;
2167
2187
  if (metadata2.attempts > maxRetries2) {
2168
- const lastError = stepData.error ?? metadata2.error;
2188
+ const lastError = metadata2.error;
2169
2189
  const exhaustedError = new StepExhaustedError(
2170
2190
  config2.name,
2171
2191
  lastError
@@ -2254,37 +2274,28 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
2254
2274
  });
2255
2275
  return output;
2256
2276
  } catch (error) {
2257
- if (error instanceof StepTimeoutError) {
2258
- if (entry.kind.type === "step") {
2259
- entry.kind.data.error = String(error);
2260
- }
2261
- entry.dirty = true;
2277
+ if (entry.kind.type === "step") {
2278
+ entry.kind.data.error = String(error);
2279
+ }
2280
+ entry.dirty = true;
2281
+ if (error instanceof StepTimeoutError && !config2.retryOnTimeout) {
2262
2282
  metadata.status = "exhausted";
2263
2283
  metadata.error = String(error);
2264
- await this.flushStorage();
2265
2284
  await this.notifyStepError(config2, metadata.attempts, error, {
2266
2285
  willRetry: false
2267
2286
  });
2268
2287
  throw markErrorReported(
2269
- attachTryStepFailure(
2270
- new CriticalError(error.message),
2271
- {
2272
- kind: "timeout",
2273
- stepName: config2.name,
2274
- attempts: metadata.attempts,
2275
- error: extractErrorInfo(error)
2276
- }
2277
- )
2288
+ attachTryStepFailure(new CriticalError(error.message), {
2289
+ kind: "timeout",
2290
+ stepName: config2.name,
2291
+ attempts: metadata.attempts,
2292
+ error: extractErrorInfo(error)
2293
+ })
2278
2294
  );
2279
2295
  }
2280
2296
  if (error instanceof CriticalError || error instanceof RollbackError) {
2281
- if (entry.kind.type === "step") {
2282
- entry.kind.data.error = String(error);
2283
- }
2284
- entry.dirty = true;
2285
2297
  metadata.status = "exhausted";
2286
2298
  metadata.error = String(error);
2287
- await this.flushStorage();
2288
2299
  await this.notifyStepError(config2, metadata.attempts, error, {
2289
2300
  willRetry: false
2290
2301
  });
@@ -2297,14 +2308,9 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
2297
2308
  })
2298
2309
  );
2299
2310
  }
2300
- if (entry.kind.type === "step") {
2301
- entry.kind.data.error = String(error);
2302
- }
2303
- entry.dirty = true;
2304
2311
  const willRetry = metadata.attempts <= maxRetries;
2305
2312
  metadata.status = willRetry ? "failed" : "exhausted";
2306
2313
  metadata.error = String(error);
2307
- await this.flushStorage();
2308
2314
  if (willRetry) {
2309
2315
  const retryDelay = calculateBackoff(
2310
2316
  metadata.attempts,
@@ -2328,7 +2334,7 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
2328
2334
  attachTryStepFailure(
2329
2335
  new StepExhaustedError(config2.name, String(error)),
2330
2336
  {
2331
- kind: "exhausted",
2337
+ kind: error instanceof StepTimeoutError ? "timeout" : "exhausted",
2332
2338
  stepName: config2.name,
2333
2339
  attempts: metadata.attempts,
2334
2340
  error: extractErrorInfo(error)
@@ -2346,7 +2352,9 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
2346
2352
  *
2347
2353
  * Note: This does NOT cancel the underlying operation. JavaScript Promises
2348
2354
  * cannot be cancelled once started. When a timeout occurs:
2349
- * - The step is marked as failed with StepTimeoutError
2355
+ * - The step is rejected with StepTimeoutError. By default this is treated
2356
+ * as a critical failure with no retry. Set retryOnTimeout: true on the
2357
+ * step config to retry timeouts like any other error.
2350
2358
  * - The underlying async operation continues running in the background
2351
2359
  * - Any side effects from the operation may still occur
2352
2360
  *
@@ -2481,8 +2489,8 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
2481
2489
  metadata.error = void 0;
2482
2490
  metadata.dirty = true;
2483
2491
  }
2484
- const historyPruneInterval = config2.historyPruneInterval ?? DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL;
2485
- const historySize = config2.historySize ?? historyPruneInterval;
2492
+ const historyPruneInterval = config2.historyPruneInterval ?? config2.commitInterval ?? config2.historyEvery ?? DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL;
2493
+ const historySize = config2.historySize ?? config2.historyKeep ?? historyPruneInterval;
2486
2494
  let lastPrunedUpTo = 0;
2487
2495
  let deferredFlush = null;
2488
2496
  while (true) {
@@ -2671,7 +2679,7 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
2671
2679
  return;
2672
2680
  }
2673
2681
  if (remaining < this.driver.workerPollInterval) {
2674
- await Promise.race([sleep(remaining), this.waitForEviction()]);
2682
+ await this.sleepOrEvict(remaining);
2675
2683
  this.checkEvicted();
2676
2684
  if (entry.kind.type === "sleep") {
2677
2685
  entry.kind.data.state = "completed";
@@ -3482,6 +3490,30 @@ var WorkflowContextImpl = class _WorkflowContextImpl {
3482
3490
  }
3483
3491
  };
3484
3492
 
3493
+ // src/utils.ts
3494
+ function sleep(ms) {
3495
+ return new Promise((resolve) => setTimeout(resolve, ms));
3496
+ }
3497
+ var TIMEOUT_MAX = 2147483647;
3498
+ function setLongTimeout(listener, after) {
3499
+ let timeout;
3500
+ function start(remaining) {
3501
+ if (remaining <= TIMEOUT_MAX) {
3502
+ timeout = setTimeout(listener, remaining);
3503
+ } else {
3504
+ timeout = setTimeout(() => {
3505
+ start(remaining - TIMEOUT_MAX);
3506
+ }, TIMEOUT_MAX);
3507
+ }
3508
+ }
3509
+ start(after);
3510
+ return {
3511
+ abort: () => {
3512
+ if (timeout !== void 0) clearTimeout(timeout);
3513
+ }
3514
+ };
3515
+ }
3516
+
3485
3517
  // src/index.ts
3486
3518
  var Loop = {
3487
3519
  continue: (state) => ({
@@ -3705,25 +3737,41 @@ async function executeLiveWorkflow(workflowId, workflowFn, input, driver, messag
3705
3737
  const hasMessages = result.waitingForMessages !== void 0;
3706
3738
  const hasDeadline = result.sleepUntil !== void 0;
3707
3739
  if (hasMessages && hasDeadline) {
3740
+ const iterationAbort = new AbortController();
3741
+ const onRunAbort = () => iterationAbort.abort();
3742
+ if (abortController.signal.aborted) {
3743
+ iterationAbort.abort();
3744
+ } else {
3745
+ abortController.signal.addEventListener("abort", onRunAbort, {
3746
+ once: true
3747
+ });
3748
+ }
3749
+ const messagePromise = awaitWithEviction(
3750
+ driver.waitForMessages(
3751
+ result.waitingForMessages,
3752
+ iterationAbort.signal
3753
+ ),
3754
+ iterationAbort.signal
3755
+ );
3756
+ const sleepPromise = waitForSleep(
3757
+ runtime,
3758
+ result.sleepUntil,
3759
+ iterationAbort.signal
3760
+ );
3761
+ messagePromise.catch(() => {
3762
+ });
3763
+ sleepPromise.catch(() => {
3764
+ });
3708
3765
  try {
3709
- const messagePromise = awaitWithEviction(
3710
- driver.waitForMessages(
3711
- result.waitingForMessages,
3712
- abortController.signal
3713
- ),
3714
- abortController.signal
3715
- );
3716
- const sleepPromise = waitForSleep(
3717
- runtime,
3718
- result.sleepUntil,
3719
- abortController.signal
3720
- );
3721
3766
  await Promise.race([messagePromise, sleepPromise]);
3722
3767
  } catch (error) {
3723
3768
  if (error instanceof EvictedError) {
3724
3769
  return lastResult;
3725
3770
  }
3726
3771
  throw error;
3772
+ } finally {
3773
+ iterationAbort.abort();
3774
+ abortController.signal.removeEventListener("abort", onRunAbort);
3727
3775
  }
3728
3776
  continue;
3729
3777
  }
@@ -4166,15 +4214,15 @@ export {
4166
4214
  deleteEntriesWithPrefix,
4167
4215
  getEntry,
4168
4216
  setEntry,
4169
- sleep,
4170
4217
  DEFAULT_MAX_RETRIES,
4171
4218
  DEFAULT_RETRY_BACKOFF_BASE,
4172
4219
  DEFAULT_RETRY_BACKOFF_MAX,
4173
4220
  DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL,
4174
4221
  DEFAULT_STEP_TIMEOUT,
4175
4222
  WorkflowContextImpl,
4223
+ sleep,
4176
4224
  Loop,
4177
4225
  runWorkflow,
4178
4226
  replayWorkflowFromStep
4179
4227
  };
4180
- //# sourceMappingURL=chunk-UMFB2AR3.js.map
4228
+ //# sourceMappingURL=chunk-KA2T56AJ.js.map