@rivetkit/workflow-engine 0.0.0-main.41efcce → 0.0.0-main.4994268

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.
@@ -670,6 +670,16 @@ function writeRemovedEntry(bc, x) {
670
670
  bare.writeString(bc, x.originalType);
671
671
  write1(bc, x.originalName);
672
672
  }
673
+ function readVersionCheckEntry(bc) {
674
+ return {
675
+ resolved: bare.readU32(bc),
676
+ latest: bare.readU32(bc)
677
+ };
678
+ }
679
+ function writeVersionCheckEntry(bc, x) {
680
+ bare.writeU32(bc, x.resolved);
681
+ bare.writeU32(bc, x.latest);
682
+ }
673
683
  function readEntryKind(bc) {
674
684
  const offset = bc.offset;
675
685
  const tag = bare.readU8(bc);
@@ -690,6 +700,8 @@ function readEntryKind(bc) {
690
700
  return { tag: "RaceEntry", val: readRaceEntry(bc) };
691
701
  case 7:
692
702
  return { tag: "RemovedEntry", val: readRemovedEntry(bc) };
703
+ case 8:
704
+ return { tag: "VersionCheckEntry", val: readVersionCheckEntry(bc) };
693
705
  default: {
694
706
  bc.offset = offset;
695
707
  throw new bare.BareError(offset, "invalid tag");
@@ -738,6 +750,11 @@ function writeEntryKind(bc, x) {
738
750
  writeRemovedEntry(bc, x.val);
739
751
  break;
740
752
  }
753
+ case "VersionCheckEntry": {
754
+ bare.writeU8(bc, 8);
755
+ writeVersionCheckEntry(bc, x.val);
756
+ break;
757
+ }
741
758
  }
742
759
  }
743
760
  function readEntry(bc) {
@@ -1182,6 +1199,14 @@ function entryKindToBare(kind) {
1182
1199
  originalName: _nullishCoalesce(kind.data.originalName, () => ( null))
1183
1200
  }
1184
1201
  };
1202
+ case "version_check":
1203
+ return {
1204
+ tag: "VersionCheckEntry",
1205
+ val: {
1206
+ resolved: kind.data.resolved,
1207
+ latest: kind.data.latest
1208
+ }
1209
+ };
1185
1210
  }
1186
1211
  }
1187
1212
  function entryKindFromBare(kind) {
@@ -1263,6 +1288,14 @@ function entryKindFromBare(kind) {
1263
1288
  originalName: _nullishCoalesce(kind.val.originalName, () => ( void 0))
1264
1289
  }
1265
1290
  };
1291
+ case "VersionCheckEntry":
1292
+ return {
1293
+ type: "version_check",
1294
+ data: {
1295
+ resolved: kind.val.resolved,
1296
+ latest: kind.val.latest
1297
+ }
1298
+ };
1266
1299
  default:
1267
1300
  throw new Error(
1268
1301
  `Unknown entry kind: ${kind.tag}`
@@ -1574,8 +1607,12 @@ async function flush(storage, driver, onHistoryUpdated, pendingDeletions) {
1574
1607
  });
1575
1608
  }
1576
1609
  if (writes.length > 0) {
1577
- for (const chunk of splitBatchWrites(writes)) {
1578
- await driver.batch(chunk);
1610
+ if (driver.atomicBatch) {
1611
+ await driver.batch(writes);
1612
+ } else {
1613
+ for (const chunk of splitBatchWrites(writes)) {
1614
+ await driver.batch(chunk);
1615
+ }
1579
1616
  }
1580
1617
  }
1581
1618
  if (pendingDeletions) {
@@ -1664,30 +1701,6 @@ function setEntry(storage, location, entry) {
1664
1701
  storage.history.entries.set(key, entry);
1665
1702
  }
1666
1703
 
1667
- // src/utils.ts
1668
- function sleep(ms) {
1669
- return new Promise((resolve) => setTimeout(resolve, ms));
1670
- }
1671
- var TIMEOUT_MAX = 2147483647;
1672
- function setLongTimeout(listener, after) {
1673
- let timeout;
1674
- function start(remaining) {
1675
- if (remaining <= TIMEOUT_MAX) {
1676
- timeout = setTimeout(listener, remaining);
1677
- } else {
1678
- timeout = setTimeout(() => {
1679
- start(remaining - TIMEOUT_MAX);
1680
- }, TIMEOUT_MAX);
1681
- }
1682
- }
1683
- start(after);
1684
- return {
1685
- abort: () => {
1686
- if (timeout !== void 0) clearTimeout(timeout);
1687
- }
1688
- };
1689
- }
1690
-
1691
1704
  // src/context.ts
1692
1705
  var DEFAULT_MAX_RETRIES = 3;
1693
1706
  var DEFAULT_RETRY_BACKOFF_BASE = 100;
@@ -1963,7 +1976,7 @@ var WorkflowContextImpl = (_class = class _WorkflowContextImpl {
1963
1976
  * Throws HistoryDivergedError if duplicate detected.
1964
1977
  */
1965
1978
  checkDuplicateName(name) {
1966
- const fullKey = locationToKey(this.storage, this.currentLocation) + "/" + name;
1979
+ const fullKey = `${locationToKey(this.storage, this.currentLocation)}/${name}`;
1967
1980
  if (this.usedNamesInExecution.has(fullKey)) {
1968
1981
  throw new HistoryDivergedError(
1969
1982
  `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.`
@@ -2017,7 +2030,7 @@ var WorkflowContextImpl = (_class = class _WorkflowContextImpl {
2017
2030
  validateComplete() {
2018
2031
  const prefix = locationToKey(this.storage, this.currentLocation);
2019
2032
  for (const key of this.storage.history.entries.keys()) {
2020
- const isUnderPrefix = prefix === "" ? true : key.startsWith(prefix + "/") || key === prefix;
2033
+ const isUnderPrefix = prefix === "" ? true : key.startsWith(`${prefix}/`) || key === prefix;
2021
2034
  if (isUnderPrefix) {
2022
2035
  if (!this.visitedKeys.has(key)) {
2023
2036
  throw new HistoryDivergedError(
@@ -2034,25 +2047,33 @@ var WorkflowContextImpl = (_class = class _WorkflowContextImpl {
2034
2047
  this.abortController.abort(new EvictedError());
2035
2048
  }
2036
2049
  /**
2037
- * Wait for eviction message.
2038
- *
2039
- * The event listener uses { once: true } to auto-remove after firing,
2040
- * preventing memory leaks if this method is called multiple times.
2050
+ * Wait for `ms`, rejecting early with EvictedError if the workflow is
2051
+ * evicted. Both the timer and the abort listener are torn down on either
2052
+ * outcome, so a completed sleep never leaves a dangling listener on the
2053
+ * long-lived run abort signal.
2041
2054
  */
2042
- waitForEviction() {
2043
- return new Promise((_, reject) => {
2044
- if (this.abortSignal.aborted) {
2045
- reject(new EvictedError());
2046
- return;
2055
+ async sleepOrEvict(ms) {
2056
+ if (this.abortSignal.aborted) {
2057
+ throw new EvictedError();
2058
+ }
2059
+ let timer;
2060
+ let onAbort;
2061
+ try {
2062
+ await new Promise((resolve, reject) => {
2063
+ timer = setTimeout(resolve, ms);
2064
+ onAbort = () => reject(new EvictedError());
2065
+ this.abortSignal.addEventListener("abort", onAbort, {
2066
+ once: true
2067
+ });
2068
+ });
2069
+ } finally {
2070
+ if (timer !== void 0) {
2071
+ clearTimeout(timer);
2047
2072
  }
2048
- this.abortSignal.addEventListener(
2049
- "abort",
2050
- () => {
2051
- reject(new EvictedError());
2052
- },
2053
- { once: true }
2054
- );
2055
- });
2073
+ if (onAbort) {
2074
+ this.abortSignal.removeEventListener("abort", onAbort);
2075
+ }
2076
+ }
2056
2077
  }
2057
2078
  // === Step ===
2058
2079
  async step(nameOrConfig, run) {
@@ -2201,7 +2222,7 @@ var WorkflowContextImpl = (_class = class _WorkflowContextImpl {
2201
2222
  }
2202
2223
  const maxRetries2 = _nullishCoalesce(config2.maxRetries, () => ( DEFAULT_MAX_RETRIES));
2203
2224
  if (metadata2.attempts > maxRetries2) {
2204
- const lastError = _nullishCoalesce(stepData.error, () => ( metadata2.error));
2225
+ const lastError = metadata2.error;
2205
2226
  const exhaustedError = new StepExhaustedError(
2206
2227
  config2.name,
2207
2228
  lastError
@@ -2290,14 +2311,13 @@ var WorkflowContextImpl = (_class = class _WorkflowContextImpl {
2290
2311
  });
2291
2312
  return output;
2292
2313
  } catch (error) {
2293
- if (error instanceof StepTimeoutError) {
2294
- if (entry.kind.type === "step") {
2295
- entry.kind.data.error = String(error);
2296
- }
2297
- entry.dirty = true;
2314
+ if (entry.kind.type === "step") {
2315
+ entry.kind.data.error = String(error);
2316
+ }
2317
+ entry.dirty = true;
2318
+ if (error instanceof StepTimeoutError && !config2.retryOnTimeout) {
2298
2319
  metadata.status = "exhausted";
2299
2320
  metadata.error = String(error);
2300
- await this.flushStorage();
2301
2321
  await this.notifyStepError(config2, metadata.attempts, error, {
2302
2322
  willRetry: false
2303
2323
  });
@@ -2311,13 +2331,8 @@ var WorkflowContextImpl = (_class = class _WorkflowContextImpl {
2311
2331
  );
2312
2332
  }
2313
2333
  if (error instanceof CriticalError || error instanceof RollbackError) {
2314
- if (entry.kind.type === "step") {
2315
- entry.kind.data.error = String(error);
2316
- }
2317
- entry.dirty = true;
2318
2334
  metadata.status = "exhausted";
2319
2335
  metadata.error = String(error);
2320
- await this.flushStorage();
2321
2336
  await this.notifyStepError(config2, metadata.attempts, error, {
2322
2337
  willRetry: false
2323
2338
  });
@@ -2330,14 +2345,9 @@ var WorkflowContextImpl = (_class = class _WorkflowContextImpl {
2330
2345
  })
2331
2346
  );
2332
2347
  }
2333
- if (entry.kind.type === "step") {
2334
- entry.kind.data.error = String(error);
2335
- }
2336
- entry.dirty = true;
2337
2348
  const willRetry = metadata.attempts <= maxRetries;
2338
2349
  metadata.status = willRetry ? "failed" : "exhausted";
2339
2350
  metadata.error = String(error);
2340
- await this.flushStorage();
2341
2351
  if (willRetry) {
2342
2352
  const retryDelay = calculateBackoff(
2343
2353
  metadata.attempts,
@@ -2361,7 +2371,7 @@ var WorkflowContextImpl = (_class = class _WorkflowContextImpl {
2361
2371
  attachTryStepFailure(
2362
2372
  new StepExhaustedError(config2.name, String(error)),
2363
2373
  {
2364
- kind: "exhausted",
2374
+ kind: error instanceof StepTimeoutError ? "timeout" : "exhausted",
2365
2375
  stepName: config2.name,
2366
2376
  attempts: metadata.attempts,
2367
2377
  error: extractErrorInfo(error)
@@ -2379,7 +2389,9 @@ var WorkflowContextImpl = (_class = class _WorkflowContextImpl {
2379
2389
  *
2380
2390
  * Note: This does NOT cancel the underlying operation. JavaScript Promises
2381
2391
  * cannot be cancelled once started. When a timeout occurs:
2382
- * - The step is marked as failed with StepTimeoutError
2392
+ * - The step is rejected with StepTimeoutError. By default this is treated
2393
+ * as a critical failure with no retry. Set retryOnTimeout: true on the
2394
+ * step config to retry timeouts like any other error.
2383
2395
  * - The underlying async operation continues running in the background
2384
2396
  * - Any side effects from the operation may still occur
2385
2397
  *
@@ -2704,7 +2716,7 @@ var WorkflowContextImpl = (_class = class _WorkflowContextImpl {
2704
2716
  return;
2705
2717
  }
2706
2718
  if (remaining < this.driver.workerPollInterval) {
2707
- await Promise.race([sleep(remaining), this.waitForEviction()]);
2719
+ await this.sleepOrEvict(remaining);
2708
2720
  this.checkEvicted();
2709
2721
  if (entry.kind.type === "sleep") {
2710
2722
  entry.kind.data.state = "completed";
@@ -3513,8 +3525,91 @@ var WorkflowContextImpl = (_class = class _WorkflowContextImpl {
3513
3525
  setEntry(this.storage, location, entry);
3514
3526
  await this.flushStorage();
3515
3527
  }
3528
+ // === Version ===
3529
+ async getVersion(name, latest) {
3530
+ this.assertNotInProgress();
3531
+ this.checkEvicted();
3532
+ this.entryInProgress = true;
3533
+ try {
3534
+ return await this.executeGetVersion(name, latest);
3535
+ } finally {
3536
+ this.entryInProgress = false;
3537
+ }
3538
+ }
3539
+ async executeGetVersion(name, latest) {
3540
+ if (!Number.isInteger(latest) || latest < 1) {
3541
+ throw new Error(
3542
+ `getVersion("${name}", ${latest}): latest must be an integer >= 1`
3543
+ );
3544
+ }
3545
+ this.checkDuplicateName(name);
3546
+ const location = appendName(this.storage, this.currentLocation, name);
3547
+ const key = locationToKey(this.storage, location);
3548
+ const existing = this.storage.history.entries.get(key);
3549
+ this.markVisited(key);
3550
+ this.stopRollbackIfMissing(existing);
3551
+ if (existing) {
3552
+ if (existing.kind.type !== "version_check") {
3553
+ throw new HistoryDivergedError(
3554
+ `Expected version_check at ${key}, found ${existing.kind.type}`
3555
+ );
3556
+ }
3557
+ return existing.kind.data.resolved;
3558
+ }
3559
+ const resolved = this.hasUnvisitedUnderCurrentScope(key) ? 1 : latest;
3560
+ const entry = createEntry(location, {
3561
+ type: "version_check",
3562
+ data: { resolved, latest }
3563
+ });
3564
+ setEntry(this.storage, location, entry);
3565
+ await this.flushStorage();
3566
+ return resolved;
3567
+ }
3568
+ /**
3569
+ * Returns true if any history entry under the current location scope
3570
+ * (other than `excludeKey`) has not yet been visited this run. Used by
3571
+ * getVersion to detect an old in-flight instance whose scope was already
3572
+ * executed by code that predates a version gate.
3573
+ */
3574
+ hasUnvisitedUnderCurrentScope(excludeKey) {
3575
+ const prefix = locationToKey(this.storage, this.currentLocation);
3576
+ for (const key of this.storage.history.entries.keys()) {
3577
+ if (key === excludeKey) {
3578
+ continue;
3579
+ }
3580
+ const isUnderPrefix = prefix === "" ? true : key.startsWith(`${prefix}/`) || key === prefix;
3581
+ if (isUnderPrefix && !this.visitedKeys.has(key)) {
3582
+ return true;
3583
+ }
3584
+ }
3585
+ return false;
3586
+ }
3516
3587
  }, _class);
3517
3588
 
3589
+ // src/utils.ts
3590
+ function sleep(ms) {
3591
+ return new Promise((resolve) => setTimeout(resolve, ms));
3592
+ }
3593
+ var TIMEOUT_MAX = 2147483647;
3594
+ function setLongTimeout(listener, after) {
3595
+ let timeout;
3596
+ function start(remaining) {
3597
+ if (remaining <= TIMEOUT_MAX) {
3598
+ timeout = setTimeout(listener, remaining);
3599
+ } else {
3600
+ timeout = setTimeout(() => {
3601
+ start(remaining - TIMEOUT_MAX);
3602
+ }, TIMEOUT_MAX);
3603
+ }
3604
+ }
3605
+ start(after);
3606
+ return {
3607
+ abort: () => {
3608
+ if (timeout !== void 0) clearTimeout(timeout);
3609
+ }
3610
+ };
3611
+ }
3612
+
3518
3613
  // src/index.ts
3519
3614
  var Loop = {
3520
3615
  continue: (state) => ({
@@ -3738,25 +3833,41 @@ async function executeLiveWorkflow(workflowId, workflowFn, input, driver, messag
3738
3833
  const hasMessages = result.waitingForMessages !== void 0;
3739
3834
  const hasDeadline = result.sleepUntil !== void 0;
3740
3835
  if (hasMessages && hasDeadline) {
3836
+ const iterationAbort = new AbortController();
3837
+ const onRunAbort = () => iterationAbort.abort();
3838
+ if (abortController.signal.aborted) {
3839
+ iterationAbort.abort();
3840
+ } else {
3841
+ abortController.signal.addEventListener("abort", onRunAbort, {
3842
+ once: true
3843
+ });
3844
+ }
3845
+ const messagePromise = awaitWithEviction(
3846
+ driver.waitForMessages(
3847
+ result.waitingForMessages,
3848
+ iterationAbort.signal
3849
+ ),
3850
+ iterationAbort.signal
3851
+ );
3852
+ const sleepPromise = waitForSleep(
3853
+ runtime,
3854
+ result.sleepUntil,
3855
+ iterationAbort.signal
3856
+ );
3857
+ messagePromise.catch(() => {
3858
+ });
3859
+ sleepPromise.catch(() => {
3860
+ });
3741
3861
  try {
3742
- const messagePromise = awaitWithEviction(
3743
- driver.waitForMessages(
3744
- result.waitingForMessages,
3745
- abortController.signal
3746
- ),
3747
- abortController.signal
3748
- );
3749
- const sleepPromise = waitForSleep(
3750
- runtime,
3751
- result.sleepUntil,
3752
- abortController.signal
3753
- );
3754
3862
  await Promise.race([messagePromise, sleepPromise]);
3755
3863
  } catch (error) {
3756
3864
  if (error instanceof EvictedError) {
3757
3865
  return lastResult;
3758
3866
  }
3759
3867
  throw error;
3868
+ } finally {
3869
+ iterationAbort.abort();
3870
+ abortController.signal.removeEventListener("abort", onRunAbort);
3760
3871
  }
3761
3872
  continue;
3762
3873
  }
@@ -4209,5 +4320,5 @@ async function executeWorkflow(workflowId, workflowFn, input, driver, messageDri
4209
4320
 
4210
4321
 
4211
4322
 
4212
- exports.extractErrorInfo = extractErrorInfo; exports.CriticalError = CriticalError; exports.RollbackError = RollbackError; exports.RollbackCheckpointError = RollbackCheckpointError; exports.SleepError = SleepError; exports.MessageWaitError = MessageWaitError; exports.EvictedError = EvictedError; exports.HistoryDivergedError = HistoryDivergedError; exports.StepExhaustedError = StepExhaustedError; exports.StepFailedError = StepFailedError; exports.JoinError = JoinError; exports.RaceError = RaceError; exports.CancelledError = CancelledError; exports.EntryInProgressError = EntryInProgressError; exports.keyStartsWith = keyStartsWith; exports.compareKeys = compareKeys; exports.keyToHex = keyToHex; exports.isLoopIterationMarker = isLoopIterationMarker; exports.registerName = registerName; exports.resolveName = resolveName; exports.locationToKey = locationToKey; exports.appendName = appendName; exports.appendLoopIteration = appendLoopIteration; exports.emptyLocation = emptyLocation; exports.parentLocation = parentLocation; exports.isLocationPrefix = isLocationPrefix; exports.locationsEqual = locationsEqual; exports.createStorage = createStorage; exports.createHistorySnapshot = createHistorySnapshot; exports.generateId = generateId; exports.createEntry = createEntry; exports.getOrCreateMetadata = getOrCreateMetadata; exports.loadStorage = loadStorage; exports.loadMetadata = loadMetadata; exports.flush = flush; exports.deleteEntriesWithPrefix = deleteEntriesWithPrefix; exports.getEntry = getEntry; exports.setEntry = setEntry; exports.sleep = sleep; exports.DEFAULT_MAX_RETRIES = DEFAULT_MAX_RETRIES; exports.DEFAULT_RETRY_BACKOFF_BASE = DEFAULT_RETRY_BACKOFF_BASE; exports.DEFAULT_RETRY_BACKOFF_MAX = DEFAULT_RETRY_BACKOFF_MAX; exports.DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL = DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL; exports.DEFAULT_STEP_TIMEOUT = DEFAULT_STEP_TIMEOUT; exports.WorkflowContextImpl = WorkflowContextImpl; exports.Loop = Loop; exports.runWorkflow = runWorkflow; exports.replayWorkflowFromStep = replayWorkflowFromStep;
4213
- //# sourceMappingURL=chunk-SFJQFMCW.cjs.map
4323
+ exports.extractErrorInfo = extractErrorInfo; exports.CriticalError = CriticalError; exports.RollbackError = RollbackError; exports.RollbackCheckpointError = RollbackCheckpointError; exports.SleepError = SleepError; exports.MessageWaitError = MessageWaitError; exports.EvictedError = EvictedError; exports.HistoryDivergedError = HistoryDivergedError; exports.StepExhaustedError = StepExhaustedError; exports.StepFailedError = StepFailedError; exports.JoinError = JoinError; exports.RaceError = RaceError; exports.CancelledError = CancelledError; exports.EntryInProgressError = EntryInProgressError; exports.keyStartsWith = keyStartsWith; exports.compareKeys = compareKeys; exports.keyToHex = keyToHex; exports.isLoopIterationMarker = isLoopIterationMarker; exports.registerName = registerName; exports.resolveName = resolveName; exports.locationToKey = locationToKey; exports.appendName = appendName; exports.appendLoopIteration = appendLoopIteration; exports.emptyLocation = emptyLocation; exports.parentLocation = parentLocation; exports.isLocationPrefix = isLocationPrefix; exports.locationsEqual = locationsEqual; exports.createStorage = createStorage; exports.createHistorySnapshot = createHistorySnapshot; exports.generateId = generateId; exports.createEntry = createEntry; exports.getOrCreateMetadata = getOrCreateMetadata; exports.loadStorage = loadStorage; exports.loadMetadata = loadMetadata; exports.flush = flush; exports.deleteEntriesWithPrefix = deleteEntriesWithPrefix; exports.getEntry = getEntry; exports.setEntry = setEntry; exports.DEFAULT_MAX_RETRIES = DEFAULT_MAX_RETRIES; exports.DEFAULT_RETRY_BACKOFF_BASE = DEFAULT_RETRY_BACKOFF_BASE; exports.DEFAULT_RETRY_BACKOFF_MAX = DEFAULT_RETRY_BACKOFF_MAX; exports.DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL = DEFAULT_LOOP_HISTORY_PRUNE_INTERVAL; exports.DEFAULT_STEP_TIMEOUT = DEFAULT_STEP_TIMEOUT; exports.WorkflowContextImpl = WorkflowContextImpl; exports.sleep = sleep; exports.Loop = Loop; exports.runWorkflow = runWorkflow; exports.replayWorkflowFromStep = replayWorkflowFromStep;
4324
+ //# sourceMappingURL=chunk-KLHCG2KY.cjs.map