@rivetkit/workflow-engine 0.0.0-main.6151ab0 → 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/dist/schemas/v1.ts +25 -0
- package/dist/tsup/{chunk-4SWXLWKL.cjs → chunk-32422MOM.cjs} +266 -105
- package/dist/tsup/chunk-32422MOM.cjs.map +1 -0
- package/dist/tsup/{chunk-UMFB2AR3.js → chunk-TVRZA5O5.js} +266 -105
- package/dist/tsup/chunk-TVRZA5O5.js.map +1 -0
- package/dist/tsup/index.cjs +2 -2
- package/dist/tsup/index.cjs.map +1 -1
- package/dist/tsup/index.d.cts +56 -13
- package/dist/tsup/index.d.ts +56 -13
- package/dist/tsup/index.js +1 -1
- package/dist/tsup/testing.cjs +29 -23
- package/dist/tsup/testing.cjs.map +1 -1
- package/dist/tsup/testing.d.cts +1 -0
- package/dist/tsup/testing.d.ts +1 -0
- package/dist/tsup/testing.js +7 -1
- package/dist/tsup/testing.js.map +1 -1
- package/package.json +6 -1
- package/schemas/serde.ts +17 -3
- package/schemas/v1.bare +10 -1
- package/schemas/versioned.ts +1 -0
- package/src/context.ts +152 -60
- package/src/driver.ts +12 -0
- package/src/index.ts +41 -21
- package/src/location.ts +1 -1
- package/src/storage.ts +98 -17
- package/src/testing.ts +7 -0
- package/src/types.ts +27 -8
- package/dist/tsup/chunk-4SWXLWKL.cjs.map +0 -1
- package/dist/tsup/chunk-UMFB2AR3.js.map +0 -1
package/src/storage.ts
CHANGED
|
@@ -40,6 +40,12 @@ import type {
|
|
|
40
40
|
WorkflowHistorySnapshot,
|
|
41
41
|
} from "./types.js";
|
|
42
42
|
|
|
43
|
+
export const MAX_KV_BATCH_ENTRIES = 128;
|
|
44
|
+
export const MAX_KV_BATCH_PAYLOAD_BYTES = 976 * 1024;
|
|
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
|
+
|
|
43
49
|
/**
|
|
44
50
|
* Create an empty storage instance.
|
|
45
51
|
*/
|
|
@@ -238,6 +244,8 @@ export async function flush(
|
|
|
238
244
|
pendingDeletions?: PendingDeletions,
|
|
239
245
|
): Promise<void> {
|
|
240
246
|
const writes: KVWrite[] = [];
|
|
247
|
+
const dirtyEntries: Entry[] = [];
|
|
248
|
+
const dirtyMetadata: EntryMetadata[] = [];
|
|
241
249
|
let historyUpdated = false;
|
|
242
250
|
|
|
243
251
|
// Flush only new names (those added since last flush)
|
|
@@ -263,7 +271,7 @@ export async function flush(
|
|
|
263
271
|
key: buildHistoryKey(entry.location),
|
|
264
272
|
value: serializeEntry(entry),
|
|
265
273
|
});
|
|
266
|
-
entry
|
|
274
|
+
dirtyEntries.push(entry);
|
|
267
275
|
historyUpdated = true;
|
|
268
276
|
}
|
|
269
277
|
}
|
|
@@ -275,7 +283,7 @@ export async function flush(
|
|
|
275
283
|
key: buildEntryMetadataKey(id),
|
|
276
284
|
value: serializeEntryMetadata(metadata),
|
|
277
285
|
});
|
|
278
|
-
metadata
|
|
286
|
+
dirtyMetadata.push(metadata);
|
|
279
287
|
historyUpdated = true;
|
|
280
288
|
}
|
|
281
289
|
}
|
|
@@ -313,29 +321,31 @@ export async function flush(
|
|
|
313
321
|
}
|
|
314
322
|
|
|
315
323
|
if (writes.length > 0) {
|
|
316
|
-
|
|
324
|
+
if (driver.atomicBatch) {
|
|
325
|
+
await driver.batch(writes);
|
|
326
|
+
} else {
|
|
327
|
+
for (const chunk of splitBatchWrites(writes)) {
|
|
328
|
+
await driver.batch(chunk);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
317
331
|
}
|
|
318
332
|
|
|
319
333
|
// Apply pending deletions after the batch write. These are collected
|
|
320
334
|
// by collectLoopPruning so pruning happens alongside the state write.
|
|
321
335
|
if (pendingDeletions) {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
deleteOps.push(driver.deletePrefix(prefix));
|
|
325
|
-
}
|
|
326
|
-
for (const range of pendingDeletions.ranges) {
|
|
327
|
-
deleteOps.push(driver.deleteRange(range.start, range.end));
|
|
328
|
-
}
|
|
329
|
-
for (const key of pendingDeletions.keys) {
|
|
330
|
-
deleteOps.push(driver.delete(key));
|
|
331
|
-
}
|
|
332
|
-
if (deleteOps.length > 0) {
|
|
333
|
-
await Promise.all(deleteOps);
|
|
336
|
+
const didChange = await runDeletes(driver, pendingDeletions);
|
|
337
|
+
if (didChange) {
|
|
334
338
|
historyUpdated = true;
|
|
335
339
|
}
|
|
336
340
|
}
|
|
337
341
|
|
|
338
342
|
// Update flushed tracking after successful write
|
|
343
|
+
for (const entry of dirtyEntries) {
|
|
344
|
+
entry.dirty = false;
|
|
345
|
+
}
|
|
346
|
+
for (const metadata of dirtyMetadata) {
|
|
347
|
+
metadata.dirty = false;
|
|
348
|
+
}
|
|
339
349
|
storage.flushedNameCount = storage.nameRegistry.length;
|
|
340
350
|
storage.flushedState = storage.state;
|
|
341
351
|
storage.flushedOutput = storage.output;
|
|
@@ -346,6 +356,78 @@ export async function flush(
|
|
|
346
356
|
}
|
|
347
357
|
}
|
|
348
358
|
|
|
359
|
+
function splitBatchWrites(writes: KVWrite[]): KVWrite[][] {
|
|
360
|
+
const chunks: KVWrite[][] = [];
|
|
361
|
+
let chunk: KVWrite[] = [];
|
|
362
|
+
let chunkBytes = 0;
|
|
363
|
+
|
|
364
|
+
for (const write of writes) {
|
|
365
|
+
const writeBytes = write.key.byteLength + write.value.byteLength;
|
|
366
|
+
if (writeBytes > MAX_KV_BATCH_PAYLOAD_BYTES) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`KV batch write is ${writeBytes} bytes, exceeding the ${MAX_KV_BATCH_PAYLOAD_BYTES} byte limit`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (
|
|
373
|
+
chunk.length >= MAX_KV_BATCH_ENTRIES ||
|
|
374
|
+
(chunk.length > 0 &&
|
|
375
|
+
chunkBytes + writeBytes > MAX_KV_BATCH_PAYLOAD_BYTES)
|
|
376
|
+
) {
|
|
377
|
+
chunks.push(chunk);
|
|
378
|
+
chunk = [];
|
|
379
|
+
chunkBytes = 0;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
chunk.push(write);
|
|
383
|
+
chunkBytes += writeBytes;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (chunk.length > 0) {
|
|
387
|
+
chunks.push(chunk);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return chunks;
|
|
391
|
+
}
|
|
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
|
+
|
|
349
431
|
/**
|
|
350
432
|
* Delete entries with a given location prefix (used for loop forgetting).
|
|
351
433
|
* Also cleans up associated metadata from both memory and driver.
|
|
@@ -359,8 +441,7 @@ export async function deleteEntriesWithPrefix(
|
|
|
359
441
|
const deletions = collectDeletionsForPrefix(storage, prefixLocation);
|
|
360
442
|
|
|
361
443
|
// Apply deletions to driver
|
|
362
|
-
await driver
|
|
363
|
-
await Promise.all(deletions.keys.map((key) => driver.delete(key)));
|
|
444
|
+
await runDeletes(driver, deletions);
|
|
364
445
|
|
|
365
446
|
if (deletions.keys.length > 0 && onHistoryUpdated) {
|
|
366
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 =
|
|
@@ -422,11 +437,7 @@ export interface TryStepConfig<T> extends StepConfig<T> {
|
|
|
422
437
|
catch?: readonly TryStepCatchKind[];
|
|
423
438
|
}
|
|
424
439
|
|
|
425
|
-
export type TryBlockCatchKind =
|
|
426
|
-
| "step"
|
|
427
|
-
| "join"
|
|
428
|
-
| "race"
|
|
429
|
-
| "rollback";
|
|
440
|
+
export type TryBlockCatchKind = "step" | "join" | "race" | "rollback";
|
|
430
441
|
|
|
431
442
|
export interface TryBlockFailure {
|
|
432
443
|
source: "step" | "join" | "race" | "block";
|
|
@@ -459,7 +470,7 @@ export type LoopResult<S, T> =
|
|
|
459
470
|
* `Loop.continue(undefined)`.
|
|
460
471
|
*/
|
|
461
472
|
export type LoopIterationResult<S, T> = Promise<
|
|
462
|
-
LoopResult<S, T> | (S extends undefined ?
|
|
473
|
+
LoopResult<S, T> | (S extends undefined ? undefined : never)
|
|
463
474
|
>;
|
|
464
475
|
|
|
465
476
|
/**
|
|
@@ -473,6 +484,12 @@ export interface LoopConfig<S, T> {
|
|
|
473
484
|
historyPruneInterval?: number;
|
|
474
485
|
/** Number of past iterations to retain when pruning. Defaults to historyPruneInterval. */
|
|
475
486
|
historySize?: number;
|
|
487
|
+
/** @deprecated Use historyPruneInterval. */
|
|
488
|
+
commitInterval?: number;
|
|
489
|
+
/** @deprecated Use historyPruneInterval. */
|
|
490
|
+
historyEvery?: number;
|
|
491
|
+
/** @deprecated Use historySize. */
|
|
492
|
+
historyKeep?: number;
|
|
476
493
|
}
|
|
477
494
|
|
|
478
495
|
/**
|
|
@@ -535,6 +552,8 @@ export interface WorkflowContextInterface {
|
|
|
535
552
|
|
|
536
553
|
removed(name: string, originalType: EntryKindType): Promise<void>;
|
|
537
554
|
|
|
555
|
+
getVersion(name: string, latest: number): Promise<number>;
|
|
556
|
+
|
|
538
557
|
isEvicted(): boolean;
|
|
539
558
|
}
|
|
540
559
|
|