@rivetkit/workflow-engine 0.0.0-main.72cbc7c → 0.0.0-main.78336a1

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/src/storage.ts CHANGED
@@ -43,6 +43,9 @@ import type {
43
43
  export const MAX_KV_BATCH_ENTRIES = 128;
44
44
  export const MAX_KV_BATCH_PAYLOAD_BYTES = 976 * 1024;
45
45
 
46
+ /** Max delete ops (one transaction/permit each) run at once, under the 128-permit cap. */
47
+ export const MAX_CONCURRENT_DELETES = 64;
48
+
46
49
  /**
47
50
  * Create an empty storage instance.
48
51
  */
@@ -318,26 +321,20 @@ export async function flush(
318
321
  }
319
322
 
320
323
  if (writes.length > 0) {
321
- for (const chunk of splitBatchWrites(writes)) {
322
- await driver.batch(chunk);
324
+ if (driver.atomicBatch) {
325
+ await driver.batch(writes);
326
+ } else {
327
+ for (const chunk of splitBatchWrites(writes)) {
328
+ await driver.batch(chunk);
329
+ }
323
330
  }
324
331
  }
325
332
 
326
333
  // Apply pending deletions after the batch write. These are collected
327
334
  // by collectLoopPruning so pruning happens alongside the state write.
328
335
  if (pendingDeletions) {
329
- const deleteOps: Promise<void>[] = [];
330
- for (const prefix of pendingDeletions.prefixes) {
331
- deleteOps.push(driver.deletePrefix(prefix));
332
- }
333
- for (const range of pendingDeletions.ranges) {
334
- deleteOps.push(driver.deleteRange(range.start, range.end));
335
- }
336
- for (const key of pendingDeletions.keys) {
337
- deleteOps.push(driver.delete(key));
338
- }
339
- if (deleteOps.length > 0) {
340
- await Promise.all(deleteOps);
336
+ const didChange = await runDeletes(driver, pendingDeletions);
337
+ if (didChange) {
341
338
  historyUpdated = true;
342
339
  }
343
340
  }
@@ -393,6 +390,44 @@ function splitBatchWrites(writes: KVWrite[]): KVWrite[][] {
393
390
  return chunks;
394
391
  }
395
392
 
393
+ /**
394
+ * Split delete keys into batches within one KV transaction (MAX_KV_BATCH_ENTRIES).
395
+ */
396
+ function splitBatchDeletes(keys: Uint8Array[]): Uint8Array[][] {
397
+ const chunks: Uint8Array[][] = [];
398
+ for (let i = 0; i < keys.length; i += MAX_KV_BATCH_ENTRIES) {
399
+ chunks.push(keys.slice(i, i + MAX_KV_BATCH_ENTRIES));
400
+ }
401
+ return chunks;
402
+ }
403
+
404
+ /**
405
+ * Apply deletions concurrently in bounded rounds; returns whether anything was deleted.
406
+ */
407
+ async function runDeletes(
408
+ driver: EngineDriver,
409
+ deletions: PendingDeletions,
410
+ ): Promise<boolean> {
411
+ const ops = [
412
+ ...deletions.prefixes.map((prefix) => () => driver.deletePrefix(prefix)),
413
+ ...deletions.ranges.map(
414
+ (range) => () => driver.deleteRange(range.start, range.end),
415
+ ),
416
+ ...splitBatchDeletes(deletions.keys).map(
417
+ (chunk) => () => driver.batchDelete(chunk),
418
+ ),
419
+ ];
420
+ if (ops.length === 0) {
421
+ return false;
422
+ }
423
+ for (let i = 0; i < ops.length; i += MAX_CONCURRENT_DELETES) {
424
+ await Promise.all(
425
+ ops.slice(i, i + MAX_CONCURRENT_DELETES).map((op) => op()),
426
+ );
427
+ }
428
+ return true;
429
+ }
430
+
396
431
  /**
397
432
  * Delete entries with a given location prefix (used for loop forgetting).
398
433
  * Also cleans up associated metadata from both memory and driver.
@@ -406,8 +441,7 @@ export async function deleteEntriesWithPrefix(
406
441
  const deletions = collectDeletionsForPrefix(storage, prefixLocation);
407
442
 
408
443
  // Apply deletions to driver
409
- await driver.deletePrefix(deletions.prefixes[0]!);
410
- await Promise.all(deletions.keys.map((key) => driver.delete(key)));
444
+ await runDeletes(driver, deletions);
411
445
 
412
446
  if (deletions.keys.length > 0 && onHistoryUpdated) {
413
447
  onHistoryUpdated();
package/src/testing.ts CHANGED
@@ -173,6 +173,13 @@ export class InMemoryDriver implements EngineDriver {
173
173
  this.kv.delete(keyToHex(key));
174
174
  }
175
175
 
176
+ async batchDelete(keys: Uint8Array[]): Promise<void> {
177
+ await sleep(this.latency);
178
+ for (const key of keys) {
179
+ this.kv.delete(keyToHex(key));
180
+ }
181
+ }
182
+
176
183
  async deletePrefix(prefix: Uint8Array): Promise<void> {
177
184
  await sleep(this.latency);
178
185
  for (const [hexKey, entry] of this.kv) {
package/src/types.ts CHANGED
@@ -135,6 +135,17 @@ export interface RemovedEntry {
135
135
  originalName?: string;
136
136
  }
137
137
 
138
+ /**
139
+ * Version check entry data - records which version of a code path this
140
+ * workflow instance is pinned to at a given location.
141
+ */
142
+ export interface VersionCheckEntry {
143
+ /** The version this instance resolved to at this location. */
144
+ resolved: number;
145
+ /** The `latest` value seen when this entry was first resolved (diagnostics). */
146
+ latest: number;
147
+ }
148
+
138
149
  /**
139
150
  * All possible entry kind types.
140
151
  */
@@ -146,7 +157,8 @@ export type EntryKindType =
146
157
  | "rollback_checkpoint"
147
158
  | "join"
148
159
  | "race"
149
- | "removed";
160
+ | "removed"
161
+ | "version_check";
150
162
 
151
163
  /**
152
164
  * Type-specific entry data.
@@ -159,7 +171,8 @@ export type EntryKind =
159
171
  | { type: "rollback_checkpoint"; data: RollbackCheckpointEntry }
160
172
  | { type: "join"; data: JoinEntry }
161
173
  | { type: "race"; data: RaceEntry }
162
- | { type: "removed"; data: RemovedEntry };
174
+ | { type: "removed"; data: RemovedEntry }
175
+ | { type: "version_check"; data: VersionCheckEntry };
163
176
 
164
177
  /**
165
178
  * An entry in the workflow history.
@@ -399,6 +412,8 @@ export interface StepConfig<T> {
399
412
  retryBackoffMax?: number;
400
413
  /** Timeout in ms for step execution (default: 30000). Set to 0 to disable. */
401
414
  timeout?: number;
415
+ /** If true, step timeouts retry like any other error instead of failing immediately as critical. Default: false. */
416
+ retryOnTimeout?: boolean;
402
417
  }
403
418
 
404
419
  export type TryStepCatchKind =
@@ -455,7 +470,7 @@ export type LoopResult<S, T> =
455
470
  * `Loop.continue(undefined)`.
456
471
  */
457
472
  export type LoopIterationResult<S, T> = Promise<
458
- LoopResult<S, T> | (S extends undefined ? void : never)
473
+ LoopResult<S, T> | (S extends undefined ? undefined : never)
459
474
  >;
460
475
 
461
476
  /**
@@ -537,6 +552,8 @@ export interface WorkflowContextInterface {
537
552
 
538
553
  removed(name: string, originalType: EntryKindType): Promise<void>;
539
554
 
555
+ getVersion(name: string, latest: number): Promise<number>;
556
+
540
557
  isEvicted(): boolean;
541
558
  }
542
559