rivetkit 2.3.11-rc.8 → 2.3.11

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 (153) hide show
  1. package/dist/browser/client.d.ts +96 -14
  2. package/dist/browser/client.js +12 -2
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.d.ts +139 -139
  5. package/dist/browser/inspector/client.js +12 -2
  6. package/dist/browser/inspector/client.js.map +1 -1
  7. package/dist/tsup/agent-os/index.cjs +146 -76
  8. package/dist/tsup/agent-os/index.cjs.map +1 -1
  9. package/dist/tsup/agent-os/index.d.cts +83 -9
  10. package/dist/tsup/agent-os/index.d.ts +83 -9
  11. package/dist/tsup/agent-os/index.js +146 -76
  12. package/dist/tsup/agent-os/index.js.map +1 -1
  13. package/dist/tsup/{chunk-JBQ63WSM.js → chunk-3ZR542SK.js} +4 -4
  14. package/dist/tsup/chunk-3ZR542SK.js.map +1 -0
  15. package/dist/tsup/{chunk-HNHLBM3Y.cjs → chunk-4KGXFC6M.cjs} +42 -5
  16. package/dist/tsup/chunk-4KGXFC6M.cjs.map +1 -0
  17. package/dist/tsup/chunk-5UYWLCRO.js +14 -0
  18. package/dist/tsup/chunk-5UYWLCRO.js.map +1 -0
  19. package/dist/tsup/{chunk-QWLJCP3X.js → chunk-6W5VGLFT.js} +42 -5
  20. package/dist/tsup/chunk-6W5VGLFT.js.map +1 -0
  21. package/dist/tsup/chunk-6WYATRS4.js +243 -0
  22. package/dist/tsup/chunk-6WYATRS4.js.map +1 -0
  23. package/dist/tsup/chunk-B3W74FBB.cjs +14 -0
  24. package/dist/tsup/chunk-B3W74FBB.cjs.map +1 -0
  25. package/dist/tsup/{chunk-V6ANE6JM.js → chunk-FWROC5LA.js} +2 -2
  26. package/dist/tsup/{chunk-ZVM35ICH.cjs → chunk-GLIPKKEC.cjs} +7 -7
  27. package/dist/tsup/{chunk-ZVM35ICH.cjs.map → chunk-GLIPKKEC.cjs.map} +1 -1
  28. package/dist/tsup/{chunk-CCUO5OKD.cjs → chunk-GN2EUMEW.cjs} +3 -3
  29. package/dist/tsup/{chunk-CCUO5OKD.cjs.map → chunk-GN2EUMEW.cjs.map} +1 -1
  30. package/dist/tsup/{chunk-2ROUKH7W.cjs → chunk-GU7ML3KE.cjs} +13 -3
  31. package/dist/tsup/chunk-GU7ML3KE.cjs.map +1 -0
  32. package/dist/tsup/{chunk-WRRB24UY.js → chunk-JQ2AZ5FN.js} +3 -3
  33. package/dist/tsup/{chunk-7AFZMFPQ.js → chunk-JTHHCZCZ.js} +10 -1
  34. package/dist/tsup/chunk-JTHHCZCZ.js.map +1 -0
  35. package/dist/tsup/{chunk-UEULFXUI.cjs → chunk-KVFIONKQ.cjs} +11 -2
  36. package/dist/tsup/chunk-KVFIONKQ.cjs.map +1 -0
  37. package/dist/tsup/chunk-LWQFXGKS.cjs +243 -0
  38. package/dist/tsup/chunk-LWQFXGKS.cjs.map +1 -0
  39. package/dist/tsup/{chunk-KVD6ZWNM.js → chunk-MO2SSUOR.js} +8 -7
  40. package/dist/tsup/chunk-MO2SSUOR.js.map +1 -0
  41. package/dist/tsup/{chunk-AGNBLCNJ.js → chunk-OUENKQHB.js} +13 -3
  42. package/dist/tsup/chunk-OUENKQHB.js.map +1 -0
  43. package/dist/tsup/{chunk-E6WB2BLO.cjs → chunk-PUZY2XCD.cjs} +10 -9
  44. package/dist/tsup/chunk-PUZY2XCD.cjs.map +1 -0
  45. package/dist/tsup/{chunk-4EDGOGCB.js → chunk-QFJ24MBU.js} +27 -2
  46. package/dist/tsup/{chunk-4EDGOGCB.js.map → chunk-QFJ24MBU.js.map} +1 -1
  47. package/dist/tsup/{chunk-A55CGZIV.cjs → chunk-RXXDNJXB.cjs} +140 -140
  48. package/dist/tsup/{chunk-A55CGZIV.cjs.map → chunk-RXXDNJXB.cjs.map} +1 -1
  49. package/dist/tsup/{chunk-N6MBTYGX.cjs → chunk-VBJQIBXI.cjs} +30 -5
  50. package/dist/tsup/chunk-VBJQIBXI.cjs.map +1 -0
  51. package/dist/tsup/client/mod.cjs +6 -6
  52. package/dist/tsup/client/mod.d.cts +4 -3
  53. package/dist/tsup/client/mod.d.ts +4 -3
  54. package/dist/tsup/client/mod.js +5 -5
  55. package/dist/tsup/common/log.cjs +2 -2
  56. package/dist/tsup/common/log.js +1 -1
  57. package/dist/tsup/common/websocket.cjs +3 -3
  58. package/dist/tsup/common/websocket.js +2 -2
  59. package/dist/tsup/{config-CZcjyT5B.d.ts → config-C4OSv0QP.d.ts} +92 -7
  60. package/dist/tsup/{config-rGWrumlO.d.cts → config-DTGoWdkG.d.cts} +92 -7
  61. package/dist/tsup/{config-BxOimoGX.d.cts → config-ey2I2zz5.d.cts} +46 -6
  62. package/dist/tsup/{config-BxOimoGX.d.ts → config-ey2I2zz5.d.ts} +46 -6
  63. package/dist/tsup/{context-B8W8E8nW.d.cts → context-C6cvDNyk.d.cts} +2 -2
  64. package/dist/tsup/{context-_UrDZhEh.d.ts → context-CMygeBxS.d.ts} +2 -2
  65. package/dist/tsup/db/drizzle.cjs +18 -10
  66. package/dist/tsup/db/drizzle.cjs.map +1 -1
  67. package/dist/tsup/db/drizzle.d.cts +5 -6
  68. package/dist/tsup/db/drizzle.d.ts +5 -6
  69. package/dist/tsup/db/drizzle.js +13 -5
  70. package/dist/tsup/db/drizzle.js.map +1 -1
  71. package/dist/tsup/db/mod.cjs +5 -175
  72. package/dist/tsup/db/mod.cjs.map +1 -1
  73. package/dist/tsup/db/mod.d.cts +11 -4
  74. package/dist/tsup/db/mod.d.ts +11 -4
  75. package/dist/tsup/db/mod.js +4 -174
  76. package/dist/tsup/db/mod.js.map +1 -1
  77. package/dist/tsup/dynamic/mod.cjs +2 -2
  78. package/dist/tsup/dynamic/mod.d.cts +3 -2
  79. package/dist/tsup/dynamic/mod.d.ts +3 -2
  80. package/dist/tsup/dynamic/mod.js +1 -1
  81. package/dist/tsup/inspector/mod.cjs +5 -4
  82. package/dist/tsup/inspector/mod.cjs.map +1 -1
  83. package/dist/tsup/inspector/mod.d.cts +12 -146
  84. package/dist/tsup/inspector/mod.d.ts +12 -146
  85. package/dist/tsup/inspector/mod.js +4 -3
  86. package/dist/tsup/inspector/mod.js.map +1 -1
  87. package/dist/tsup/inspector/workflow.cjs +21 -0
  88. package/dist/tsup/inspector/workflow.cjs.map +1 -0
  89. package/dist/tsup/inspector/workflow.d.cts +144 -0
  90. package/dist/tsup/inspector/workflow.d.ts +144 -0
  91. package/dist/tsup/inspector/workflow.js +21 -0
  92. package/dist/tsup/inspector/workflow.js.map +1 -0
  93. package/dist/tsup/inspector-tab/mod.cjs +2 -2
  94. package/dist/tsup/inspector-tab/mod.d.cts +4 -3
  95. package/dist/tsup/inspector-tab/mod.d.ts +4 -3
  96. package/dist/tsup/inspector-tab/mod.js +1 -1
  97. package/dist/tsup/mod.cjs +837 -243
  98. package/dist/tsup/mod.cjs.map +1 -1
  99. package/dist/tsup/mod.d.cts +6 -5
  100. package/dist/tsup/mod.d.ts +6 -5
  101. package/dist/tsup/mod.js +766 -172
  102. package/dist/tsup/mod.js.map +1 -1
  103. package/dist/tsup/test/mod.cjs +7 -7
  104. package/dist/tsup/test/mod.d.cts +3 -2
  105. package/dist/tsup/test/mod.d.ts +3 -2
  106. package/dist/tsup/test/mod.js +5 -5
  107. package/dist/tsup/unstable/migrations.d.cts +1 -1
  108. package/dist/tsup/unstable/migrations.d.ts +1 -1
  109. package/dist/tsup/utils.cjs +2 -2
  110. package/dist/tsup/utils.js +1 -1
  111. package/dist/tsup/workflow/mod.cjs +89 -95
  112. package/dist/tsup/workflow/mod.cjs.map +1 -1
  113. package/dist/tsup/workflow/mod.d.cts +5 -4
  114. package/dist/tsup/workflow/mod.d.ts +5 -4
  115. package/dist/tsup/workflow/mod.js +86 -92
  116. package/dist/tsup/workflow/mod.js.map +1 -1
  117. package/package.json +19 -9
  118. package/src/actor/config.test.ts +65 -1
  119. package/src/actor/config.ts +169 -5
  120. package/src/actor/mod.ts +10 -1
  121. package/src/client/actor-common.ts +5 -14
  122. package/src/common/database/config.ts +50 -3
  123. package/src/common/database/mod.test.ts +142 -4
  124. package/src/common/database/mod.ts +210 -98
  125. package/src/common/database/native-database.ts +42 -3
  126. package/src/common/database/shared.ts +9 -0
  127. package/src/db/drizzle.test.ts +8 -1
  128. package/src/db/drizzle.ts +15 -4
  129. package/src/db/mod.ts +3 -0
  130. package/src/inspector/workflow.ts +61 -0
  131. package/src/registry/napi-runtime.ts +69 -1
  132. package/src/registry/native.ts +505 -155
  133. package/src/registry/run-handler-coordinator.test.ts +252 -0
  134. package/src/registry/run-handler-coordinator.ts +219 -0
  135. package/src/registry/runtime.ts +34 -1
  136. package/src/registry/wasm-runtime.ts +122 -17
  137. package/src/registry/write-through-proxy.ts +101 -0
  138. package/src/utils/node.ts +44 -28
  139. package/src/workflow/driver.ts +56 -58
  140. package/src/workflow/inspector.ts +10 -15
  141. package/src/workflow/mod.ts +48 -70
  142. package/dist/tsup/chunk-2ROUKH7W.cjs.map +0 -1
  143. package/dist/tsup/chunk-7AFZMFPQ.js.map +0 -1
  144. package/dist/tsup/chunk-AGNBLCNJ.js.map +0 -1
  145. package/dist/tsup/chunk-E6WB2BLO.cjs.map +0 -1
  146. package/dist/tsup/chunk-HNHLBM3Y.cjs.map +0 -1
  147. package/dist/tsup/chunk-JBQ63WSM.js.map +0 -1
  148. package/dist/tsup/chunk-KVD6ZWNM.js.map +0 -1
  149. package/dist/tsup/chunk-N6MBTYGX.cjs.map +0 -1
  150. package/dist/tsup/chunk-QWLJCP3X.js.map +0 -1
  151. package/dist/tsup/chunk-UEULFXUI.cjs.map +0 -1
  152. /package/dist/tsup/{chunk-V6ANE6JM.js.map → chunk-FWROC5LA.js.map} +0 -0
  153. /package/dist/tsup/{chunk-WRRB24UY.js.map → chunk-JQ2AZ5FN.js.map} +0 -0
@@ -15,6 +15,8 @@ import {
15
15
  disposeRunInspector,
16
16
  getRunFunction,
17
17
  getRunInspectorConfig,
18
+ getRunInspectorKind,
19
+ hasRunInspectorConfig,
18
20
  RAW_STATE_SYMBOL,
19
21
  type ScheduledEventInfo,
20
22
  type WorkflowInspectorConfig,
@@ -44,7 +46,15 @@ import {
44
46
  } from "@/client/client";
45
47
  import { convertRegistryConfigToClientConfig } from "@/client/config";
46
48
  import { HEADER_CONN_PARAMS } from "@/common/actor-router-consts";
47
- import type { AnyDatabaseProvider } from "@/common/database/config";
49
+ import type {
50
+ AnyDatabaseProvider,
51
+ SqliteProfilingOptions,
52
+ } from "@/common/database/config";
53
+ import {
54
+ bindNativeStateTransactionContext,
55
+ db as defaultDb,
56
+ registerNativeStateTransactionOpener,
57
+ } from "@/common/database/mod";
48
58
  import { wrapJsNativeDatabase } from "@/common/database/native-database";
49
59
  import { assertJsonCompatValue, type JsonCompatValue } from "@/common/encoding";
50
60
  import { isResponseLike, type ResponseLike } from "@/common/fetch-like";
@@ -83,6 +93,7 @@ import {
83
93
  validateQueueBody,
84
94
  validateQueueComplete,
85
95
  } from "./native-validation";
96
+ import { RunHandlerCoordinator } from "./run-handler-coordinator";
86
97
  import type {
87
98
  ActorContextHandle,
88
99
  ActorFactoryHandle,
@@ -104,7 +115,10 @@ import type {
104
115
  WebSocketHandle,
105
116
  } from "./runtime";
106
117
  import { loadWasmRuntime } from "./wasm-runtime";
107
- import { createWriteThroughProxy } from "./write-through-proxy";
118
+ import {
119
+ createWriteThroughProxy,
120
+ unwrapWriteThroughProxy,
121
+ } from "./write-through-proxy";
108
122
 
109
123
  const textEncoder = new TextEncoder();
110
124
  const textDecoder = new TextDecoder();
@@ -128,6 +142,14 @@ type NativePersistConnState = {
128
142
  state: unknown;
129
143
  };
130
144
 
145
+ type NativeStateTransactionScope = {
146
+ actorContext: ActorContextHandleAdapter;
147
+ actorStateBaseline: unknown;
148
+ connectionStateBaselines: Map<string, Uint8Array>;
149
+ committed: boolean;
150
+ release: () => void;
151
+ };
152
+
131
153
  const defaultRuntimeLoaders: RuntimeLoaders = {
132
154
  loadNative: loadNapiRuntime,
133
155
  loadWasm: loadWasmRuntime,
@@ -271,14 +293,14 @@ type NativePersistActorState = {
271
293
  state: unknown;
272
294
  isInOnStateChange: boolean;
273
295
  connStates: Map<string, NativePersistConnState>;
274
- // Memoized deep write-through proxy and the state object it wraps. Rebuilt
275
- // only when the underlying state object identity changes.
276
- stateProxy?: unknown;
277
- stateProxyTarget?: unknown;
278
296
  // Set when a coalesced save and onStateChange flush is pending for the
279
297
  // current event loop tick.
280
298
  saveScheduled?: boolean;
281
299
  pendingSaveHandle?: ReturnType<typeof setImmediate>;
300
+ activeStateTransactionOwner?: symbol;
301
+ pendingStateTransactionOwners?: Set<symbol>;
302
+ stateTransactionTail?: Promise<void>;
303
+ stateTransactionSaveDeferred?: boolean;
282
304
  };
283
305
  type NativeDestroyGate = {
284
306
  destroyCompletion?: Promise<void>;
@@ -364,6 +386,15 @@ function stateMutationReentrantError(): RivetError {
364
386
  );
365
387
  }
366
388
 
389
+ function stateTransactionConflictError(): RivetError {
390
+ return new RivetError(
391
+ "actor",
392
+ "state_transaction_conflict",
393
+ "State cannot be mutated by another action while an experimental state-aware database transaction is active.",
394
+ { public: true, statusCode: 409 },
395
+ );
396
+ }
397
+
367
398
  function databaseNotConfiguredError(): RivetError {
368
399
  return new RivetError(
369
400
  "actor",
@@ -381,6 +412,14 @@ function databaseClientNotReadyError(): RivetError {
381
412
  { public: true },
382
413
  );
383
414
  }
415
+ function runHandlerNotConfiguredError(): RivetError {
416
+ return new RivetError(
417
+ "actor",
418
+ "run_handler_unavailable",
419
+ "Cannot schedule a run wake because this actor does not define a run handler.",
420
+ { public: true, statusCode: 409 },
421
+ );
422
+ }
384
423
 
385
424
  function stateNotEnabledError(): RivetError {
386
425
  return new RivetError(
@@ -533,10 +572,11 @@ function getOrCreateNativeSqlDatabase(
533
572
  execute: (sql, params) => runtime.actorSqlExecute(ctx, sql, params),
534
573
  executeBatch: (statements) =>
535
574
  runtime.actorSqlExecuteBatch(ctx, statements),
536
- beginTransaction: async (timeoutMs) => {
575
+ beginTransaction: async (timeoutMs, name) => {
537
576
  const transaction = await runtime.actorSqlBeginTransaction(
538
577
  ctx,
539
578
  timeoutMs,
579
+ name,
540
580
  );
541
581
  return {
542
582
  exec: (sql) =>
@@ -552,6 +592,40 @@ function getOrCreateNativeSqlDatabase(
552
592
  runtime.actorSqlTransactionRollback(transaction),
553
593
  };
554
594
  },
595
+ beginStateTransaction: async (timeoutMs, context) => {
596
+ const scope = context as NativeStateTransactionScope | undefined;
597
+ if (!scope) {
598
+ throw new Error(
599
+ "actor state transaction context is not configured",
600
+ );
601
+ }
602
+ const transaction = await runtime.actorBeginStateTransaction(
603
+ ctx,
604
+ timeoutMs,
605
+ );
606
+ return {
607
+ exec: async () => {
608
+ throw new Error(
609
+ "actor state transactions only support single-statement execute calls",
610
+ );
611
+ },
612
+ execute: (sql, params) =>
613
+ runtime.actorStateTransactionExecute(
614
+ transaction,
615
+ sql,
616
+ params,
617
+ ),
618
+ commit: async () => {
619
+ await runtime.actorStateTransactionCommit(
620
+ transaction,
621
+ scope.actorContext.serializeForTick("save"),
622
+ );
623
+ scope.committed = true;
624
+ },
625
+ rollback: () =>
626
+ runtime.actorStateTransactionRollback(transaction),
627
+ };
628
+ },
555
629
  query: (sql, params) => runtime.actorSqlQuery(ctx, sql, params),
556
630
  run: (sql, params) => runtime.actorSqlRun(ctx, sql, params),
557
631
  metrics: () => runtime.actorSqlMetrics(ctx),
@@ -1227,6 +1301,9 @@ class NativeConnAdapter {
1227
1301
  #schemas: NativeValidationConfig;
1228
1302
  #ctx?: ActorContextHandle;
1229
1303
  #queueHibernationRemoval?: (connId: string) => void;
1304
+ #assertCanMutateState?: () => void;
1305
+ #stateProxy?: unknown;
1306
+ #stateProxyTarget?: unknown;
1230
1307
 
1231
1308
  constructor(
1232
1309
  runtime: CoreRuntime,
@@ -1234,12 +1311,14 @@ class NativeConnAdapter {
1234
1311
  schemas: NativeValidationConfig = {},
1235
1312
  ctx?: ActorContextHandle,
1236
1313
  queueHibernationRemoval?: (connId: string) => void,
1314
+ assertCanMutateState?: () => void,
1237
1315
  ) {
1238
1316
  this.#runtime = runtime;
1239
1317
  this.#conn = conn;
1240
1318
  this.#schemas = schemas;
1241
1319
  this.#ctx = ctx;
1242
1320
  this.#queueHibernationRemoval = queueHibernationRemoval;
1321
+ this.#assertCanMutateState = assertCanMutateState;
1243
1322
  (
1244
1323
  this as NativeConnAdapter & {
1245
1324
  [CONN_STATE_MANAGER_SYMBOL]?: unknown;
@@ -1270,24 +1349,55 @@ class NativeConnAdapter {
1270
1349
 
1271
1350
  get state(): unknown {
1272
1351
  const nextState = this.#readState();
1352
+ if (!this.#ctx) {
1353
+ return this.#createStateProxy(nextState);
1354
+ }
1355
+
1356
+ // Reading `conn.state` rebuilds the deep write-through proxy, which
1357
+ // allocates fresh on-change caches and rewraps the whole tree. Memoize
1358
+ // the proxy keyed on the underlying state object so repeated reads and
1359
+ // deep read cascades reuse a single proxy.
1360
+ if (
1361
+ this.#stateProxy === undefined ||
1362
+ this.#stateProxyTarget !== nextState
1363
+ ) {
1364
+ this.#stateProxyTarget = nextState;
1365
+ this.#stateProxy = this.#createStateProxy(nextState);
1366
+ }
1367
+ return this.#stateProxy;
1368
+ }
1369
+
1370
+ #createStateProxy(state: unknown): unknown {
1371
+ const assertCurrentState = () => {
1372
+ this.#assertCanMutateState?.();
1373
+ if (this.#ctx && this.#readState() !== state) {
1374
+ throw stateTransactionConflictError();
1375
+ }
1376
+ };
1273
1377
  return createWriteThroughProxy(
1274
- nextState,
1378
+ state,
1275
1379
  (nextValue) => {
1380
+ assertCurrentState();
1276
1381
  this.#writeState(nextValue, { writeNative: true });
1277
1382
  },
1278
1383
  (newValue) => {
1384
+ assertCurrentState();
1279
1385
  assertJsonCompatValue(newValue);
1280
1386
  },
1281
1387
  );
1282
1388
  }
1283
1389
 
1284
1390
  set state(value: unknown) {
1285
- assertJsonCompatValue(value);
1286
- this.#writeState(value, { writeNative: true });
1391
+ this.#assertCanMutateState?.();
1392
+ const nextValue = unwrapWriteThroughProxy(value);
1393
+ assertJsonCompatValue(nextValue);
1394
+ this.#writeState(nextValue, { writeNative: true });
1287
1395
  }
1288
1396
 
1289
1397
  initializeState(value: unknown): void {
1290
- this.#writeState(value, { writeNative: false });
1398
+ this.#writeState(unwrapWriteThroughProxy(value), {
1399
+ writeNative: false,
1400
+ });
1291
1401
  }
1292
1402
 
1293
1403
  get isHibernatable(): boolean {
@@ -1700,8 +1810,9 @@ function wrapQueueMessage(
1700
1810
  schemas: NativeValidationConfig["queues"],
1701
1811
  ) {
1702
1812
  const name = callNativeSync(() => message.name());
1813
+ const id = callNativeSync(() => message.id());
1703
1814
  return {
1704
- id: Number(callNativeSync(() => message.id())),
1815
+ id: id <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(id) : id,
1705
1816
  name,
1706
1817
  body: validateQueueBody(
1707
1818
  schemas,
@@ -1885,6 +1996,32 @@ class NativeQueueAdapter {
1885
1996
  cleanup?.();
1886
1997
  }
1887
1998
  }
1999
+ async waitForAvailable(
2000
+ names?: readonly string[],
2001
+ options?: { timeout?: number; signal?: AbortSignal },
2002
+ ): Promise<void> {
2003
+ await this.waitForNamesAvailable(names ?? [], options);
2004
+ }
2005
+
2006
+ async complete(
2007
+ message: { id: number | bigint; name: string },
2008
+ response?: unknown,
2009
+ ): Promise<void> {
2010
+ const validatedResponse = validateQueueComplete(
2011
+ this.#schemas,
2012
+ message.name,
2013
+ response,
2014
+ );
2015
+ await callNative(() =>
2016
+ this.#runtime.actorQueueCompletePersisted(
2017
+ this.#ctx,
2018
+ BigInt(message.id),
2019
+ message.name,
2020
+ encodeValue(validatedResponse),
2021
+ ),
2022
+ );
2023
+ this.#pendingCompletableMessageIds.delete(message.id.toString());
2024
+ }
1888
2025
 
1889
2026
  async enqueueAndWait(
1890
2027
  name: string,
@@ -2464,15 +2601,18 @@ class NativeConnectionMap implements ReadonlyMap<string, NativeConnAdapter> {
2464
2601
  #runtime: CoreRuntime;
2465
2602
  #ctx: ActorContextHandle;
2466
2603
  #schemas: NativeValidationConfig;
2604
+ #assertCanMutateState: () => void;
2467
2605
 
2468
2606
  constructor(
2469
2607
  runtime: CoreRuntime,
2470
2608
  ctx: ActorContextHandle,
2471
2609
  schemas: NativeValidationConfig,
2610
+ assertCanMutateState: () => void,
2472
2611
  ) {
2473
2612
  this.#runtime = runtime;
2474
2613
  this.#ctx = ctx;
2475
2614
  this.#schemas = schemas;
2615
+ this.#assertCanMutateState = assertCanMutateState;
2476
2616
  }
2477
2617
 
2478
2618
  #connToAdapter(conn: ConnHandle): NativeConnAdapter {
@@ -2488,6 +2628,7 @@ class NativeConnectionMap implements ReadonlyMap<string, NativeConnAdapter> {
2488
2628
  connId,
2489
2629
  ),
2490
2630
  ),
2631
+ this.#assertCanMutateState,
2491
2632
  );
2492
2633
  }
2493
2634
 
@@ -2575,10 +2716,13 @@ export class ActorContextHandleAdapter {
2575
2716
  #queue?: NativeQueueAdapter;
2576
2717
  #request?: Request;
2577
2718
  #schedule?: NativeScheduleAdapter;
2578
- #sql?: ReturnType<typeof wrapJsNativeDatabase>;
2579
- #runHandlerActiveProvider?: () => boolean;
2719
+ #run?: { setWakeAt(timestamp: number | null): Promise<void> };
2720
+ #runHandlerConfigured: boolean;
2580
2721
  #onStateChange?: NativeOnStateChangeHandler;
2581
2722
  #stateEnabled: boolean;
2723
+ #stateProxy?: unknown;
2724
+ #stateProxyTarget?: unknown;
2725
+ readonly #stateTransactionOwner = Symbol("actor-state-transaction-owner");
2582
2726
 
2583
2727
  constructor(
2584
2728
  runtime: CoreRuntime,
@@ -2588,16 +2732,16 @@ export class ActorContextHandleAdapter {
2588
2732
  databaseProvider?: AnyDatabaseProvider,
2589
2733
  request?: Request,
2590
2734
  stateEnabled = true,
2591
- runHandlerActiveProvider?: () => boolean,
2592
2735
  onStateChange?: NativeOnStateChangeHandler,
2593
2736
  dispatchCancelToken?: CancellationTokenHandle,
2737
+ runHandlerConfigured = false,
2594
2738
  ) {
2595
2739
  this.#runtime = runtime;
2596
2740
  this.#ctx = ctx;
2597
2741
  this.#clientFactory = clientFactory;
2598
2742
  this.#schemas = schemas;
2599
2743
  this.#dispatchCancelToken = dispatchCancelToken;
2600
- this.#runHandlerActiveProvider = runHandlerActiveProvider;
2744
+ this.#runHandlerConfigured = runHandlerConfigured;
2601
2745
  this.#onStateChange = onStateChange;
2602
2746
  this.#stateEnabled = stateEnabled;
2603
2747
  if (databaseProvider) {
@@ -2620,13 +2764,6 @@ export class ActorContextHandleAdapter {
2620
2764
  return this.#kv;
2621
2765
  }
2622
2766
 
2623
- get sql() {
2624
- if (!this.#sql) {
2625
- this.#sql = getOrCreateNativeSqlDatabase(this.#runtime, this.#ctx);
2626
- }
2627
- return this.#sql;
2628
- }
2629
-
2630
2767
  async actorRuntimeSocket(): Promise<{ path: string }> {
2631
2768
  return await callNative(() =>
2632
2769
  this.#runtime.actorRuntimeSocketProvision(this.#ctx),
@@ -2645,7 +2782,7 @@ export class ActorContextHandleAdapter {
2645
2782
  const runtimeState = getNativeRuntimeState(this.#runtime, this.#ctx);
2646
2783
  const cachedClient = runtimeState.databaseClient;
2647
2784
  if (cachedClient) {
2648
- this.#db = cachedClient.client;
2785
+ this.#db = this.#bindDatabaseClient(cachedClient.client);
2649
2786
  return this.#db;
2650
2787
  }
2651
2788
 
@@ -2663,29 +2800,35 @@ export class ActorContextHandleAdapter {
2663
2800
  if (!this.#stateEnabled) {
2664
2801
  throw stateNotEnabledError();
2665
2802
  }
2666
- const actorState = getNativePersistState(this.#runtime, this.#ctx);
2667
2803
  const nextState = this.#readState();
2668
2804
  // Reading `c.state` rebuilds the deep write-through proxy, which
2669
2805
  // allocates fresh on-change caches and rewraps the whole tree. Memoize
2670
2806
  // the proxy keyed on the underlying state object so repeated reads and
2671
2807
  // deep read cascades reuse a single proxy.
2672
2808
  if (
2673
- actorState.stateProxy === undefined ||
2674
- actorState.stateProxyTarget !== nextState
2809
+ this.#stateProxy === undefined ||
2810
+ this.#stateProxyTarget !== nextState
2675
2811
  ) {
2676
- actorState.stateProxyTarget = nextState;
2677
- actorState.stateProxy = createWriteThroughProxy(
2812
+ const assertCurrentState = () => {
2813
+ this.#assertCanMutateState();
2814
+ if (this.#readState() !== nextState) {
2815
+ throw stateTransactionConflictError();
2816
+ }
2817
+ };
2818
+ this.#stateProxyTarget = nextState;
2819
+ this.#stateProxy = createWriteThroughProxy(
2678
2820
  nextState,
2679
2821
  (nextValue) => {
2822
+ assertCurrentState();
2680
2823
  this.#writeState(nextValue, { scheduleSave: true });
2681
2824
  },
2682
2825
  (newValue) => {
2683
- this.#assertCanMutateState();
2826
+ assertCurrentState();
2684
2827
  assertJsonCompatValue(newValue);
2685
2828
  },
2686
2829
  );
2687
2830
  }
2688
- return actorState.stateProxy;
2831
+ return this.#stateProxy;
2689
2832
  }
2690
2833
 
2691
2834
  set state(value: unknown) {
@@ -2693,15 +2836,18 @@ export class ActorContextHandleAdapter {
2693
2836
  throw stateNotEnabledError();
2694
2837
  }
2695
2838
  this.#assertCanMutateState();
2696
- assertJsonCompatValue(value);
2697
- this.#writeState(value, { scheduleSave: true });
2839
+ const nextValue = unwrapWriteThroughProxy(value);
2840
+ assertJsonCompatValue(nextValue);
2841
+ this.#writeState(nextValue, { scheduleSave: true });
2698
2842
  }
2699
2843
 
2700
2844
  initializeState(value: unknown): void {
2701
2845
  if (!this.#stateEnabled) {
2702
2846
  return;
2703
2847
  }
2704
- this.#writeState(value, { scheduleSave: false });
2848
+ this.#writeState(unwrapWriteThroughProxy(value), {
2849
+ scheduleSave: false,
2850
+ });
2705
2851
  }
2706
2852
 
2707
2853
  get vars(): unknown {
@@ -2732,6 +2878,30 @@ export class ActorContextHandleAdapter {
2732
2878
  return this.#queue;
2733
2879
  }
2734
2880
 
2881
+ get run() {
2882
+ if (!this.#run) {
2883
+ this.#run = {
2884
+ setWakeAt: async (timestamp: number | null) => {
2885
+ if (timestamp !== null && !this.#runHandlerConfigured) {
2886
+ throw runHandlerNotConfiguredError();
2887
+ }
2888
+ if (
2889
+ timestamp !== null &&
2890
+ (!Number.isSafeInteger(timestamp) || timestamp < 0)
2891
+ ) {
2892
+ throw new TypeError(
2893
+ "Run wake timestamp must be a non-negative safe integer or null",
2894
+ );
2895
+ }
2896
+ await callNative(() =>
2897
+ this.#runtime.actorSetRunWakeAt(this.#ctx, timestamp),
2898
+ );
2899
+ },
2900
+ };
2901
+ }
2902
+ return this.#run;
2903
+ }
2904
+
2735
2905
  get schedule(): NativeScheduleAdapter {
2736
2906
  if (!this.#schedule) {
2737
2907
  this.#schedule = new NativeScheduleAdapter(
@@ -2773,6 +2943,7 @@ export class ActorContextHandleAdapter {
2773
2943
  this.#runtime,
2774
2944
  this.#ctx,
2775
2945
  this.#schemas,
2946
+ () => this.#assertCanMutateState(),
2776
2947
  );
2777
2948
  }
2778
2949
  return this.#connMap;
@@ -2853,12 +3024,12 @@ export class ActorContextHandleAdapter {
2853
3024
  const runtimeState = getNativeRuntimeState(this.#runtime, this.#ctx);
2854
3025
  const cachedClient = runtimeState.databaseClient;
2855
3026
  if (cachedClient) {
2856
- this.#db = cachedClient.client;
3027
+ this.#db = this.#bindDatabaseClient(cachedClient.client);
2857
3028
  return this.#db;
2858
3029
  }
2859
3030
 
2860
3031
  const actorId = this.actorId;
2861
- const client = await this.#databaseProvider.createClient({
3032
+ const createdClient = await this.#databaseProvider.createClient({
2862
3033
  actorId,
2863
3034
  kv: {
2864
3035
  batchPut: async (entries) => {
@@ -2879,21 +3050,147 @@ export class ActorContextHandleAdapter {
2879
3050
  log: {
2880
3051
  debug: (obj) => logger().debug(obj),
2881
3052
  },
2882
- nativeDatabaseProvider: {
2883
- open: async (requestedActorId) => {
2884
- void requestedActorId;
2885
- return getOrCreateNativeSqlDatabase(
3053
+ nativeDatabaseProvider: registerNativeStateTransactionOpener(
3054
+ {
3055
+ open: async (requestedActorId) => {
3056
+ void requestedActorId;
3057
+ return getOrCreateNativeSqlDatabase(
3058
+ this.#runtime,
3059
+ this.#ctx,
3060
+ );
3061
+ },
3062
+ },
3063
+ async (timeoutMs, context) =>
3064
+ await getOrCreateNativeSqlDatabase(
2886
3065
  this.#runtime,
2887
3066
  this.#ctx,
2888
- );
2889
- },
2890
- },
3067
+ ).beginStateTransaction(timeoutMs, context),
3068
+ ),
2891
3069
  });
2892
3070
  runtimeState.databaseClient = {
2893
- client,
3071
+ client: createdClient,
2894
3072
  };
2895
- this.#db = client;
2896
- return client;
3073
+ this.#db = this.#bindDatabaseClient(createdClient);
3074
+ return this.#db;
3075
+ }
3076
+
3077
+ #bindDatabaseClient(client: unknown): unknown {
3078
+ return bindNativeStateTransactionContext(client, {
3079
+ enter: async () => await this.#enterStateTransaction(),
3080
+ exit: (scope) =>
3081
+ this.#exitStateTransaction(
3082
+ scope as NativeStateTransactionScope,
3083
+ ),
3084
+ });
3085
+ }
3086
+
3087
+ async #enterStateTransaction(): Promise<NativeStateTransactionScope> {
3088
+ const actorState = getNativePersistState(this.#runtime, this.#ctx);
3089
+ let pendingOwners = actorState.pendingStateTransactionOwners;
3090
+ if (!pendingOwners) {
3091
+ pendingOwners = new Set();
3092
+ actorState.pendingStateTransactionOwners = pendingOwners;
3093
+ }
3094
+ if (
3095
+ actorState.activeStateTransactionOwner ===
3096
+ this.#stateTransactionOwner ||
3097
+ pendingOwners.has(this.#stateTransactionOwner)
3098
+ ) {
3099
+ throw stateTransactionConflictError();
3100
+ }
3101
+ pendingOwners.add(this.#stateTransactionOwner);
3102
+ const predecessor =
3103
+ actorState.stateTransactionTail ?? Promise.resolve();
3104
+ let release!: () => void;
3105
+ const hold = new Promise<void>((resolve) => {
3106
+ release = resolve;
3107
+ });
3108
+ actorState.stateTransactionTail = predecessor.then(
3109
+ async () => await hold,
3110
+ );
3111
+ try {
3112
+ await predecessor;
3113
+ pendingOwners.delete(this.#stateTransactionOwner);
3114
+ actorState.activeStateTransactionOwner =
3115
+ this.#stateTransactionOwner;
3116
+ return {
3117
+ actorContext: this,
3118
+ actorStateBaseline: this.#stateEnabled
3119
+ ? structuredClone(this.#readState())
3120
+ : undefined,
3121
+ connectionStateBaselines: new Map(
3122
+ callNativeSync(() =>
3123
+ this.#runtime.actorConns(this.#ctx),
3124
+ ).map((conn) => [
3125
+ callNativeSync(() => this.#runtime.connId(conn)),
3126
+ new Uint8Array(
3127
+ callNativeSync(() => this.#runtime.connState(conn)),
3128
+ ),
3129
+ ]),
3130
+ ),
3131
+ committed: false,
3132
+ release,
3133
+ };
3134
+ } catch (error) {
3135
+ pendingOwners.delete(this.#stateTransactionOwner);
3136
+ if (
3137
+ actorState.activeStateTransactionOwner ===
3138
+ this.#stateTransactionOwner
3139
+ ) {
3140
+ actorState.activeStateTransactionOwner = undefined;
3141
+ }
3142
+ release();
3143
+ throw error;
3144
+ }
3145
+ }
3146
+
3147
+ #exitStateTransaction(scope: NativeStateTransactionScope): void {
3148
+ const actorState = getNativePersistState(this.#runtime, this.#ctx);
3149
+ try {
3150
+ if (!scope.committed) {
3151
+ this.#restoreStateTransactionBaseline(scope);
3152
+ }
3153
+ } finally {
3154
+ if (
3155
+ actorState.activeStateTransactionOwner ===
3156
+ this.#stateTransactionOwner
3157
+ ) {
3158
+ actorState.activeStateTransactionOwner = undefined;
3159
+ }
3160
+ if (actorState.stateTransactionSaveDeferred) {
3161
+ actorState.stateTransactionSaveDeferred = false;
3162
+ this.#scheduleSave();
3163
+ }
3164
+ scope.release();
3165
+ }
3166
+ }
3167
+
3168
+ #restoreStateTransactionBaseline(scope: NativeStateTransactionScope): void {
3169
+ const actorState = getNativePersistState(this.#runtime, this.#ctx);
3170
+ if (actorState.pendingSaveHandle !== undefined) {
3171
+ clearImmediate(actorState.pendingSaveHandle);
3172
+ }
3173
+ actorState.pendingSaveHandle = undefined;
3174
+ actorState.saveScheduled = false;
3175
+
3176
+ if (this.#stateEnabled) {
3177
+ actorState.state = structuredClone(scope.actorStateBaseline);
3178
+ }
3179
+ for (const conn of callNativeSync(() =>
3180
+ this.#runtime.actorConns(this.#ctx),
3181
+ )) {
3182
+ const connId = callNativeSync(() => this.#runtime.connId(conn));
3183
+ const baseline = scope.connectionStateBaselines.get(connId);
3184
+ if (baseline === undefined) {
3185
+ continue;
3186
+ }
3187
+ callNativeSync(() => this.#runtime.connSetState(conn, baseline));
3188
+ const cached = actorState.connStates.get(connId);
3189
+ if (cached) {
3190
+ cached.state = decodeValue(baseline);
3191
+ }
3192
+ }
3193
+ this.#scheduleSave();
2897
3194
  }
2898
3195
 
2899
3196
  async prepare(): Promise<void> {
@@ -2916,7 +3213,6 @@ export class ActorContextHandleAdapter {
2916
3213
 
2917
3214
  async closeDatabase(): Promise<void> {
2918
3215
  this.#db = undefined;
2919
- this.#sql = undefined;
2920
3216
  await closeNativeDatabaseClient(this.#runtime, this.#ctx);
2921
3217
  await closeNativeSqlDatabase(this.#runtime, this.#ctx);
2922
3218
  }
@@ -2941,6 +3237,7 @@ export class ActorContextHandleAdapter {
2941
3237
  maxWait?: number;
2942
3238
  }): Promise<void> {
2943
3239
  if (opts?.immediate) {
3240
+ this.#assertCanAwaitStateSave();
2944
3241
  await callNative(() =>
2945
3242
  this.#runtime.actorRequestSaveAndWait(this.#ctx, {
2946
3243
  immediate: true,
@@ -2966,11 +3263,22 @@ export class ActorContextHandleAdapter {
2966
3263
  async saveStateAndWorkflowBatch(
2967
3264
  writes: Array<{ key: Uint8Array; value: Uint8Array }>,
2968
3265
  ): Promise<void> {
3266
+ this.#assertCanAwaitStateSave();
2969
3267
  await callNative(() =>
2970
3268
  this.#runtime.actorSaveStateAndWorkflowBatch(this.#ctx, writes),
2971
3269
  );
2972
3270
  }
2973
3271
 
3272
+ #assertCanAwaitStateSave(): void {
3273
+ const actorState = getNativePersistState(this.#runtime, this.#ctx);
3274
+ if (
3275
+ actorState.activeStateTransactionOwner ===
3276
+ this.#stateTransactionOwner
3277
+ ) {
3278
+ throw stateTransactionConflictError();
3279
+ }
3280
+ }
3281
+
2974
3282
  serializeForTick(reason: SerializeStateReason): RuntimeStateDeltaPayload {
2975
3283
  void reason;
2976
3284
  const actorState = getNativePersistState(this.#runtime, this.#ctx);
@@ -3034,10 +3342,6 @@ export class ActorContextHandleAdapter {
3034
3342
  return promise;
3035
3343
  }
3036
3344
 
3037
- runHandlerActive(): boolean {
3038
- return this.#runHandlerActiveProvider?.() ?? false;
3039
- }
3040
-
3041
3345
  internalKeepAwake<T>(run: Promise<T> | (() => Promise<T>)): Promise<T> {
3042
3346
  const promise = typeof run === "function" ? run() : run;
3043
3347
  // Track only completion, swallowing the outcome. The real result/error
@@ -3138,7 +3442,6 @@ export class ActorContextHandleAdapter {
3138
3442
  // down so the request-save and onStateChange always run.
3139
3443
  this.#flushStateChange();
3140
3444
  this.#abortSignalCleanup?.();
3141
- this.#sql = undefined;
3142
3445
  }
3143
3446
 
3144
3447
  #createActorAbortSignal(): AbortSignal {
@@ -3185,6 +3488,19 @@ export class ActorContextHandleAdapter {
3185
3488
  if (actorState.isInOnStateChange) {
3186
3489
  throw stateMutationReentrantError();
3187
3490
  }
3491
+ if (
3492
+ actorState.activeStateTransactionOwner !==
3493
+ this.#stateTransactionOwner &&
3494
+ (actorState.activeStateTransactionOwner !== undefined ||
3495
+ (actorState.pendingStateTransactionOwners?.size ?? 0) > 0)
3496
+ ) {
3497
+ throw stateTransactionConflictError();
3498
+ }
3499
+ }
3500
+
3501
+ /** @internal */
3502
+ assertCanMutateState(): void {
3503
+ this.#assertCanMutateState();
3188
3504
  }
3189
3505
 
3190
3506
  // Coalesce the request-save and onStateChange work to once per event loop
@@ -3208,6 +3524,15 @@ export class ActorContextHandleAdapter {
3208
3524
  if (!actorState.saveScheduled) {
3209
3525
  return;
3210
3526
  }
3527
+ if (actorState.activeStateTransactionOwner !== undefined) {
3528
+ actorState.saveScheduled = false;
3529
+ if (actorState.pendingSaveHandle !== undefined) {
3530
+ clearImmediate(actorState.pendingSaveHandle);
3531
+ actorState.pendingSaveHandle = undefined;
3532
+ }
3533
+ actorState.stateTransactionSaveDeferred = true;
3534
+ return;
3535
+ }
3211
3536
  actorState.saveScheduled = false;
3212
3537
  if (actorState.pendingSaveHandle !== undefined) {
3213
3538
  clearImmediate(actorState.pendingSaveHandle);
@@ -3409,10 +3734,6 @@ class NativeWorkflowRuntimeAdapter {
3409
3734
  };
3410
3735
  }
3411
3736
 
3412
- isRunHandlerActive(): boolean {
3413
- return this.#ctx.runHandlerActive();
3414
- }
3415
-
3416
3737
  async restartRunHandler(): Promise<void> {
3417
3738
  await this.#ctx.restartRunHandler();
3418
3739
  }
@@ -3460,28 +3781,33 @@ function withConnContext(
3460
3781
  stateEnabled = true,
3461
3782
  onStateChange?: NativeOnStateChangeHandler,
3462
3783
  dispatchCancelToken?: CancellationTokenHandle,
3784
+ runHandlerConfigured = false,
3463
3785
  ) {
3464
- return Object.assign(
3465
- new ActorContextHandleAdapter(
3786
+ const actorContext = new ActorContextHandleAdapter(
3787
+ runtime,
3788
+ ctx,
3789
+ clientFactory,
3790
+ schemas,
3791
+ databaseProvider,
3792
+ request,
3793
+ stateEnabled,
3794
+ onStateChange,
3795
+ dispatchCancelToken,
3796
+ runHandlerConfigured,
3797
+ );
3798
+ return Object.assign(actorContext, {
3799
+ conn: new NativeConnAdapter(
3466
3800
  runtime,
3467
- ctx,
3468
- clientFactory,
3801
+ conn,
3469
3802
  schemas,
3470
- databaseProvider,
3471
- request,
3472
- stateEnabled,
3473
- undefined,
3474
- onStateChange,
3475
- dispatchCancelToken,
3476
- ),
3477
- {
3478
- conn: new NativeConnAdapter(runtime, conn, schemas, ctx, (connId) =>
3803
+ ctx,
3804
+ (connId) =>
3479
3805
  callNativeSync(() =>
3480
3806
  runtime.actorQueueHibernationRemoval(ctx, connId),
3481
3807
  ),
3482
- ),
3483
- },
3484
- );
3808
+ () => actorContext.assertCanMutateState(),
3809
+ ),
3810
+ });
3485
3811
  }
3486
3812
 
3487
3813
  function buildActorConfig(
@@ -3494,12 +3820,16 @@ function buildActorConfig(
3494
3820
  const canHibernate = options.canHibernateWebSocket;
3495
3821
  const usesRemoteSqlite =
3496
3822
  sqliteBackendForConfig(registryConfig) === "remote";
3823
+ const sqliteProfiling = (
3824
+ config.db as { sqliteProfiling?: SqliteProfilingOptions } | undefined
3825
+ )?.sqliteProfiling;
3497
3826
 
3498
3827
  return {
3499
3828
  name: options.name as string | undefined,
3500
3829
  icon: options.icon as string | undefined,
3501
- hasDatabase: config.db !== undefined || usesRemoteSqlite,
3830
+ hasDatabase: true,
3502
3831
  remoteSqlite: usesRemoteSqlite,
3832
+ sqliteProfiling,
3503
3833
  enableActorRuntimeSocket: options.enableActorRuntimeSocket === true,
3504
3834
  hasState:
3505
3835
  config.state !== undefined ||
@@ -3644,7 +3974,10 @@ export function buildNativeFactory(
3644
3974
  definition: AnyActorDefinition,
3645
3975
  ): ActorFactoryHandle {
3646
3976
  const config = definition.config as Record<string, any>;
3647
- const databaseProvider = config.db as AnyDatabaseProvider;
3977
+ const databaseProvider = (config.db ?? defaultDb()) as Exclude<
3978
+ AnyDatabaseProvider,
3979
+ undefined
3980
+ >;
3648
3981
  const actionHandlers = flattenActionHandlers(config.actions);
3649
3982
  const schemaConfig: NativeValidationConfig = {
3650
3983
  actionInputSchemas: flattenActionInputSchemas(
@@ -3662,16 +3995,21 @@ export function buildNativeFactory(
3662
3995
  ),
3663
3996
  { encoding: "bare" },
3664
3997
  );
3665
- const nativeRunHandlerActiveByActorId = new Map<string, boolean>();
3666
- const isNativeRunHandlerActive = (ctx: ActorContextHandle) =>
3667
- nativeRunHandlerActiveByActorId.get(
3668
- callNativeSync(() => runtime.actorId(ctx)),
3669
- ) ?? false;
3670
- const getNativeWorkflowInspector = (ctx: ActorContextHandle) =>
3671
- getRunInspectorConfig(
3672
- config.run,
3673
- callNativeSync(() => runtime.actorId(ctx)),
3674
- )?.workflow as NativeWorkflowInspectorConfig | undefined;
3998
+ const run = getRunFunction(config.run);
3999
+ const runHandlerCoordinator =
4000
+ getRunInspectorKind(config.run) === "workflow"
4001
+ ? new RunHandlerCoordinator(config.run)
4002
+ : undefined;
4003
+ const getNativeWorkflowInspector = (ctx: ActorContextHandle) => {
4004
+ const actorId = callNativeSync(() => runtime.actorId(ctx));
4005
+ const restart = () =>
4006
+ callNativeSync(() => runtime.actorRestartRunHandler(ctx));
4007
+ return (runHandlerCoordinator?.getInspector(actorId, restart)
4008
+ ?.workflow ??
4009
+ getRunInspectorConfig(config.run, actorId)?.workflow) as
4010
+ | NativeWorkflowInspectorConfig
4011
+ | undefined;
4012
+ };
3675
4013
  const onStateChange =
3676
4014
  typeof config.onStateChange === "function"
3677
4015
  ? (actorCtx: ActorContextHandleAdapter, nextState: unknown) => {
@@ -3704,9 +4042,9 @@ export function buildNativeFactory(
3704
4042
  databaseProvider,
3705
4043
  request,
3706
4044
  stateEnabled,
3707
- () => isNativeRunHandlerActive(ctx),
3708
4045
  onStateChange,
3709
4046
  cancelToken,
4047
+ run !== undefined,
3710
4048
  );
3711
4049
  const makeConnCtx = (
3712
4050
  ctx: ActorContextHandle,
@@ -3725,6 +4063,7 @@ export function buildNativeFactory(
3725
4063
  stateEnabled,
3726
4064
  onStateChange,
3727
4065
  cancelToken,
4066
+ run !== undefined,
3728
4067
  );
3729
4068
  const maybeHandleNativeInspectorRequest = async (
3730
4069
  ctx: ActorContextHandle,
@@ -3793,6 +4132,7 @@ export function buildNativeFactory(
3793
4132
  const workflowState = async () =>
3794
4133
  (await getNativeWorkflowInspector(ctx)?.getState?.()) ?? null;
3795
4134
  const actorCtx = makeActorCtx(ctx, jsRequest);
4135
+ const sql = getOrCreateNativeSqlDatabase(runtime, ctx);
3796
4136
  try {
3797
4137
  if (
3798
4138
  url.pathname === "/inspector/state" &&
@@ -3964,7 +4304,7 @@ export function buildNativeFactory(
3964
4304
  url.pathname === "/inspector/database/schema" &&
3965
4305
  jsRequest.method === "GET"
3966
4306
  ) {
3967
- const db = actorCtx.sql;
4307
+ const db = sql;
3968
4308
  const tables = queryRows(
3969
4309
  await db.query(
3970
4310
  "SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name",
@@ -4018,7 +4358,7 @@ export function buildNativeFactory(
4018
4358
  );
4019
4359
  const quoted = `"${table.replace(/"/g, '""')}"`;
4020
4360
  const rows = queryRows(
4021
- await actorCtx.sql.query(
4361
+ await sql.query(
4022
4362
  `SELECT * FROM ${quoted} LIMIT ? OFFSET ?`,
4023
4363
  [
4024
4364
  Math.max(0, Math.min(limit, 500)),
@@ -4061,15 +4401,11 @@ export function buildNativeFactory(
4061
4401
  const bindings = normalizeSqlitePropertyBindings(
4062
4402
  body.properties as Record<string, unknown>,
4063
4403
  );
4064
- const rows = queryRows(
4065
- await actorCtx.sql.query(body.sql, bindings),
4066
- );
4404
+ const rows = queryRows(await sql.query(body.sql, bindings));
4067
4405
  return jsonResponse({ rows: jsonSafe(rows) });
4068
4406
  }
4069
4407
  const args = Array.isArray(body.args) ? body.args : [];
4070
- const rows = queryRows(
4071
- await actorCtx.sql.query(body.sql, args),
4072
- );
4408
+ const rows = queryRows(await sql.query(body.sql, args));
4073
4409
  return jsonResponse({ rows: jsonSafe(rows) });
4074
4410
  }
4075
4411
  if (
@@ -4098,7 +4434,7 @@ export function buildNativeFactory(
4098
4434
  rpcs: Object.keys(actionHandlers).sort(),
4099
4435
  queueSize: inspectorSnapshot.queueSize,
4100
4436
  isStateEnabled: stateEnabled,
4101
- isDatabaseEnabled: databaseProvider !== undefined,
4437
+ isDatabaseEnabled: true,
4102
4438
  isWorkflowEnabled:
4103
4439
  getNativeWorkflowInspector(ctx) !== undefined,
4104
4440
  workflowState: await workflowState(),
@@ -4354,6 +4690,7 @@ export function buildNativeFactory(
4354
4690
  async (error: unknown, payload: { ctx: ActorContextHandle }) => {
4355
4691
  const { ctx } = unwrapTsfnPayload(error, payload);
4356
4692
  const actorCtx = makeActorCtx(ctx);
4693
+ const actorId = callNativeSync(() => runtime.actorId(ctx));
4357
4694
  // TODO: Move this save hook into cleanupNativeSleepRuntimeState
4358
4695
  // so immediate and deferred sleep cleanup share one save-state
4359
4696
  // path instead of passing a callback through cleanup.
@@ -4384,6 +4721,8 @@ export function buildNativeFactory(
4384
4721
  saveActorState,
4385
4722
  );
4386
4723
  } finally {
4724
+ runHandlerCoordinator?.destroy(actorId);
4725
+ disposeRunInspector(config.run, actorId);
4387
4726
  await actorCtx.dispose();
4388
4727
  }
4389
4728
  }
@@ -4393,16 +4732,16 @@ export function buildNativeFactory(
4393
4732
  async (error: unknown, payload: { ctx: ActorContextHandle }) => {
4394
4733
  const { ctx } = unwrapTsfnPayload(error, payload);
4395
4734
  const actorCtx = makeActorCtx(ctx);
4735
+ const actorId = callNativeSync(() => runtime.actorId(ctx));
4736
+ // Close run control before user cleanup so replay cannot race actor
4737
+ // destruction. Recreating this actor id receives a fresh controller.
4738
+ runHandlerCoordinator?.destroy(actorId);
4739
+ disposeRunInspector(config.run, actorId);
4396
4740
  try {
4397
4741
  if (typeof config.onDestroy === "function") {
4398
4742
  await config.onDestroy(actorCtx);
4399
4743
  }
4400
4744
  } finally {
4401
- const actorId = callNativeSync(() => runtime.actorId(ctx));
4402
- // Release actorId-keyed state so it does not accumulate per
4403
- // destroyed actor.
4404
- nativeRunHandlerActiveByActorId.delete(actorId);
4405
- disposeRunInspector(config.run, actorId);
4406
4745
  resolveNativeDestroy(runtime, ctx);
4407
4746
  await actorCtx.closeDatabase();
4408
4747
  clearNativeRuntimeState(runtime, ctx);
@@ -4483,6 +4822,7 @@ export function buildNativeFactory(
4483
4822
  connId,
4484
4823
  ),
4485
4824
  ),
4825
+ () => actorCtx.assertCanMutateState(),
4486
4826
  );
4487
4827
  try {
4488
4828
  const nextConnState = hasStaticConnState
@@ -4538,6 +4878,7 @@ export function buildNativeFactory(
4538
4878
  connId,
4539
4879
  ),
4540
4880
  ),
4881
+ () => actorCtx.assertCanMutateState(),
4541
4882
  );
4542
4883
  try {
4543
4884
  await config.onConnect(
@@ -4581,6 +4922,7 @@ export function buildNativeFactory(
4581
4922
  connId,
4582
4923
  ),
4583
4924
  ),
4925
+ () => actorCtx.assertCanMutateState(),
4584
4926
  ),
4585
4927
  );
4586
4928
  }
@@ -4789,7 +5131,6 @@ export function buildNativeFactory(
4789
5131
  )
4790
5132
  : undefined,
4791
5133
  run: (() => {
4792
- const run = getRunFunction(config.run);
4793
5134
  if (!run) {
4794
5135
  return undefined;
4795
5136
  }
@@ -4801,67 +5142,75 @@ export function buildNativeFactory(
4801
5142
  ) => {
4802
5143
  const { ctx } = unwrapTsfnPayload(error, payload);
4803
5144
  const actorId = callNativeSync(() => runtime.actorId(ctx));
4804
- const actorCtx = makeActorCtx(ctx);
4805
- nativeRunHandlerActiveByActorId.set(actorId, true);
4806
- try {
4807
- await run(actorCtx);
4808
- } finally {
4809
- // Delete rather than set(false): an absent entry already
4810
- // reads as inactive, and deleting keeps this map bounded
4811
- // to currently-running handlers instead of accumulating an
4812
- // entry per actor id forever.
4813
- nativeRunHandlerActiveByActorId.delete(actorId);
4814
- await actorCtx.dispose();
5145
+ const executeRun = async () => {
5146
+ const actorCtx = makeActorCtx(ctx);
5147
+ try {
5148
+ await run(actorCtx);
5149
+ } finally {
5150
+ await actorCtx.dispose();
5151
+ }
5152
+ };
5153
+ if (runHandlerCoordinator) {
5154
+ const outcome = await runHandlerCoordinator.run(
5155
+ actorId,
5156
+ () =>
5157
+ callNativeSync(() =>
5158
+ runtime.actorRestartRunHandler(ctx),
5159
+ ),
5160
+ executeRun,
5161
+ );
5162
+ // Only a start suppressed behind a failed replay must preserve a
5163
+ // consumed durable wake. Closed actors must remain closed.
5164
+ return outcome !== "suppressed";
4815
5165
  }
5166
+ await executeRun();
5167
+ return true;
4816
5168
  },
4817
5169
  );
4818
5170
  })(),
4819
- getWorkflowHistory:
4820
- getRunInspectorConfig(config.run) !== undefined
4821
- ? wrapNativeCallback(
4822
- async (
4823
- error: unknown,
4824
- payload: { ctx: ActorContextHandle },
4825
- ) => {
4826
- const { ctx } = unwrapTsfnPayload(error, payload);
4827
- const history =
4828
- getNativeWorkflowInspector(ctx)?.getHistory();
4829
- return history == null
4830
- ? undefined
4831
- : toUint8Array(history);
5171
+ getWorkflowHistory: hasRunInspectorConfig(config.run)
5172
+ ? wrapNativeCallback(
5173
+ async (
5174
+ error: unknown,
5175
+ payload: { ctx: ActorContextHandle },
5176
+ ) => {
5177
+ const { ctx } = unwrapTsfnPayload(error, payload);
5178
+ const history =
5179
+ getNativeWorkflowInspector(ctx)?.getHistory();
5180
+ return history == null
5181
+ ? undefined
5182
+ : toUint8Array(history);
5183
+ },
5184
+ )
5185
+ : undefined,
5186
+ replayWorkflow: hasRunInspectorConfig(config.run)
5187
+ ? wrapNativeCallback(
5188
+ async (
5189
+ error: unknown,
5190
+ payload: {
5191
+ ctx: ActorContextHandle;
5192
+ entryId?: string;
4832
5193
  },
4833
- )
4834
- : undefined,
4835
- replayWorkflow:
4836
- getRunInspectorConfig(config.run) !== undefined
4837
- ? wrapNativeCallback(
4838
- async (
4839
- error: unknown,
4840
- payload: {
4841
- ctx: ActorContextHandle;
4842
- entryId?: string;
4843
- },
4844
- ) => {
4845
- const { ctx, entryId } = unwrapTsfnPayload(
4846
- error,
4847
- payload,
4848
- );
4849
- const workflowInspector =
4850
- getNativeWorkflowInspector(ctx);
4851
- if (!workflowInspector?.replayFromStep) {
4852
- return undefined;
4853
- }
5194
+ ) => {
5195
+ const { ctx, entryId } = unwrapTsfnPayload(
5196
+ error,
5197
+ payload,
5198
+ );
5199
+ const workflowInspector =
5200
+ getNativeWorkflowInspector(ctx);
5201
+ if (!workflowInspector?.replayFromStep) {
5202
+ return undefined;
5203
+ }
4854
5204
 
4855
- const history =
4856
- (await workflowInspector.replayFromStep(
4857
- entryId,
4858
- )) ?? null;
4859
- return history == null
4860
- ? undefined
4861
- : toUint8Array(history);
4862
- },
4863
- )
4864
- : undefined,
5205
+ const history =
5206
+ (await workflowInspector.replayFromStep(entryId)) ??
5207
+ null;
5208
+ return history == null
5209
+ ? undefined
5210
+ : toUint8Array(history);
5211
+ },
5212
+ )
5213
+ : undefined,
4865
5214
  actions: Object.fromEntries(
4866
5215
  Object.entries(actionHandlers).map(([name, handler]) => [
4867
5216
  name,
@@ -4943,6 +5292,7 @@ export function buildNativeFactory(
4943
5292
  stateEnabled,
4944
5293
  onStateChange,
4945
5294
  cancelToken,
5295
+ run !== undefined,
4946
5296
  );
4947
5297
  try {
4948
5298
  if (