rivetkit 2.3.3 → 2.3.5-rc.1

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.
Files changed (161) hide show
  1. package/dist/browser/client.d.ts +157 -9
  2. package/dist/browser/client.js +518 -542
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.d.ts +101 -2
  5. package/dist/browser/inspector/client.js +1245 -227
  6. package/dist/browser/inspector/client.js.map +1 -1
  7. package/dist/tsup/agent-os/index.cjs +103 -36
  8. package/dist/tsup/agent-os/index.cjs.map +1 -1
  9. package/dist/tsup/agent-os/index.d.cts +159 -11
  10. package/dist/tsup/agent-os/index.d.ts +159 -11
  11. package/dist/tsup/agent-os/index.js +103 -36
  12. package/dist/tsup/agent-os/index.js.map +1 -1
  13. package/dist/tsup/{chunk-34P567PO.cjs → chunk-4XKED5P5.cjs} +145 -176
  14. package/dist/tsup/chunk-4XKED5P5.cjs.map +1 -0
  15. package/dist/tsup/{chunk-PYO2U53V.cjs → chunk-6HQCLWAW.cjs} +384 -4
  16. package/dist/tsup/chunk-6HQCLWAW.cjs.map +1 -0
  17. package/dist/tsup/{chunk-JZ7TWV65.js → chunk-7AFZMFPQ.js} +15 -1
  18. package/dist/tsup/chunk-7AFZMFPQ.js.map +1 -0
  19. package/dist/tsup/{chunk-BLDO2FKO.js → chunk-B4IZU537.js} +382 -2
  20. package/dist/tsup/chunk-B4IZU537.js.map +1 -0
  21. package/dist/tsup/{chunk-3MHDOUD7.cjs → chunk-G3CCRCTK.cjs} +14 -9
  22. package/dist/tsup/chunk-G3CCRCTK.cjs.map +1 -0
  23. package/dist/tsup/{chunk-CXRPENO3.js → chunk-GAV4PKFT.js} +15 -46
  24. package/dist/tsup/{chunk-CXRPENO3.js.map → chunk-GAV4PKFT.js.map} +1 -1
  25. package/dist/tsup/{chunk-HXUEHHJF.js → chunk-JI6GZ2C2.js} +2 -11
  26. package/dist/tsup/{chunk-HXUEHHJF.js.map → chunk-JI6GZ2C2.js.map} +1 -1
  27. package/dist/tsup/{chunk-YUEC5SEY.cjs → chunk-M3FCXL3Q.cjs} +3 -3
  28. package/dist/tsup/{chunk-YUEC5SEY.cjs.map → chunk-M3FCXL3Q.cjs.map} +1 -1
  29. package/dist/tsup/{chunk-KEKWJECX.js → chunk-MGTSVSUC.js} +2 -2
  30. package/dist/tsup/{chunk-VE2X4KMG.cjs → chunk-MYWZCDML.cjs} +2 -11
  31. package/dist/tsup/chunk-MYWZCDML.cjs.map +1 -0
  32. package/dist/tsup/{chunk-DFA7SYSW.js → chunk-NIC3QLKL.js} +3 -3
  33. package/dist/tsup/{chunk-BZM4PMFS.js → chunk-P4OGQ4JL.js} +5 -7
  34. package/dist/tsup/{chunk-BZM4PMFS.js.map → chunk-P4OGQ4JL.js.map} +1 -1
  35. package/dist/tsup/{chunk-NT2NK3VT.js → chunk-PAG6G46L.js} +2 -2
  36. package/dist/tsup/{chunk-XIX5DOZN.js → chunk-SHBE77IT.js} +14 -9
  37. package/dist/tsup/chunk-SHBE77IT.js.map +1 -0
  38. package/dist/tsup/{chunk-TYLP3U4Y.cjs → chunk-TNIWIUNV.cjs} +9 -11
  39. package/dist/tsup/chunk-TNIWIUNV.cjs.map +1 -0
  40. package/dist/tsup/{chunk-S644FL73.cjs → chunk-TR5NLR5V.cjs} +4 -4
  41. package/dist/tsup/{chunk-S644FL73.cjs.map → chunk-TR5NLR5V.cjs.map} +1 -1
  42. package/dist/tsup/{chunk-ZA7FLHKH.cjs → chunk-UEULFXUI.cjs} +16 -2
  43. package/dist/tsup/chunk-UEULFXUI.cjs.map +1 -0
  44. package/dist/tsup/{chunk-X63ROSOG.cjs → chunk-UJ2FQY63.cjs} +7 -7
  45. package/dist/tsup/{chunk-X63ROSOG.cjs.map → chunk-UJ2FQY63.cjs.map} +1 -1
  46. package/dist/tsup/client/mod.cjs +7 -8
  47. package/dist/tsup/client/mod.cjs.map +1 -1
  48. package/dist/tsup/client/mod.d.cts +5 -4
  49. package/dist/tsup/client/mod.d.ts +5 -4
  50. package/dist/tsup/client/mod.js +6 -7
  51. package/dist/tsup/common/log.cjs +2 -2
  52. package/dist/tsup/common/log.js +1 -1
  53. package/dist/tsup/common/websocket.cjs +3 -3
  54. package/dist/tsup/common/websocket.js +2 -2
  55. package/dist/tsup/{config-D49x8NpL.d.cts → config-BxOimoGX.d.cts} +16 -0
  56. package/dist/tsup/{config-D49x8NpL.d.ts → config-BxOimoGX.d.ts} +16 -0
  57. package/dist/tsup/{config-ZkIsRFsN.d.ts → config-BzdqbLAP.d.ts} +164 -27
  58. package/dist/tsup/{config-BfBhjEbk.d.cts → config-CPPEwn_e.d.cts} +164 -27
  59. package/dist/tsup/{context-sZ9GlaBK.d.cts → context-DSA9gFXB.d.cts} +2 -2
  60. package/dist/tsup/{context-B7-84u9K.d.ts → context-wWv-K2K_.d.ts} +2 -2
  61. package/dist/tsup/db/drizzle.cjs +120 -68
  62. package/dist/tsup/db/drizzle.cjs.map +1 -1
  63. package/dist/tsup/db/drizzle.d.cts +8 -3
  64. package/dist/tsup/db/drizzle.d.ts +8 -3
  65. package/dist/tsup/db/drizzle.js +120 -68
  66. package/dist/tsup/db/drizzle.js.map +1 -1
  67. package/dist/tsup/db/mod.cjs +74 -22
  68. package/dist/tsup/db/mod.cjs.map +1 -1
  69. package/dist/tsup/db/mod.d.cts +4 -3
  70. package/dist/tsup/db/mod.d.ts +4 -3
  71. package/dist/tsup/db/mod.js +74 -22
  72. package/dist/tsup/db/mod.js.map +1 -1
  73. package/dist/tsup/dynamic/mod.cjs +2 -2
  74. package/dist/tsup/dynamic/mod.d.cts +3 -3
  75. package/dist/tsup/dynamic/mod.d.ts +3 -3
  76. package/dist/tsup/dynamic/mod.js +1 -1
  77. package/dist/tsup/inspector/mod.cjs +4 -5
  78. package/dist/tsup/inspector/mod.cjs.map +1 -1
  79. package/dist/tsup/inspector/mod.js +3 -4
  80. package/dist/tsup/inspector/mod.js.map +1 -1
  81. package/dist/tsup/inspector-tab/mod.cjs +2 -2
  82. package/dist/tsup/inspector-tab/mod.cjs.map +1 -1
  83. package/dist/tsup/inspector-tab/mod.d.cts +5 -5
  84. package/dist/tsup/inspector-tab/mod.d.ts +5 -5
  85. package/dist/tsup/inspector-tab/mod.js +1 -1
  86. package/dist/tsup/inspector-tab/mod.js.map +1 -1
  87. package/dist/tsup/mod.cjs +605 -158
  88. package/dist/tsup/mod.cjs.map +1 -1
  89. package/dist/tsup/mod.d.cts +5 -5
  90. package/dist/tsup/mod.d.ts +5 -5
  91. package/dist/tsup/mod.js +528 -81
  92. package/dist/tsup/mod.js.map +1 -1
  93. package/dist/tsup/test/mod.cjs +10 -11
  94. package/dist/tsup/test/mod.cjs.map +1 -1
  95. package/dist/tsup/test/mod.d.cts +3 -3
  96. package/dist/tsup/test/mod.d.ts +3 -3
  97. package/dist/tsup/test/mod.js +6 -7
  98. package/dist/tsup/test/mod.js.map +1 -1
  99. package/dist/tsup/utils-CV0akuDD.d.ts +162 -0
  100. package/dist/tsup/utils-DQDCphrl.d.cts +162 -0
  101. package/dist/tsup/utils.cjs +6 -2
  102. package/dist/tsup/utils.cjs.map +1 -1
  103. package/dist/tsup/utils.d.cts +3 -146
  104. package/dist/tsup/utils.d.ts +3 -146
  105. package/dist/tsup/utils.js +5 -1
  106. package/dist/tsup/workflow/mod.cjs +198 -151
  107. package/dist/tsup/workflow/mod.cjs.map +1 -1
  108. package/dist/tsup/workflow/mod.d.cts +5 -5
  109. package/dist/tsup/workflow/mod.d.ts +5 -5
  110. package/dist/tsup/workflow/mod.js +187 -140
  111. package/dist/tsup/workflow/mod.js.map +1 -1
  112. package/package.json +8 -8
  113. package/src/actor/config.test.ts +14 -0
  114. package/src/actor/config.ts +104 -16
  115. package/src/actor/definition.ts +18 -7
  116. package/src/agent-os/actor/index.ts +2 -2
  117. package/src/common/bare/generated/actor-runtime-socket-protocol/v1.ts +848 -0
  118. package/src/common/bare/generated/inspector/v6.ts +1367 -0
  119. package/src/common/database/config.ts +24 -0
  120. package/src/common/database/mod.test.ts +148 -59
  121. package/src/common/database/mod.ts +93 -23
  122. package/src/common/database/native-database.test.ts +9 -0
  123. package/src/common/database/native-database.ts +151 -0
  124. package/src/common/database/shared.ts +15 -0
  125. package/src/common/encoding.ts +28 -21
  126. package/src/db/drizzle.test.ts +157 -0
  127. package/src/db/drizzle.ts +170 -79
  128. package/src/dynamic/isolate-runtime.ts +0 -2
  129. package/src/engine-client/driver.ts +0 -15
  130. package/src/engine-client/mod.ts +0 -41
  131. package/src/inspector/client.browser.ts +69 -8
  132. package/src/inspector-tab/mod.ts +1 -0
  133. package/src/registry/config/index.ts +1 -35
  134. package/src/registry/napi-runtime.ts +204 -16
  135. package/src/registry/native.ts +200 -30
  136. package/src/registry/runtime.ts +146 -3
  137. package/src/registry/wasm-runtime.ts +217 -6
  138. package/src/utils.ts +3 -0
  139. package/src/workflow/context.ts +5 -27
  140. package/src/workflow/driver.test.ts +62 -0
  141. package/src/workflow/driver.ts +231 -113
  142. package/src/workflow/mod.ts +3 -3
  143. package/dist/tsup/chunk-34P567PO.cjs.map +0 -1
  144. package/dist/tsup/chunk-3MHDOUD7.cjs.map +0 -1
  145. package/dist/tsup/chunk-APW73HCH.cjs +0 -382
  146. package/dist/tsup/chunk-APW73HCH.cjs.map +0 -1
  147. package/dist/tsup/chunk-BLDO2FKO.js.map +0 -1
  148. package/dist/tsup/chunk-DHXWD4DF.js +0 -382
  149. package/dist/tsup/chunk-DHXWD4DF.js.map +0 -1
  150. package/dist/tsup/chunk-JZ7TWV65.js.map +0 -1
  151. package/dist/tsup/chunk-PYO2U53V.cjs.map +0 -1
  152. package/dist/tsup/chunk-TYLP3U4Y.cjs.map +0 -1
  153. package/dist/tsup/chunk-VE2X4KMG.cjs.map +0 -1
  154. package/dist/tsup/chunk-XIX5DOZN.js.map +0 -1
  155. package/dist/tsup/chunk-ZA7FLHKH.cjs.map +0 -1
  156. package/src/actor/driver.ts +0 -141
  157. package/src/actor/instance/mod.ts +0 -2484
  158. package/src/drivers/engine/actor-driver.ts +0 -2473
  159. /package/dist/tsup/{chunk-KEKWJECX.js.map → chunk-MGTSVSUC.js.map} +0 -0
  160. /package/dist/tsup/{chunk-DFA7SYSW.js.map → chunk-NIC3QLKL.js.map} +0 -0
  161. /package/dist/tsup/{chunk-NT2NK3VT.js.map → chunk-PAG6G46L.js.map} +0 -0
@@ -8,8 +8,14 @@ import type {
8
8
  import type { RunContext } from "@/actor/config";
9
9
  import type { AnyStaticActorInstance } from "@/actor/definition";
10
10
  import { makeWorkflowKey, workflowStoragePrefix } from "@/actor/keys";
11
+ import type { SqliteDatabase } from "@/common/database/config";
11
12
 
12
13
  const WORKFLOW_STORAGE_PREFIX = workflowStoragePrefix();
14
+ // Keep workflow flushes below depot's 320-dirty-page commit ceiling. The
15
+ // former actor-KV transport allowed 976 KiB batches; SQLite also dirties table
16
+ // and index pages, so it needs the same conservative budget as core imports.
17
+ const WORKFLOW_SQLITE_MAX_BATCH_ROWS = 128;
18
+ const WORKFLOW_SQLITE_MAX_BATCH_BYTES = 512 * 1024;
13
19
 
14
20
  // Mirrors the element shape returned by `queueManager.receive`. The actor
15
21
  // instance is reached through a loose type here, so the call's result is
@@ -22,8 +28,6 @@ interface ReceivedQueueMessage {
22
28
  complete?: (response?: unknown) => Promise<void>;
23
29
  }
24
30
 
25
- // `kvListPrefix` returns key/value tuples, but the loose actor type erases that
26
- // so the tuple destructures need an explicit annotation.
27
31
  type KVEntryTuple = [Uint8Array, Uint8Array];
28
32
 
29
33
  function stripWorkflowKey(prefixed: Uint8Array): Uint8Array {
@@ -41,6 +45,206 @@ function computeUpperBound(prefix: Uint8Array): Uint8Array | null {
41
45
  return null;
42
46
  }
43
47
 
48
+ function normalizeSqlBlob(value: unknown): Uint8Array {
49
+ if (value instanceof Uint8Array) {
50
+ return value;
51
+ }
52
+ if (value instanceof ArrayBuffer) {
53
+ return new Uint8Array(value);
54
+ }
55
+ if (ArrayBuffer.isView(value)) {
56
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
57
+ }
58
+ if (Array.isArray(value)) {
59
+ const bytes = new Uint8Array(value.length);
60
+ for (const [i, item] of value.entries()) {
61
+ if (!Number.isInteger(item) || item < 0 || item > 255) {
62
+ throw new Error("workflow sqlite value was not a byte array");
63
+ }
64
+ bytes[i] = item;
65
+ }
66
+ return bytes;
67
+ }
68
+ throw new Error("workflow sqlite value was not a blob");
69
+ }
70
+
71
+ function runtimeSqlFromContext(
72
+ runCtx?: RunContext<any, any, any, any, any, any, any, any>,
73
+ ): SqliteDatabase | undefined {
74
+ const sql = (runCtx as unknown as { sql?: unknown } | undefined)?.sql;
75
+ if (
76
+ sql &&
77
+ typeof sql === "object" &&
78
+ "query" in sql &&
79
+ "execute" in sql &&
80
+ "executeBatch" in sql &&
81
+ "run" in sql
82
+ ) {
83
+ return sql as SqliteDatabase;
84
+ }
85
+ return undefined;
86
+ }
87
+
88
+ type WorkflowSqliteDatabase = SqliteDatabase &
89
+ Required<Pick<SqliteDatabase, "executeBatch">>;
90
+
91
+ class WorkflowStorage {
92
+ #sql: WorkflowSqliteDatabase;
93
+
94
+ constructor(sql?: SqliteDatabase) {
95
+ if (!sql) {
96
+ throw new Error(
97
+ "workflow storage requires embedded SQLite; actor KV workflow storage is no longer supported",
98
+ );
99
+ }
100
+ if (!sql.executeBatch) {
101
+ throw new Error(
102
+ "workflow storage requires a SQLite database with executeBatch support",
103
+ );
104
+ }
105
+ this.#sql = sql as WorkflowSqliteDatabase;
106
+ }
107
+
108
+ async get(key: Uint8Array): Promise<Uint8Array | null> {
109
+ const prefixed = makeWorkflowKey(key);
110
+ const result = await this.#sql.query(
111
+ "SELECT value FROM _rivet_wf_kv WHERE key = ?",
112
+ [prefixed],
113
+ );
114
+ return result.rows[0]?.[0] == null
115
+ ? null
116
+ : normalizeSqlBlob(result.rows[0][0]);
117
+ }
118
+
119
+ async set(key: Uint8Array, value: Uint8Array): Promise<void> {
120
+ await this.batch([{ key, value }]);
121
+ }
122
+
123
+ async delete(key: Uint8Array): Promise<void> {
124
+ const prefixed = makeWorkflowKey(key);
125
+ await this.#sql.run("DELETE FROM _rivet_wf_kv WHERE key = ?", [
126
+ prefixed,
127
+ ]);
128
+ }
129
+
130
+ async deletePrefix(prefix: Uint8Array): Promise<void> {
131
+ const start = makeWorkflowKey(prefix);
132
+ const end = computeUpperBound(start);
133
+ if (end) {
134
+ await this.#sql.run(
135
+ "DELETE FROM _rivet_wf_kv WHERE key >= ? AND key < ?",
136
+ [start, end],
137
+ );
138
+ return;
139
+ }
140
+ const entries = await this.listRaw(start);
141
+ await this.deleteRawKeys(
142
+ entries
143
+ .filter(([key]) => keyStartsWith(key, start))
144
+ .map(([key]) => key),
145
+ );
146
+ }
147
+
148
+ async deleteRange(start: Uint8Array, end: Uint8Array): Promise<void> {
149
+ const prefixedStart = makeWorkflowKey(start);
150
+ const prefixedEnd = makeWorkflowKey(end);
151
+ await this.#sql.run(
152
+ "DELETE FROM _rivet_wf_kv WHERE key >= ? AND key < ?",
153
+ [prefixedStart, prefixedEnd],
154
+ );
155
+ }
156
+
157
+ async list(prefix: Uint8Array): Promise<KVEntry[]> {
158
+ const prefixed = makeWorkflowKey(prefix);
159
+ const entries = await this.listRaw(prefixed);
160
+ return entries
161
+ .filter(([key]) => keyStartsWith(key, prefixed))
162
+ .map(([key, value]: KVEntryTuple) => ({
163
+ key: stripWorkflowKey(key),
164
+ value,
165
+ }));
166
+ }
167
+
168
+ async batch(writes: KVWrite[]): Promise<void> {
169
+ if (writes.length === 0) return;
170
+ for (const chunk of chunkWorkflowWrites(writes)) {
171
+ await this.#sql.executeBatch(
172
+ chunk.map(({ key, value }) => ({
173
+ sql: "INSERT INTO _rivet_wf_kv (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value",
174
+ params: [makeWorkflowKey(key), value],
175
+ })),
176
+ );
177
+ }
178
+ }
179
+
180
+ async listRaw(prefix: Uint8Array): Promise<KVEntryTuple[]> {
181
+ const end = computeUpperBound(prefix);
182
+ const result = end
183
+ ? await this.#sql.query(
184
+ "SELECT key, value FROM _rivet_wf_kv WHERE key >= ? AND key < ? ORDER BY key ASC",
185
+ [prefix, end],
186
+ )
187
+ : await this.#sql.query(
188
+ "SELECT key, value FROM _rivet_wf_kv WHERE key >= ? ORDER BY key ASC",
189
+ [prefix],
190
+ );
191
+ return result.rows.map((row) => [
192
+ normalizeSqlBlob(row[0]),
193
+ normalizeSqlBlob(row[1]),
194
+ ]);
195
+ }
196
+
197
+ async deleteRawKeys(keys: Uint8Array[]): Promise<void> {
198
+ for (
199
+ let start = 0;
200
+ start < keys.length;
201
+ start += WORKFLOW_SQLITE_MAX_BATCH_ROWS
202
+ ) {
203
+ await this.#sql.executeBatch(
204
+ keys
205
+ .slice(start, start + WORKFLOW_SQLITE_MAX_BATCH_ROWS)
206
+ .map((key) => ({
207
+ sql: "DELETE FROM _rivet_wf_kv WHERE key = ?",
208
+ params: [key],
209
+ })),
210
+ );
211
+ }
212
+ }
213
+ }
214
+
215
+ export function chunkWorkflowWrites(writes: KVWrite[]): KVWrite[][] {
216
+ const chunks: KVWrite[][] = [];
217
+ let chunk: KVWrite[] = [];
218
+ let chunkBytes = 0;
219
+ for (const write of writes) {
220
+ const writeBytes =
221
+ WORKFLOW_STORAGE_PREFIX.length +
222
+ write.key.length +
223
+ write.value.length;
224
+ if (
225
+ chunk.length > 0 &&
226
+ (chunk.length >= WORKFLOW_SQLITE_MAX_BATCH_ROWS ||
227
+ chunkBytes + writeBytes > WORKFLOW_SQLITE_MAX_BATCH_BYTES)
228
+ ) {
229
+ chunks.push(chunk);
230
+ chunk = [];
231
+ chunkBytes = 0;
232
+ }
233
+ chunk.push(write);
234
+ chunkBytes += writeBytes;
235
+ }
236
+ if (chunk.length > 0) chunks.push(chunk);
237
+ return chunks;
238
+ }
239
+
240
+ function keyStartsWith(key: Uint8Array, prefix: Uint8Array): boolean {
241
+ if (key.length < prefix.length) return false;
242
+ for (let i = 0; i < prefix.length; i++) {
243
+ if (key[i] !== prefix[i]) return false;
244
+ }
245
+ return true;
246
+ }
247
+
44
248
  class ActorWorkflowMessageDriver implements WorkflowMessageDriver {
45
249
  #actor: AnyStaticActorInstance;
46
250
  #runCtx: RunContext<any, any, any, any, any, any, any, any>;
@@ -113,10 +317,12 @@ class ActorWorkflowMessageDriver implements WorkflowMessageDriver {
113
317
  }
114
318
 
115
319
  export class ActorWorkflowDriver implements EngineDriver {
320
+ readonly atomicBatch = true;
116
321
  readonly workerPollInterval = 100;
117
322
  readonly messageDriver: WorkflowMessageDriver;
118
323
  #actor: AnyStaticActorInstance;
119
324
  #runCtx: RunContext<any, any, any, any, any, any, any, any>;
325
+ #storage: WorkflowStorage;
120
326
 
121
327
  constructor(
122
328
  actor: AnyStaticActorInstance,
@@ -125,97 +331,42 @@ export class ActorWorkflowDriver implements EngineDriver {
125
331
  this.#actor = actor;
126
332
  this.#runCtx = runCtx;
127
333
  this.messageDriver = new ActorWorkflowMessageDriver(actor, runCtx);
334
+ this.#storage = new WorkflowStorage(runtimeSqlFromContext(runCtx));
128
335
  }
129
336
 
130
337
  async get(key: Uint8Array): Promise<Uint8Array | null> {
131
- const [value] = await this.#runCtx.internalKeepAwake(
132
- this.#actor.driver.kvBatchGet(this.#actor.id, [
133
- makeWorkflowKey(key),
134
- ]),
135
- );
136
- return value ?? null;
338
+ return await this.#runCtx.internalKeepAwake(this.#storage.get(key));
137
339
  }
138
340
 
139
341
  async set(key: Uint8Array, value: Uint8Array): Promise<void> {
140
- await this.#runCtx.internalKeepAwake(
141
- this.#actor.driver.kvBatchPut(this.#actor.id, [
142
- [makeWorkflowKey(key), value],
143
- ]),
144
- );
342
+ await this.#runCtx.internalKeepAwake(this.#storage.set(key, value));
145
343
  }
146
344
 
147
345
  async delete(key: Uint8Array): Promise<void> {
148
- await this.#runCtx.internalKeepAwake(
149
- this.#actor.driver.kvBatchDelete(this.#actor.id, [
150
- makeWorkflowKey(key),
151
- ]),
152
- );
346
+ await this.#runCtx.internalKeepAwake(this.#storage.delete(key));
153
347
  }
154
348
 
155
349
  async deletePrefix(prefix: Uint8Array): Promise<void> {
156
- const start = makeWorkflowKey(prefix);
157
- const end = computeUpperBound(start);
158
- if (end) {
159
- await this.#runCtx.internalKeepAwake(
160
- this.#actor.driver.kvDeleteRange(this.#actor.id, start, end),
161
- );
162
- } else {
163
- const entries = await this.#runCtx.internalKeepAwake(
164
- this.#actor.driver.kvListPrefix(this.#actor.id, start),
165
- );
166
- if (entries.length === 0) {
167
- return;
168
- }
169
- await this.#runCtx.internalKeepAwake(
170
- this.#actor.driver.kvBatchDelete(
171
- this.#actor.id,
172
- entries.map(([key]: KVEntryTuple) => key),
173
- ),
174
- );
175
- }
350
+ await this.#runCtx.internalKeepAwake(
351
+ this.#storage.deletePrefix(prefix),
352
+ );
176
353
  }
177
354
 
178
355
  async deleteRange(start: Uint8Array, end: Uint8Array): Promise<void> {
179
356
  await this.#runCtx.internalKeepAwake(
180
- this.#actor.driver.kvDeleteRange(
181
- this.#actor.id,
182
- makeWorkflowKey(start),
183
- makeWorkflowKey(end),
184
- ),
357
+ this.#storage.deleteRange(start, end),
185
358
  );
186
359
  }
187
360
 
188
361
  async list(prefix: Uint8Array): Promise<KVEntry[]> {
189
- const entries = await this.#runCtx.internalKeepAwake(
190
- this.#actor.driver.kvListPrefix(
191
- this.#actor.id,
192
- makeWorkflowKey(prefix),
193
- ),
194
- );
195
- return entries.map(([key, value]: KVEntryTuple) => ({
196
- key: stripWorkflowKey(key),
197
- value,
198
- }));
362
+ return await this.#runCtx.internalKeepAwake(this.#storage.list(prefix));
199
363
  }
200
364
 
201
365
  async batch(writes: KVWrite[]): Promise<void> {
202
366
  if (writes.length === 0) return;
203
367
 
204
- // Flush actor state together with workflow state to ensure atomicity.
205
- // If the server crashes after workflow flush, actor state must also be persisted.
206
368
  await this.#runCtx.internalKeepAwake(
207
- Promise.all([
208
- this.#actor.driver.kvBatchPut(
209
- this.#actor.id,
210
- writes.map(({ key, value }) => [
211
- makeWorkflowKey(key),
212
- value,
213
- ]),
214
- ),
215
- this.#actor.stateManager.saveState({
216
- immediate: true,
217
- }),
218
- ]),
369
+ this.#actor.stateManager.saveStateAndWorkflowBatch(writes),
219
370
  );
220
371
  }
221
372
 
@@ -267,68 +418,38 @@ export class ActorWorkflowControlDriver implements EngineDriver {
267
418
  readonly messageDriver: WorkflowMessageDriver =
268
419
  new NoopWorkflowMessageDriver();
269
420
  #actor: AnyStaticActorInstance;
421
+ #storage: WorkflowStorage;
270
422
 
271
- constructor(actor: AnyStaticActorInstance) {
423
+ constructor(
424
+ actor: AnyStaticActorInstance,
425
+ runCtx?: RunContext<any, any, any, any, any, any, any, any>,
426
+ ) {
272
427
  this.#actor = actor;
428
+ this.#storage = new WorkflowStorage(runtimeSqlFromContext(runCtx));
273
429
  }
274
430
 
275
431
  async get(key: Uint8Array): Promise<Uint8Array | null> {
276
- const [value] = await this.#actor.driver.kvBatchGet(this.#actor.id, [
277
- makeWorkflowKey(key),
278
- ]);
279
- return value ?? null;
432
+ return await this.#storage.get(key);
280
433
  }
281
434
 
282
435
  async set(key: Uint8Array, value: Uint8Array): Promise<void> {
283
- await this.#actor.driver.kvBatchPut(this.#actor.id, [
284
- [makeWorkflowKey(key), value],
285
- ]);
436
+ await this.#storage.set(key, value);
286
437
  }
287
438
 
288
439
  async delete(key: Uint8Array): Promise<void> {
289
- await this.#actor.driver.kvBatchDelete(this.#actor.id, [
290
- makeWorkflowKey(key),
291
- ]);
440
+ await this.#storage.delete(key);
292
441
  }
293
442
 
294
443
  async deletePrefix(prefix: Uint8Array): Promise<void> {
295
- const start = makeWorkflowKey(prefix);
296
- const end = computeUpperBound(start);
297
- if (end) {
298
- await this.#actor.driver.kvDeleteRange(this.#actor.id, start, end);
299
- return;
300
- }
301
-
302
- const entries = await this.#actor.driver.kvListPrefix(
303
- this.#actor.id,
304
- start,
305
- );
306
- if (entries.length === 0) {
307
- return;
308
- }
309
- await this.#actor.driver.kvBatchDelete(
310
- this.#actor.id,
311
- entries.map(([key]: KVEntryTuple) => key),
312
- );
444
+ await this.#storage.deletePrefix(prefix);
313
445
  }
314
446
 
315
447
  async deleteRange(start: Uint8Array, end: Uint8Array): Promise<void> {
316
- await this.#actor.driver.kvDeleteRange(
317
- this.#actor.id,
318
- makeWorkflowKey(start),
319
- makeWorkflowKey(end),
320
- );
448
+ await this.#storage.deleteRange(start, end);
321
449
  }
322
450
 
323
451
  async list(prefix: Uint8Array): Promise<KVEntry[]> {
324
- const entries = await this.#actor.driver.kvListPrefix(
325
- this.#actor.id,
326
- makeWorkflowKey(prefix),
327
- );
328
- return entries.map(([key, value]: KVEntryTuple) => ({
329
- key: stripWorkflowKey(key),
330
- value,
331
- }));
452
+ return await this.#storage.list(prefix);
332
453
  }
333
454
 
334
455
  async batch(writes: KVWrite[]): Promise<void> {
@@ -336,10 +457,7 @@ export class ActorWorkflowControlDriver implements EngineDriver {
336
457
  return;
337
458
  }
338
459
 
339
- await this.#actor.driver.kvBatchPut(
340
- this.#actor.id,
341
- writes.map(({ key, value }) => [makeWorkflowKey(key), value]),
342
- );
460
+ await this.#storage.batch(writes);
343
461
  }
344
462
 
345
463
  async setAlarm(_workflowId: string, wakeAt: number): Promise<void> {
@@ -6,8 +6,8 @@ import {
6
6
  RaceError,
7
7
  RollbackCheckpointError,
8
8
  RollbackError,
9
- replayWorkflowFromStep,
10
9
  type RunWorkflowOptions,
10
+ replayWorkflowFromStep,
11
11
  runWorkflow,
12
12
  StepExhaustedError,
13
13
  type WorkflowErrorEvent,
@@ -201,7 +201,7 @@ export function workflow<
201
201
  const workflowInspector = getWorkflowInspector(actor.id);
202
202
 
203
203
  const driver = new ActorWorkflowDriver(actor, runCtx);
204
- const controlDriver = new ActorWorkflowControlDriver(actor);
204
+ const controlDriver = new ActorWorkflowControlDriver(actor, runCtx);
205
205
  workflowInspector.setReplayFromStep(async (entryId) => {
206
206
  const workflowState = await workflowInspector.adapter.getState();
207
207
  if (
@@ -212,7 +212,7 @@ export function workflow<
212
212
  throw workflowReplayInFlightError();
213
213
  }
214
214
 
215
- let snapshot;
215
+ let snapshot: Awaited<ReturnType<typeof replayWorkflowFromStep>>;
216
216
  try {
217
217
  snapshot = await replayWorkflowFromStep(
218
218
  actor.id,