@rivetkit/workflow-engine 0.0.0-feat-workflows-public-host-apis.1550fe4 → 0.0.0-feat-sqlite-sync.18dce02
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/tsup/{chunk-DTS4S7RT.js → chunk-AAAPEYGX.js} +123 -42
- package/dist/tsup/chunk-AAAPEYGX.js.map +1 -0
- package/dist/tsup/{chunk-KLHCG2KY.cjs → chunk-XRITRTKT.cjs} +123 -42
- package/dist/tsup/chunk-XRITRTKT.cjs.map +1 -0
- package/dist/tsup/index.cjs +2 -2
- package/dist/tsup/index.cjs.map +1 -1
- package/dist/tsup/index.d.cts +36 -2
- package/dist/tsup/index.d.ts +36 -2
- 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 +2 -1
- package/dist/tsup/testing.d.ts +2 -1
- package/dist/tsup/testing.js +7 -1
- package/dist/tsup/testing.js.map +1 -1
- package/package.json +2 -2
- package/src/context.ts +15 -2
- package/src/driver.ts +45 -0
- package/src/index.ts +88 -22
- package/src/keys.ts +1 -0
- package/src/storage.ts +46 -14
- package/src/testing.ts +7 -0
- package/dist/tsup/chunk-DTS4S7RT.js.map +0 -1
- package/dist/tsup/chunk-KLHCG2KY.cjs.map +0 -1
package/src/context.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Logger } from "pino";
|
|
2
|
-
import type { EngineDriver } from "./driver.js";
|
|
2
|
+
import type { EngineDriver, WorkflowSpan } from "./driver.js";
|
|
3
3
|
import {
|
|
4
4
|
extractErrorInfo,
|
|
5
5
|
getErrorEventTag,
|
|
@@ -345,6 +345,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
345
345
|
private abortController: AbortController;
|
|
346
346
|
private currentLocation: Location;
|
|
347
347
|
private visitedKeys: Set<string>;
|
|
348
|
+
private runSpan?: WorkflowSpan;
|
|
348
349
|
private mode: "forward" | "rollback";
|
|
349
350
|
private rollbackActions?: RollbackAction[];
|
|
350
351
|
private rollbackCheckpointSet: boolean;
|
|
@@ -369,6 +370,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
369
370
|
onError?: WorkflowErrorHandler,
|
|
370
371
|
logger?: Logger,
|
|
371
372
|
visitedKeys?: Set<string>,
|
|
373
|
+
runSpan?: WorkflowSpan,
|
|
372
374
|
) {
|
|
373
375
|
this.currentLocation = location;
|
|
374
376
|
this.abortController = abortController ?? new AbortController();
|
|
@@ -379,6 +381,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
379
381
|
this.onError = onError;
|
|
380
382
|
this.logger = logger;
|
|
381
383
|
this.visitedKeys = visitedKeys ?? new Set();
|
|
384
|
+
this.runSpan = runSpan;
|
|
382
385
|
}
|
|
383
386
|
|
|
384
387
|
get abortSignal(): AbortSignal {
|
|
@@ -435,6 +438,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
435
438
|
this.onError,
|
|
436
439
|
this.logger,
|
|
437
440
|
this.visitedKeys,
|
|
441
|
+
this.runSpan,
|
|
438
442
|
);
|
|
439
443
|
}
|
|
440
444
|
|
|
@@ -906,10 +910,15 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
906
910
|
// Get timeout configuration
|
|
907
911
|
const timeout = config.timeout ?? DEFAULT_STEP_TIMEOUT;
|
|
908
912
|
|
|
913
|
+
const stepSpan = this.runSpan?.startStep(
|
|
914
|
+
config.name,
|
|
915
|
+
metadata.attempts,
|
|
916
|
+
);
|
|
917
|
+
|
|
909
918
|
try {
|
|
910
919
|
// Execute with timeout
|
|
911
920
|
const output = await this.executeWithTimeout(
|
|
912
|
-
config.run(),
|
|
921
|
+
stepSpan ? stepSpan.run(() => config.run()) : config.run(),
|
|
913
922
|
timeout,
|
|
914
923
|
config.name,
|
|
915
924
|
);
|
|
@@ -936,6 +945,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
936
945
|
await this.flushStorage();
|
|
937
946
|
}
|
|
938
947
|
|
|
948
|
+
stepSpan?.finish("ok");
|
|
939
949
|
this.log("debug", {
|
|
940
950
|
msg: "step completed",
|
|
941
951
|
step: config.name,
|
|
@@ -951,6 +961,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
951
961
|
// Timeout errors are treated as critical by default. Steps opt
|
|
952
962
|
// into retrying on timeout with retryOnTimeout: true.
|
|
953
963
|
if (error instanceof StepTimeoutError && !config.retryOnTimeout) {
|
|
964
|
+
stepSpan?.finish("failed", error);
|
|
954
965
|
metadata.status = "exhausted";
|
|
955
966
|
metadata.error = String(error);
|
|
956
967
|
await this.notifyStepError(config, metadata.attempts, error, {
|
|
@@ -970,6 +981,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
970
981
|
error instanceof CriticalError ||
|
|
971
982
|
error instanceof RollbackError
|
|
972
983
|
) {
|
|
984
|
+
stepSpan?.finish("failed", error);
|
|
973
985
|
metadata.status = "exhausted";
|
|
974
986
|
metadata.error = String(error);
|
|
975
987
|
await this.notifyStepError(config, metadata.attempts, error, {
|
|
@@ -989,6 +1001,7 @@ export class WorkflowContextImpl implements WorkflowContextInterface {
|
|
|
989
1001
|
}
|
|
990
1002
|
|
|
991
1003
|
const willRetry = metadata.attempts <= maxRetries;
|
|
1004
|
+
stepSpan?.finish(willRetry ? "retry" : "failed", error);
|
|
992
1005
|
metadata.status = willRetry ? "failed" : "exhausted";
|
|
993
1006
|
metadata.error = String(error);
|
|
994
1007
|
|
package/src/driver.ts
CHANGED
|
@@ -16,6 +16,42 @@ export interface KVWrite {
|
|
|
16
16
|
value: Uint8Array;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/** How one run of the workflow function ended. */
|
|
20
|
+
export type WorkflowOutcome =
|
|
21
|
+
| "completed"
|
|
22
|
+
| "sleeping"
|
|
23
|
+
| "evicted"
|
|
24
|
+
| "failed"
|
|
25
|
+
| "cancelled";
|
|
26
|
+
|
|
27
|
+
/** How one step attempt ended. `retry` will be tried again, `failed` will not. */
|
|
28
|
+
export type WorkflowStepOutcome = "ok" | "retry" | "failed";
|
|
29
|
+
|
|
30
|
+
/** One open run of the workflow function. */
|
|
31
|
+
export interface WorkflowSpan {
|
|
32
|
+
/** Runs `body` attributed to this run. */
|
|
33
|
+
run<T>(body: () => Promise<T>): Promise<T>;
|
|
34
|
+
/** Returns nothing when the attempt is not being traced. */
|
|
35
|
+
startStep(name: string, attempt: number): WorkflowStepSpan | undefined;
|
|
36
|
+
finish(outcome: WorkflowOutcome): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** One open attempt at one step. */
|
|
40
|
+
export interface WorkflowStepSpan {
|
|
41
|
+
/** Runs `body` attributed to this attempt. */
|
|
42
|
+
run<T>(body: () => Promise<T>): Promise<T>;
|
|
43
|
+
/** `error` is what the step callback threw. */
|
|
44
|
+
finish(outcome: WorkflowStepOutcome, error?: unknown): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Receives the start and end of each workflow run and step attempt. A
|
|
49
|
+
* completed step replayed from history is not an attempt and is not reported.
|
|
50
|
+
*/
|
|
51
|
+
export interface WorkflowTelemetryDriver {
|
|
52
|
+
startSpan(): Promise<WorkflowSpan>;
|
|
53
|
+
}
|
|
54
|
+
|
|
19
55
|
/**
|
|
20
56
|
* The engine driver provides the KV and scheduling interface.
|
|
21
57
|
* Implementations must provide these methods to integrate with different backends.
|
|
@@ -54,6 +90,12 @@ export interface EngineDriver {
|
|
|
54
90
|
*/
|
|
55
91
|
delete(key: Uint8Array): Promise<void>;
|
|
56
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Batch delete multiple keys in a single operation.
|
|
95
|
+
* Should be atomic if possible.
|
|
96
|
+
*/
|
|
97
|
+
batchDelete(keys: Uint8Array[]): Promise<void>;
|
|
98
|
+
|
|
57
99
|
/**
|
|
58
100
|
* Delete all keys with a given prefix.
|
|
59
101
|
*/
|
|
@@ -111,4 +153,7 @@ export interface EngineDriver {
|
|
|
111
153
|
messageNames: string[],
|
|
112
154
|
abortSignal: AbortSignal,
|
|
113
155
|
): Promise<void>;
|
|
156
|
+
|
|
157
|
+
/** Present when the host traces workflows. */
|
|
158
|
+
readonly telemetry?: WorkflowTelemetryDriver;
|
|
114
159
|
}
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,16 @@ export {
|
|
|
12
12
|
WorkflowContextImpl,
|
|
13
13
|
} from "./context.js";
|
|
14
14
|
// Driver
|
|
15
|
-
export type {
|
|
15
|
+
export type {
|
|
16
|
+
EngineDriver,
|
|
17
|
+
KVEntry,
|
|
18
|
+
KVWrite,
|
|
19
|
+
WorkflowOutcome,
|
|
20
|
+
WorkflowSpan,
|
|
21
|
+
WorkflowStepOutcome,
|
|
22
|
+
WorkflowStepSpan,
|
|
23
|
+
WorkflowTelemetryDriver,
|
|
24
|
+
} from "./driver.js";
|
|
16
25
|
export { extractErrorInfo } from "./error-utils.js";
|
|
17
26
|
// Errors
|
|
18
27
|
export {
|
|
@@ -143,7 +152,7 @@ import {
|
|
|
143
152
|
} from "../schemas/serde.js";
|
|
144
153
|
import { type RollbackAction, WorkflowContextImpl } from "./context.js";
|
|
145
154
|
// Main workflow runner
|
|
146
|
-
import type { EngineDriver } from "./driver.js";
|
|
155
|
+
import type { EngineDriver, WorkflowOutcome, WorkflowSpan } from "./driver.js";
|
|
147
156
|
import {
|
|
148
157
|
extractErrorInfo,
|
|
149
158
|
getErrorEventTag,
|
|
@@ -288,6 +297,7 @@ async function executeRollback<TInput, TOutput>(
|
|
|
288
297
|
historyNotifier?: HistoryNotifier,
|
|
289
298
|
onError?: RunWorkflowOptions["onError"],
|
|
290
299
|
logger?: Logger,
|
|
300
|
+
runSpan?: WorkflowSpan,
|
|
291
301
|
): Promise<void> {
|
|
292
302
|
const rollbackActions: RollbackAction[] = [];
|
|
293
303
|
const ctx = new WorkflowContextImpl(
|
|
@@ -303,6 +313,8 @@ async function executeRollback<TInput, TOutput>(
|
|
|
303
313
|
historyNotifier,
|
|
304
314
|
onError,
|
|
305
315
|
logger,
|
|
316
|
+
undefined,
|
|
317
|
+
runSpan,
|
|
306
318
|
);
|
|
307
319
|
|
|
308
320
|
try {
|
|
@@ -511,16 +523,19 @@ async function executeLiveWorkflow<TInput, TOutput>(
|
|
|
511
523
|
let lastResult: WorkflowResult<TOutput> | undefined;
|
|
512
524
|
|
|
513
525
|
while (true) {
|
|
514
|
-
const result = await
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
526
|
+
const result = await traceRun(driver, (runSpan) =>
|
|
527
|
+
executeWorkflow(
|
|
528
|
+
workflowId,
|
|
529
|
+
workflowFn,
|
|
530
|
+
input,
|
|
531
|
+
driver,
|
|
532
|
+
messageDriver,
|
|
533
|
+
abortController,
|
|
534
|
+
onHistoryUpdated,
|
|
535
|
+
onError,
|
|
536
|
+
logger,
|
|
537
|
+
runSpan,
|
|
538
|
+
),
|
|
524
539
|
);
|
|
525
540
|
lastResult = result;
|
|
526
541
|
|
|
@@ -642,16 +657,19 @@ export function runWorkflow<TInput, TOutput>(
|
|
|
642
657
|
options.onError,
|
|
643
658
|
logger,
|
|
644
659
|
)
|
|
645
|
-
:
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
660
|
+
: traceRun(driver, (runSpan) =>
|
|
661
|
+
executeWorkflow(
|
|
662
|
+
workflowId,
|
|
663
|
+
workflowFn,
|
|
664
|
+
input,
|
|
665
|
+
driver,
|
|
666
|
+
messageDriver,
|
|
667
|
+
abortController,
|
|
668
|
+
options.onHistoryUpdated,
|
|
669
|
+
options.onError,
|
|
670
|
+
logger,
|
|
671
|
+
runSpan,
|
|
672
|
+
),
|
|
655
673
|
);
|
|
656
674
|
|
|
657
675
|
return {
|
|
@@ -886,6 +904,49 @@ function findReplayBoundaryEntry(
|
|
|
886
904
|
return boundary;
|
|
887
905
|
}
|
|
888
906
|
|
|
907
|
+
function runOutcomeFromState(state: WorkflowState): WorkflowOutcome {
|
|
908
|
+
switch (state) {
|
|
909
|
+
case "completed":
|
|
910
|
+
return "completed";
|
|
911
|
+
case "sleeping":
|
|
912
|
+
return "sleeping";
|
|
913
|
+
case "failed":
|
|
914
|
+
return "failed";
|
|
915
|
+
case "cancelled":
|
|
916
|
+
return "cancelled";
|
|
917
|
+
case "pending":
|
|
918
|
+
case "running":
|
|
919
|
+
case "rolling_back":
|
|
920
|
+
return "evicted";
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/** Internal: Report one workflow run to the driver's telemetry, when it has any. */
|
|
925
|
+
async function traceRun<TOutput>(
|
|
926
|
+
driver: EngineDriver,
|
|
927
|
+
run: (span?: WorkflowSpan) => Promise<WorkflowResult<TOutput>>,
|
|
928
|
+
): Promise<WorkflowResult<TOutput>> {
|
|
929
|
+
if (!driver.telemetry) {
|
|
930
|
+
return await run();
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const span = await driver.telemetry.startSpan();
|
|
934
|
+
let outcome: WorkflowOutcome = "failed";
|
|
935
|
+
try {
|
|
936
|
+
const result = await span.run(() => run(span));
|
|
937
|
+
outcome = runOutcomeFromState(result.state);
|
|
938
|
+
return result;
|
|
939
|
+
} catch (error) {
|
|
940
|
+
// A run throws EvictedError only when the workflow was already cancelled.
|
|
941
|
+
if (error instanceof EvictedError) {
|
|
942
|
+
outcome = "cancelled";
|
|
943
|
+
}
|
|
944
|
+
throw error;
|
|
945
|
+
} finally {
|
|
946
|
+
await span.finish(outcome);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
889
950
|
/**
|
|
890
951
|
* Internal: Execute the workflow and return the result.
|
|
891
952
|
*/
|
|
@@ -899,6 +960,7 @@ async function executeWorkflow<TInput, TOutput>(
|
|
|
899
960
|
onHistoryUpdated?: (history: WorkflowHistorySnapshot) => void,
|
|
900
961
|
onError?: RunWorkflowOptions["onError"],
|
|
901
962
|
logger?: Logger,
|
|
963
|
+
runSpan?: WorkflowSpan,
|
|
902
964
|
): Promise<WorkflowResult<TOutput>> {
|
|
903
965
|
const storage = await loadStorage(driver);
|
|
904
966
|
const historyNotifier: HistoryNotifier = onHistoryUpdated
|
|
@@ -953,6 +1015,7 @@ async function executeWorkflow<TInput, TOutput>(
|
|
|
953
1015
|
historyNotifier,
|
|
954
1016
|
onError,
|
|
955
1017
|
logger,
|
|
1018
|
+
runSpan,
|
|
956
1019
|
);
|
|
957
1020
|
} catch (error) {
|
|
958
1021
|
if (error instanceof EvictedError) {
|
|
@@ -986,6 +1049,8 @@ async function executeWorkflow<TInput, TOutput>(
|
|
|
986
1049
|
historyNotifier,
|
|
987
1050
|
onError,
|
|
988
1051
|
logger,
|
|
1052
|
+
undefined,
|
|
1053
|
+
runSpan,
|
|
989
1054
|
);
|
|
990
1055
|
|
|
991
1056
|
storage.state = "running";
|
|
@@ -1064,6 +1129,7 @@ async function executeWorkflow<TInput, TOutput>(
|
|
|
1064
1129
|
historyNotifier,
|
|
1065
1130
|
onError,
|
|
1066
1131
|
logger,
|
|
1132
|
+
runSpan,
|
|
1067
1133
|
);
|
|
1068
1134
|
} catch (rollbackError) {
|
|
1069
1135
|
if (rollbackError instanceof EvictedError) {
|
package/src/keys.ts
CHANGED
|
@@ -14,6 +14,7 @@ export const KEY_PREFIX = {
|
|
|
14
14
|
HISTORY: 2, // History entries: [2, ...locationSegments]
|
|
15
15
|
WORKFLOW: 3, // Workflow metadata: [3, field]
|
|
16
16
|
ENTRY_METADATA: 4, // Entry metadata: [4, entryId]
|
|
17
|
+
// 5 is reserved for RivetKit's workflow trace context.
|
|
17
18
|
} as const;
|
|
18
19
|
|
|
19
20
|
// Workflow metadata field identifiers
|
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
|
*/
|
|
@@ -330,18 +333,8 @@ export async function flush(
|
|
|
330
333
|
// Apply pending deletions after the batch write. These are collected
|
|
331
334
|
// by collectLoopPruning so pruning happens alongside the state write.
|
|
332
335
|
if (pendingDeletions) {
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
deleteOps.push(driver.deletePrefix(prefix));
|
|
336
|
-
}
|
|
337
|
-
for (const range of pendingDeletions.ranges) {
|
|
338
|
-
deleteOps.push(driver.deleteRange(range.start, range.end));
|
|
339
|
-
}
|
|
340
|
-
for (const key of pendingDeletions.keys) {
|
|
341
|
-
deleteOps.push(driver.delete(key));
|
|
342
|
-
}
|
|
343
|
-
if (deleteOps.length > 0) {
|
|
344
|
-
await Promise.all(deleteOps);
|
|
336
|
+
const didChange = await runDeletes(driver, pendingDeletions);
|
|
337
|
+
if (didChange) {
|
|
345
338
|
historyUpdated = true;
|
|
346
339
|
}
|
|
347
340
|
}
|
|
@@ -397,6 +390,46 @@ function splitBatchWrites(writes: KVWrite[]): KVWrite[][] {
|
|
|
397
390
|
return chunks;
|
|
398
391
|
}
|
|
399
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(
|
|
413
|
+
(prefix) => () => driver.deletePrefix(prefix),
|
|
414
|
+
),
|
|
415
|
+
...deletions.ranges.map(
|
|
416
|
+
(range) => () => driver.deleteRange(range.start, range.end),
|
|
417
|
+
),
|
|
418
|
+
...splitBatchDeletes(deletions.keys).map(
|
|
419
|
+
(chunk) => () => driver.batchDelete(chunk),
|
|
420
|
+
),
|
|
421
|
+
];
|
|
422
|
+
if (ops.length === 0) {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
for (let i = 0; i < ops.length; i += MAX_CONCURRENT_DELETES) {
|
|
426
|
+
await Promise.all(
|
|
427
|
+
ops.slice(i, i + MAX_CONCURRENT_DELETES).map((op) => op()),
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
|
|
400
433
|
/**
|
|
401
434
|
* Delete entries with a given location prefix (used for loop forgetting).
|
|
402
435
|
* Also cleans up associated metadata from both memory and driver.
|
|
@@ -410,8 +443,7 @@ export async function deleteEntriesWithPrefix(
|
|
|
410
443
|
const deletions = collectDeletionsForPrefix(storage, prefixLocation);
|
|
411
444
|
|
|
412
445
|
// Apply deletions to driver
|
|
413
|
-
await driver
|
|
414
|
-
await Promise.all(deletions.keys.map((key) => driver.delete(key)));
|
|
446
|
+
await runDeletes(driver, deletions);
|
|
415
447
|
|
|
416
448
|
if (deletions.keys.length > 0 && onHistoryUpdated) {
|
|
417
449
|
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) {
|