rivetkit 2.3.0-rc.9 → 2.3.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 (226) hide show
  1. package/dist/browser/client.d.ts +511 -62
  2. package/dist/browser/client.js +230 -174
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +53 -23
  5. package/dist/browser/inspector/client.js.map +1 -1
  6. package/dist/tsup/actor/errors.cjs +4 -2
  7. package/dist/tsup/actor/errors.cjs.map +1 -1
  8. package/dist/tsup/actor/errors.d.cts +1 -1
  9. package/dist/tsup/actor/errors.d.ts +1 -1
  10. package/dist/tsup/actor/errors.js +3 -1
  11. package/dist/tsup/agent-os/index.cjs +2163 -2087
  12. package/dist/tsup/agent-os/index.cjs.map +1 -1
  13. package/dist/tsup/agent-os/index.d.cts +509 -69
  14. package/dist/tsup/agent-os/index.d.ts +509 -69
  15. package/dist/tsup/agent-os/index.js +2163 -2087
  16. package/dist/tsup/agent-os/index.js.map +1 -1
  17. package/dist/tsup/{chunk-WQ4HNA4W.cjs → chunk-3MHDOUD7.cjs} +73 -3
  18. package/dist/tsup/chunk-3MHDOUD7.cjs.map +1 -0
  19. package/dist/tsup/{chunk-QAZLM4WT.cjs → chunk-4FC7TVS6.cjs} +8 -4
  20. package/dist/tsup/chunk-4FC7TVS6.cjs.map +1 -0
  21. package/dist/tsup/{chunk-4CGA6QJO.cjs → chunk-4UUEB43Y.cjs} +24 -9
  22. package/dist/tsup/chunk-4UUEB43Y.cjs.map +1 -0
  23. package/dist/tsup/{chunk-GVTOE34S.cjs → chunk-5IWLUJ6W.cjs} +222 -235
  24. package/dist/tsup/chunk-5IWLUJ6W.cjs.map +1 -0
  25. package/dist/tsup/{chunk-MMMEZM5J.js → chunk-H6VVZMWN.js} +4 -4
  26. package/dist/tsup/chunk-H6VVZMWN.js.map +1 -0
  27. package/dist/tsup/{chunk-3YY5S6TV.js → chunk-HXUEHHJF.js} +2 -2
  28. package/dist/tsup/chunk-HXUEHHJF.js.map +1 -0
  29. package/dist/tsup/{chunk-H7P7WR2Y.js → chunk-I35VSLEM.js} +6 -6
  30. package/dist/tsup/chunk-I35VSLEM.js.map +1 -0
  31. package/dist/tsup/{chunk-H37XQU3I.js → chunk-JBUZRPY5.js} +2 -2
  32. package/dist/tsup/{chunk-CPA4Y3RG.cjs → chunk-JLJJZYCJ.cjs} +10 -10
  33. package/dist/tsup/chunk-JLJJZYCJ.cjs.map +1 -0
  34. package/dist/tsup/{chunk-PCBNKI2J.js → chunk-JZ7TWV65.js} +1 -1
  35. package/dist/tsup/chunk-JZ7TWV65.js.map +1 -0
  36. package/dist/tsup/{chunk-VRCIXJRN.js → chunk-L2X3YFER.js} +64 -10
  37. package/dist/tsup/chunk-L2X3YFER.js.map +1 -0
  38. package/dist/tsup/{chunk-Y5NSCZA2.cjs → chunk-MNHKOS6L.cjs} +72 -18
  39. package/dist/tsup/chunk-MNHKOS6L.cjs.map +1 -0
  40. package/dist/tsup/{chunk-KJTA3ATT.js → chunk-NERUIBOT.js} +22 -7
  41. package/dist/tsup/chunk-NERUIBOT.js.map +1 -0
  42. package/dist/tsup/{chunk-4WPEZBK4.cjs → chunk-OST76LRW.cjs} +10 -10
  43. package/dist/tsup/chunk-OST76LRW.cjs.map +1 -0
  44. package/dist/tsup/{chunk-MALSPBAF.cjs → chunk-OZBCXBVP.cjs} +3 -3
  45. package/dist/tsup/{chunk-MALSPBAF.cjs.map → chunk-OZBCXBVP.cjs.map} +1 -1
  46. package/dist/tsup/{chunk-F3Q5BFQ6.js → chunk-PT6OIW5E.js} +66 -79
  47. package/dist/tsup/chunk-PT6OIW5E.js.map +1 -0
  48. package/dist/tsup/{chunk-W7EYSYVI.js → chunk-R6KPN5EW.js} +134 -20
  49. package/dist/tsup/chunk-R6KPN5EW.js.map +1 -0
  50. package/dist/tsup/{chunk-VJFRBJVQ.cjs → chunk-V5KMAMX3.cjs} +138 -24
  51. package/dist/tsup/chunk-V5KMAMX3.cjs.map +1 -0
  52. package/dist/tsup/{chunk-LD5YASJU.cjs → chunk-VE2X4KMG.cjs} +2 -2
  53. package/dist/tsup/{chunk-LD5YASJU.cjs.map → chunk-VE2X4KMG.cjs.map} +1 -1
  54. package/dist/tsup/{chunk-T6YVRM4K.js → chunk-XIX5DOZN.js} +72 -2
  55. package/dist/tsup/chunk-XIX5DOZN.js.map +1 -0
  56. package/dist/tsup/{chunk-2NDZ7JCR.cjs → chunk-ZA7FLHKH.cjs} +1 -1
  57. package/dist/tsup/chunk-ZA7FLHKH.cjs.map +1 -0
  58. package/dist/tsup/{chunk-KIWH5H3K.js → chunk-ZZ3WBRPD.js} +7 -3
  59. package/dist/tsup/chunk-ZZ3WBRPD.js.map +1 -0
  60. package/dist/tsup/client/mod.cjs +9 -9
  61. package/dist/tsup/client/mod.d.cts +5 -5
  62. package/dist/tsup/client/mod.d.ts +5 -5
  63. package/dist/tsup/client/mod.js +8 -8
  64. package/dist/tsup/common/log.cjs +3 -3
  65. package/dist/tsup/common/log.js +2 -2
  66. package/dist/tsup/common/websocket.cjs +4 -4
  67. package/dist/tsup/common/websocket.js +3 -3
  68. package/dist/tsup/{config-Ca8dN4cS.d.cts → config-CzvopP5m.d.cts} +544 -23
  69. package/dist/tsup/{config-CxjGYf4K.d.cts → config-D49x8NpL.d.cts} +1 -2
  70. package/dist/tsup/{config-CxjGYf4K.d.ts → config-D49x8NpL.d.ts} +1 -2
  71. package/dist/tsup/{config-0Ta55UV0.d.ts → config-DZuT7tcp.d.ts} +544 -23
  72. package/dist/tsup/context-CyAdY-aA.d.ts +128 -0
  73. package/dist/tsup/context-sNB28g0N.d.cts +128 -0
  74. package/dist/tsup/db/drizzle.cjs +3 -3
  75. package/dist/tsup/db/drizzle.d.cts +1 -1
  76. package/dist/tsup/db/drizzle.d.ts +1 -1
  77. package/dist/tsup/db/drizzle.js +1 -1
  78. package/dist/tsup/db/mod.cjs +2 -2
  79. package/dist/tsup/db/mod.d.cts +2 -2
  80. package/dist/tsup/db/mod.d.ts +2 -2
  81. package/dist/tsup/db/mod.js +1 -1
  82. package/dist/tsup/dynamic/mod.cjs +24 -0
  83. package/dist/tsup/dynamic/mod.cjs.map +1 -0
  84. package/dist/tsup/dynamic/mod.d.cts +37 -0
  85. package/dist/tsup/dynamic/mod.d.ts +37 -0
  86. package/dist/tsup/dynamic/mod.js +24 -0
  87. package/dist/tsup/dynamic/mod.js.map +1 -0
  88. package/dist/tsup/inspector/mod.cjs +6 -6
  89. package/dist/tsup/inspector/mod.js +5 -5
  90. package/dist/tsup/inspector-tab/mod.cjs +173 -0
  91. package/dist/tsup/inspector-tab/mod.cjs.map +1 -0
  92. package/dist/tsup/inspector-tab/mod.d.cts +250 -0
  93. package/dist/tsup/inspector-tab/mod.d.ts +250 -0
  94. package/dist/tsup/inspector-tab/mod.js +173 -0
  95. package/dist/tsup/inspector-tab/mod.js.map +1 -0
  96. package/dist/tsup/mod.cjs +758 -348
  97. package/dist/tsup/mod.cjs.map +1 -1
  98. package/dist/tsup/mod.d.cts +5 -5
  99. package/dist/tsup/mod.d.ts +5 -5
  100. package/dist/tsup/mod.js +662 -252
  101. package/dist/tsup/mod.js.map +1 -1
  102. package/dist/tsup/test/mod.cjs +21 -18
  103. package/dist/tsup/test/mod.cjs.map +1 -1
  104. package/dist/tsup/test/mod.d.cts +4 -4
  105. package/dist/tsup/test/mod.d.ts +4 -4
  106. package/dist/tsup/test/mod.js +18 -15
  107. package/dist/tsup/test/mod.js.map +1 -1
  108. package/dist/tsup/{utils-DVekpm4I.d.cts → utils-CqDnC_PS.d.cts} +2 -1
  109. package/dist/tsup/{utils-DVekpm4I.d.ts → utils-CqDnC_PS.d.ts} +2 -1
  110. package/dist/tsup/utils.cjs +3 -3
  111. package/dist/tsup/utils.d.cts +1 -1
  112. package/dist/tsup/utils.d.ts +1 -1
  113. package/dist/tsup/utils.js +2 -2
  114. package/dist/tsup/workflow/mod.cjs +383 -322
  115. package/dist/tsup/workflow/mod.cjs.map +1 -1
  116. package/dist/tsup/workflow/mod.d.cts +8 -8
  117. package/dist/tsup/workflow/mod.d.ts +8 -8
  118. package/dist/tsup/workflow/mod.js +360 -299
  119. package/dist/tsup/workflow/mod.js.map +1 -1
  120. package/package.json +35 -14
  121. package/src/actor/config.ts +173 -51
  122. package/src/actor/contexts/index.ts +7 -2
  123. package/src/actor/definition.ts +17 -19
  124. package/src/actor/driver.ts +3 -3
  125. package/src/actor/errors.ts +20 -3
  126. package/src/actor/instance/mod.ts +26 -34
  127. package/src/actor/keys.ts +1 -1
  128. package/src/actor/mod.ts +22 -20
  129. package/src/actor/schema.ts +2 -2
  130. package/src/agent-os/actor/index.ts +38 -18
  131. package/src/agent-os/actor/preview.ts +1 -2
  132. package/src/agent-os/actor/session.ts +2 -2
  133. package/src/agent-os/config.ts +1 -1
  134. package/src/agent-os/fs/database-vfs.ts +1 -1
  135. package/src/agent-os/index.ts +16 -15
  136. package/src/client/actor-common.ts +87 -54
  137. package/src/client/actor-conn.ts +8 -36
  138. package/src/client/actor-handle.ts +69 -51
  139. package/src/client/actor-query.ts +5 -5
  140. package/src/client/errors.ts +1 -1
  141. package/src/client/lifecycle-errors.ts +2 -4
  142. package/src/client/query.ts +1 -1
  143. package/src/client/queue.ts +8 -3
  144. package/src/client/raw-utils.ts +8 -6
  145. package/src/client/resolve-gateway-target.ts +1 -1
  146. package/src/client/utils.ts +2 -7
  147. package/src/common/actor-websocket.ts +3 -1
  148. package/src/common/bare/actor-persist/v1.ts +205 -163
  149. package/src/common/bare/actor-persist/v2.ts +265 -213
  150. package/src/common/bare/actor-persist/v3.ts +176 -172
  151. package/src/common/bare/actor-persist/v4.ts +254 -253
  152. package/src/common/bare/transport/v1.ts +659 -543
  153. package/src/common/client-protocol-versioned.ts +66 -64
  154. package/src/common/database/config.ts +2 -8
  155. package/src/common/database/native-database.ts +1 -1
  156. package/src/common/database/shared.ts +1 -0
  157. package/src/common/encoding.ts +250 -16
  158. package/src/common/engine.ts +28 -1
  159. package/src/common/eventsource.ts +1 -1
  160. package/src/common/inline-websocket-adapter.ts +14 -13
  161. package/src/common/log.ts +1 -0
  162. package/src/common/router.ts +13 -17
  163. package/src/common/utils.ts +1 -150
  164. package/src/common/websocket-interface.ts +1 -1
  165. package/src/db/mod.ts +1 -1
  166. package/src/devtools-loader/index.ts +4 -7
  167. package/src/devtools-loader/serve-devtools.ts +26 -0
  168. package/src/drivers/engine/actor-driver.ts +58 -56
  169. package/src/dynamic/instance.ts +32 -0
  170. package/src/dynamic/internal.ts +50 -0
  171. package/src/dynamic/isolate-runtime.ts +66 -0
  172. package/src/dynamic/mod.ts +32 -0
  173. package/src/engine-client/actor-http-client.ts +3 -3
  174. package/src/engine-client/actor-websocket-client.ts +6 -5
  175. package/src/engine-client/api-endpoints.ts +51 -2
  176. package/src/engine-client/api-utils.ts +2 -2
  177. package/src/engine-client/driver.ts +1 -1
  178. package/src/engine-client/mod.ts +6 -3
  179. package/src/engine-client/ws-proxy.ts +9 -4
  180. package/src/inspector/client.browser.ts +5 -11
  181. package/src/inspector/mod.ts +1 -3
  182. package/src/inspector-tab/mod.ts +315 -0
  183. package/src/registry/config/envoy.ts +1 -2
  184. package/src/registry/config/index.ts +40 -16
  185. package/src/registry/index.ts +209 -73
  186. package/src/registry/napi-runtime.ts +29 -2
  187. package/src/registry/native-validation.ts +10 -12
  188. package/src/registry/native.ts +433 -198
  189. package/src/registry/process-metrics.ts +250 -0
  190. package/src/registry/runtime.ts +52 -1
  191. package/src/registry/wasm-runtime.ts +29 -2
  192. package/src/registry/write-through-proxy.ts +40 -0
  193. package/src/serde.ts +2 -2
  194. package/src/serverless/configure.ts +18 -7
  195. package/src/test/mod.ts +11 -8
  196. package/src/utils/endpoint-parser.ts +1 -1
  197. package/src/utils/env-vars.ts +37 -0
  198. package/src/utils/router.ts +1 -1
  199. package/src/utils.ts +1 -2
  200. package/src/workflow/context.ts +699 -240
  201. package/src/workflow/driver.ts +23 -12
  202. package/src/workflow/inspector.ts +4 -3
  203. package/src/workflow/mod.ts +37 -23
  204. package/dist/tsup/chunk-2NDZ7JCR.cjs.map +0 -1
  205. package/dist/tsup/chunk-3YY5S6TV.js.map +0 -1
  206. package/dist/tsup/chunk-4CGA6QJO.cjs.map +0 -1
  207. package/dist/tsup/chunk-4WPEZBK4.cjs.map +0 -1
  208. package/dist/tsup/chunk-CPA4Y3RG.cjs.map +0 -1
  209. package/dist/tsup/chunk-F3Q5BFQ6.js.map +0 -1
  210. package/dist/tsup/chunk-GVTOE34S.cjs.map +0 -1
  211. package/dist/tsup/chunk-H7P7WR2Y.js.map +0 -1
  212. package/dist/tsup/chunk-KIWH5H3K.js.map +0 -1
  213. package/dist/tsup/chunk-KJTA3ATT.js.map +0 -1
  214. package/dist/tsup/chunk-MMMEZM5J.js.map +0 -1
  215. package/dist/tsup/chunk-PCBNKI2J.js.map +0 -1
  216. package/dist/tsup/chunk-QAZLM4WT.cjs.map +0 -1
  217. package/dist/tsup/chunk-T6YVRM4K.js.map +0 -1
  218. package/dist/tsup/chunk-VJFRBJVQ.cjs.map +0 -1
  219. package/dist/tsup/chunk-VRCIXJRN.js.map +0 -1
  220. package/dist/tsup/chunk-W7EYSYVI.js.map +0 -1
  221. package/dist/tsup/chunk-WQ4HNA4W.cjs.map +0 -1
  222. package/dist/tsup/chunk-Y5NSCZA2.cjs.map +0 -1
  223. package/dist/tsup/context-B_IWbWne.d.ts +0 -92
  224. package/dist/tsup/context-CUrQ9MHc.d.cts +0 -92
  225. package/src/utils/serve.ts +0 -217
  226. /package/dist/tsup/{chunk-H37XQU3I.js.map → chunk-JBUZRPY5.js.map} +0 -0
@@ -2,8 +2,10 @@ import { VirtualWebSocket } from "@rivetkit/virtual-websocket";
2
2
  import {
3
3
  ACTOR_CONTEXT_INTERNAL_SYMBOL,
4
4
  CONN_STATE_MANAGER_SYMBOL,
5
+ disposeRunInspector,
5
6
  getRunFunction,
6
7
  getRunInspectorConfig,
8
+ RAW_STATE_SYMBOL,
7
9
  type WorkflowInspectorConfig,
8
10
  } from "@/actor/config";
9
11
  import type { AnyActorDefinition } from "@/actor/definition";
@@ -12,6 +14,7 @@ import {
12
14
  encodeBridgeRivetError,
13
15
  forbiddenError,
14
16
  INTERNAL_ERROR_CODE,
17
+ isActorAbortedError,
15
18
  isRivetErrorLike,
16
19
  RivetError,
17
20
  type RivetErrorLike,
@@ -32,6 +35,7 @@ import { convertRegistryConfigToClientConfig } from "@/client/config";
32
35
  import { HEADER_CONN_PARAMS } from "@/common/actor-router-consts";
33
36
  import type { AnyDatabaseProvider } from "@/common/database/config";
34
37
  import { wrapJsNativeDatabase } from "@/common/database/native-database";
38
+ import { assertJsonCompatValue, type JsonCompatValue } from "@/common/encoding";
35
39
  import { decodeWorkflowHistoryTransport } from "@/common/inspector-transport";
36
40
  import { deconstructError, stringifyError } from "@/common/utils";
37
41
  import type {
@@ -47,12 +51,13 @@ import type {
47
51
  RuntimeKind,
48
52
  SqliteBackend,
49
53
  } from "@/registry/config";
50
- import {
51
- decodeCborCompat,
52
- decodeCborJsonCompat,
53
- encodeCborCompat,
54
- } from "@/serde";
54
+ import { decodeCborCompat, encodeCborCompat } from "@/serde";
55
55
  import { getEnvUniversal, VERSION } from "@/utils";
56
+ import {
57
+ getNodeFsSync,
58
+ getNodePath,
59
+ importNodeDependencies,
60
+ } from "@/utils/node";
56
61
  import { logger } from "./log";
57
62
  import { loadNapiRuntime } from "./napi-runtime";
58
63
  import {
@@ -73,15 +78,18 @@ import type {
73
78
  RuntimeActorConfig,
74
79
  RuntimeBytes,
75
80
  RuntimeHttpResponse,
81
+ RuntimeInspectorTabEntry,
76
82
  RuntimeQueueMessage,
77
83
  RuntimeServeConfig,
78
84
  RuntimeStateDeltaPayload,
79
85
  WebSocketHandle,
80
86
  } from "./runtime";
81
87
  import { loadWasmRuntime } from "./wasm-runtime";
88
+ import { createWriteThroughProxy } from "./write-through-proxy";
82
89
 
83
90
  const textEncoder = new TextEncoder();
84
91
  const textDecoder = new TextDecoder();
92
+
85
93
  type ResolvedRuntimeKind = Exclude<RuntimeKind, "auto">;
86
94
  type RuntimeHostKind = "node-like" | "edge-like";
87
95
  export type RuntimeLoaders = {
@@ -91,6 +99,7 @@ export type RuntimeLoaders = {
91
99
  ) => ReturnType<typeof loadWasmRuntime>;
92
100
  detectHost: () => RuntimeHostKind;
93
101
  };
102
+
94
103
  type SerializeStateReason = "save" | "inspector";
95
104
  type NativeOnStateChangeHandler = (
96
105
  ctx: ActorContextHandleAdapter,
@@ -345,6 +354,15 @@ function databaseNotConfiguredError(): RivetError {
345
354
  );
346
355
  }
347
356
 
357
+ function databaseClientNotReadyError(): RivetError {
358
+ return new RivetError(
359
+ "actor",
360
+ "database_client_not_ready",
361
+ "actor database client was not initialized before user code ran. this is an internal lifecycle error; the migration callback should have pre-warmed the client. file an issue if you can reproduce.",
362
+ { public: true },
363
+ );
364
+ }
365
+
348
366
  function stateNotEnabledError(): RivetError {
349
367
  return new RivetError(
350
368
  "actor",
@@ -413,8 +431,33 @@ function clearNativeRuntimeState(
413
431
  async function cleanupNativeSleepRuntimeState(
414
432
  runtime: CoreRuntime,
415
433
  ctx: ActorContextHandle,
434
+ afterTrackedWorkDrained?: () => Promise<void>,
416
435
  ): Promise<void> {
417
- await runtime.actorWaitForTrackedShutdownWork(ctx);
436
+ // The bounded wait gives shutdown work one grace-period chance to finish.
437
+ // Drained means all tracked shutdown work completed before the deadline, so
438
+ // we can save final state and clear runtime state immediately. If it did not
439
+ // drain, close database handles now, then defer the final save and clear until
440
+ // the tracked work finishes without a deadline.
441
+ const drained = await runtime.actorWaitForTrackedShutdownWork(ctx);
442
+ if (!drained) {
443
+ await closeNativeDatabaseClient(runtime, ctx);
444
+ await closeNativeSqlDatabase(runtime, ctx);
445
+ void runtime
446
+ .actorWaitForTrackedShutdownWorkUnbounded(ctx)
447
+ .then(async () => {
448
+ await afterTrackedWorkDrained?.();
449
+ clearNativeRuntimeState(runtime, ctx);
450
+ })
451
+ .catch((error) => {
452
+ logger().warn({
453
+ msg: "deferred native sleep cleanup failed",
454
+ error: stringifyError(error),
455
+ });
456
+ });
457
+ return;
458
+ }
459
+
460
+ await afterTrackedWorkDrained?.();
418
461
  await closeNativeDatabaseClient(runtime, ctx);
419
462
  await closeNativeSqlDatabase(runtime, ctx);
420
463
  clearNativeRuntimeState(runtime, ctx);
@@ -587,11 +630,19 @@ function decodeValue<T>(value?: RuntimeBytes | null): T {
587
630
  return undefined as T;
588
631
  }
589
632
 
590
- return decodeCborJsonCompat(value);
633
+ return decodeCborCompat(value);
591
634
  }
592
635
 
593
636
  function encodeValue(value: unknown): RuntimeBytes {
594
- return encodeCborCompat(value);
637
+ return encodeCborCompat(value as JsonCompatValue);
638
+ }
639
+
640
+ function normalizeArgs(value: unknown): unknown[] {
641
+ return Array.isArray(value)
642
+ ? value
643
+ : value === undefined || value === null
644
+ ? []
645
+ : [value];
595
646
  }
596
647
 
597
648
  function unwrapTsfnPayload<T>(error: unknown, payload: T): T {
@@ -924,6 +975,7 @@ function serializeWorkflowEntryKind(
924
975
  }
925
976
  }
926
977
 
978
+ // TODO: Switch inspector routes to CBOR encoding
927
979
  function serializeWorkflowHistoryForJson(data: ArrayBuffer | null): {
928
980
  nameRegistry: string[];
929
981
  entries: Array<{
@@ -957,7 +1009,7 @@ function serializeWorkflowHistoryForJson(data: ArrayBuffer | null): {
957
1009
 
958
1010
  const history = decodeWorkflowHistoryTransport(data);
959
1011
 
960
- return {
1012
+ return jsonSafe({
961
1013
  nameRegistry: [...history.nameRegistry],
962
1014
  entries: history.entries.map((entry) => ({
963
1015
  id: entry.id,
@@ -987,7 +1039,7 @@ function serializeWorkflowHistoryForJson(data: ArrayBuffer | null): {
987
1039
  ],
988
1040
  ),
989
1041
  ),
990
- };
1042
+ });
991
1043
  }
992
1044
 
993
1045
  function toHttpJsonCompatible<T>(value: T): T {
@@ -1059,60 +1111,7 @@ function wrapNativeCallback<Args extends Array<unknown>, Result>(
1059
1111
 
1060
1112
  function decodeArgs(value?: RuntimeBytes | null): unknown[] {
1061
1113
  const decoded = decodeValue<unknown>(value);
1062
- return Array.isArray(decoded)
1063
- ? decoded
1064
- : decoded === undefined
1065
- ? []
1066
- : [decoded];
1067
- }
1068
-
1069
- function createWriteThroughProxy<T>(
1070
- value: T,
1071
- commit: (next: T) => void,
1072
- beforeChange?: () => void,
1073
- ): T {
1074
- if (!value || typeof value !== "object") {
1075
- return value;
1076
- }
1077
-
1078
- const proxies = new WeakMap<object, object>();
1079
- const wrap = (target: object): object => {
1080
- const cached = proxies.get(target);
1081
- if (cached) {
1082
- return cached;
1083
- }
1084
-
1085
- const proxy = new Proxy(target, {
1086
- get(innerTarget, property, receiver) {
1087
- const result = Reflect.get(innerTarget, property, receiver);
1088
- return result && typeof result === "object"
1089
- ? wrap(result as object)
1090
- : result;
1091
- },
1092
- set(innerTarget, property, nextValue, receiver) {
1093
- beforeChange?.();
1094
- const updated = Reflect.set(
1095
- innerTarget,
1096
- property,
1097
- nextValue,
1098
- receiver,
1099
- );
1100
- commit(value);
1101
- return updated;
1102
- },
1103
- deleteProperty(innerTarget, property) {
1104
- beforeChange?.();
1105
- const updated = Reflect.deleteProperty(innerTarget, property);
1106
- commit(value);
1107
- return updated;
1108
- },
1109
- });
1110
-
1111
- proxies.set(target, proxy);
1112
- return proxy;
1113
- };
1114
-
1115
- return wrap(value as object) as T;
1114
+ return normalizeArgs(decoded);
1116
1115
  }
1117
1116
 
1118
1117
  function buildRequest(init: {
@@ -1204,14 +1203,25 @@ class NativeConnAdapter {
1204
1203
  );
1205
1204
  }
1206
1205
 
1206
+ [RAW_STATE_SYMBOL](): unknown {
1207
+ return this.#readState();
1208
+ }
1209
+
1207
1210
  get state(): unknown {
1208
1211
  const nextState = this.#readState();
1209
- return createWriteThroughProxy(nextState, (nextValue) => {
1210
- this.#writeState(nextValue, { writeNative: true });
1211
- });
1212
+ return createWriteThroughProxy(
1213
+ nextState,
1214
+ (nextValue) => {
1215
+ this.#writeState(nextValue, { writeNative: true });
1216
+ },
1217
+ (newValue) => {
1218
+ assertJsonCompatValue(newValue);
1219
+ },
1220
+ );
1212
1221
  }
1213
1222
 
1214
1223
  set state(value: unknown) {
1224
+ assertJsonCompatValue(value);
1215
1225
  this.#writeState(value, { writeNative: true });
1216
1226
  }
1217
1227
 
@@ -1683,7 +1693,12 @@ class NativeQueueAdapter {
1683
1693
  signal?: AbortSignal;
1684
1694
  },
1685
1695
  ) {
1686
- if (!options?.signal) {
1696
+ const { token, cleanup } = await createCancellationTokenHandle(
1697
+ this.#runtime,
1698
+ options?.signal,
1699
+ );
1700
+
1701
+ try {
1687
1702
  await callNative(() =>
1688
1703
  this.#runtime.actorQueueWaitForNamesAvailable(
1689
1704
  this.#ctx,
@@ -1691,57 +1706,11 @@ class NativeQueueAdapter {
1691
1706
  {
1692
1707
  timeoutMs: options?.timeout,
1693
1708
  },
1709
+ token,
1694
1710
  ),
1695
1711
  );
1696
- return;
1697
- }
1698
-
1699
- const deadline =
1700
- options.timeout === undefined
1701
- ? undefined
1702
- : Date.now() + options.timeout;
1703
-
1704
- for (;;) {
1705
- if (options.signal.aborted) {
1706
- throw actorAbortedError();
1707
- }
1708
-
1709
- const remainingTimeout =
1710
- deadline === undefined
1711
- ? undefined
1712
- : Math.max(0, deadline - Date.now());
1713
- const sliceTimeout =
1714
- remainingTimeout === undefined
1715
- ? 100
1716
- : Math.min(remainingTimeout, 100);
1717
-
1718
- try {
1719
- await callNative(() =>
1720
- this.#runtime.actorQueueWaitForNamesAvailable(
1721
- this.#ctx,
1722
- [...names],
1723
- {
1724
- timeoutMs: sliceTimeout,
1725
- },
1726
- ),
1727
- );
1728
- return;
1729
- } catch (error) {
1730
- if (
1731
- (error as { group?: string; code?: string }).group ===
1732
- "queue" &&
1733
- (error as { group?: string; code?: string }).code ===
1734
- "timed_out"
1735
- ) {
1736
- if (
1737
- remainingTimeout === undefined ||
1738
- remainingTimeout > 100
1739
- ) {
1740
- continue;
1741
- }
1742
- }
1743
- throw error;
1744
- }
1712
+ } finally {
1713
+ cleanup?.();
1745
1714
  }
1746
1715
  }
1747
1716
 
@@ -1844,11 +1813,7 @@ class NativeQueueAdapter {
1844
1813
  }
1845
1814
  yield message;
1846
1815
  } catch (error) {
1847
- if (
1848
- isRivetErrorLike(error) &&
1849
- error.group === "actor" &&
1850
- error.code === "aborted"
1851
- ) {
1816
+ if (isActorAbortedError(error)) {
1852
1817
  return;
1853
1818
  }
1854
1819
  throw error;
@@ -2323,6 +2288,104 @@ class TrackedWebSocketHandleAdapter implements UniversalWebSocket {
2323
2288
  }
2324
2289
  }
2325
2290
 
2291
+ class NativeConnectionMap implements ReadonlyMap<string, NativeConnAdapter> {
2292
+ #runtime: CoreRuntime;
2293
+ #ctx: ActorContextHandle;
2294
+ #schemas: NativeValidationConfig;
2295
+
2296
+ constructor(
2297
+ runtime: CoreRuntime,
2298
+ ctx: ActorContextHandle,
2299
+ schemas: NativeValidationConfig,
2300
+ ) {
2301
+ this.#runtime = runtime;
2302
+ this.#ctx = ctx;
2303
+ this.#schemas = schemas;
2304
+ }
2305
+
2306
+ #connToAdapter(conn: ConnHandle): NativeConnAdapter {
2307
+ return new NativeConnAdapter(
2308
+ this.#runtime,
2309
+ conn,
2310
+ this.#schemas,
2311
+ this.#ctx,
2312
+ (connId) =>
2313
+ callNativeSync(() =>
2314
+ this.#runtime.actorQueueHibernationRemoval(
2315
+ this.#ctx,
2316
+ connId,
2317
+ ),
2318
+ ),
2319
+ );
2320
+ }
2321
+
2322
+ get size(): number {
2323
+ return callNativeSync(() => this.#runtime.actorConns(this.#ctx)).length;
2324
+ }
2325
+
2326
+ get(key: string): NativeConnAdapter | undefined {
2327
+ const conns = callNativeSync(() => this.#runtime.actorConns(this.#ctx));
2328
+ const conn = conns.find((c) => this.#runtime.connId(c) === key);
2329
+ if (!conn) return undefined;
2330
+ return this.#connToAdapter(conn);
2331
+ }
2332
+
2333
+ has(key: string): boolean {
2334
+ const conns = callNativeSync(() => this.#runtime.actorConns(this.#ctx));
2335
+ return conns.some((c) => this.#runtime.connId(c) === key);
2336
+ }
2337
+
2338
+ keys(): MapIterator<string> {
2339
+ const conns = callNativeSync(() => this.#runtime.actorConns(this.#ctx));
2340
+ return conns
2341
+ .map((c) => this.#runtime.connId(c))
2342
+ [Symbol.iterator]() satisfies MapIterator<string>;
2343
+ }
2344
+
2345
+ values(): MapIterator<NativeConnAdapter> {
2346
+ const conns = callNativeSync(() => this.#runtime.actorConns(this.#ctx));
2347
+ return conns
2348
+ .map((c) => this.#connToAdapter(c))
2349
+ [Symbol.iterator]() satisfies MapIterator<NativeConnAdapter>;
2350
+ }
2351
+
2352
+ entries(): MapIterator<[string, NativeConnAdapter]> {
2353
+ const conns = callNativeSync(() => this.#runtime.actorConns(this.#ctx));
2354
+ return conns
2355
+ .map(
2356
+ (c) =>
2357
+ [this.#runtime.connId(c), this.#connToAdapter(c)] as [
2358
+ string,
2359
+ NativeConnAdapter,
2360
+ ],
2361
+ )
2362
+ [Symbol.iterator]() satisfies MapIterator<
2363
+ [string, NativeConnAdapter]
2364
+ >;
2365
+ }
2366
+
2367
+ forEach(
2368
+ callback: (
2369
+ value: NativeConnAdapter,
2370
+ key: string,
2371
+ map: ReadonlyMap<string, NativeConnAdapter>,
2372
+ ) => void,
2373
+ thisArg?: unknown,
2374
+ ): void {
2375
+ const conns = callNativeSync(() => this.#runtime.actorConns(this.#ctx));
2376
+ for (const conn of conns) {
2377
+ const id = this.#runtime.connId(conn);
2378
+ callback.call(thisArg, this.#connToAdapter(conn), id, this);
2379
+ }
2380
+ }
2381
+
2382
+ [Symbol.iterator](): MapIterator<[string, NativeConnAdapter]> {
2383
+ return this.entries();
2384
+ }
2385
+
2386
+ readonly [Symbol.toStringTag] = "NativeConnectionMap";
2387
+ }
2388
+
2326
2389
  export class ActorContextHandleAdapter {
2327
2390
  #runtime: CoreRuntime;
2328
2391
  #ctx: ActorContextHandle;
@@ -2331,9 +2394,9 @@ export class ActorContextHandleAdapter {
2331
2394
  #abortSignalCleanup?: () => void;
2332
2395
  #client?: AnyClient;
2333
2396
  #clientFactory?: () => AnyClient;
2397
+ #connMap?: NativeConnectionMap;
2334
2398
  #databaseProvider?: Exclude<AnyDatabaseProvider, undefined>;
2335
2399
  #db?: unknown;
2336
- #dbProxy?: unknown;
2337
2400
  #dispatchCancelToken?: CancellationTokenHandle;
2338
2401
  #kv?: NativeKvAdapter;
2339
2402
  #queue?: NativeQueueAdapter;
@@ -2396,32 +2459,25 @@ export class ActorContextHandleAdapter {
2396
2459
  throw databaseNotConfiguredError();
2397
2460
  }
2398
2461
 
2399
- if (!this.#dbProxy) {
2400
- this.#dbProxy = new Proxy(
2401
- {},
2402
- {
2403
- get: (_target, property) => {
2404
- if (property === "then") {
2405
- return undefined;
2406
- }
2462
+ if (this.#db) {
2463
+ return this.#db;
2464
+ }
2407
2465
 
2408
- return async (...args: Array<unknown>) => {
2409
- const client = await this.ensureDatabaseClient();
2410
- const value = Reflect.get(
2411
- client as object,
2412
- property,
2413
- );
2414
- if (typeof value !== "function") {
2415
- return value;
2416
- }
2417
- return await value.apply(client, args);
2418
- };
2419
- },
2420
- },
2421
- );
2466
+ const runtimeState = getNativeRuntimeState(this.#runtime, this.#ctx);
2467
+ const cachedClient = runtimeState.databaseClient;
2468
+ if (cachedClient) {
2469
+ this.#db = cachedClient.client;
2470
+ return this.#db;
2422
2471
  }
2423
2472
 
2424
- return this.#dbProxy;
2473
+ throw databaseClientNotReadyError();
2474
+ }
2475
+
2476
+ [RAW_STATE_SYMBOL](): unknown {
2477
+ if (!this.#stateEnabled) {
2478
+ throw stateNotEnabledError();
2479
+ }
2480
+ return this.#readState();
2425
2481
  }
2426
2482
 
2427
2483
  get state(): unknown {
@@ -2444,8 +2500,9 @@ export class ActorContextHandleAdapter {
2444
2500
  (nextValue) => {
2445
2501
  this.#writeState(nextValue, { scheduleSave: true });
2446
2502
  },
2447
- () => {
2503
+ (newValue) => {
2448
2504
  this.#assertCanMutateState();
2505
+ assertJsonCompatValue(newValue);
2449
2506
  },
2450
2507
  );
2451
2508
  }
@@ -2457,6 +2514,7 @@ export class ActorContextHandleAdapter {
2457
2514
  throw stateNotEnabledError();
2458
2515
  }
2459
2516
  this.#assertCanMutateState();
2517
+ assertJsonCompatValue(value);
2460
2518
  this.#writeState(value, { scheduleSave: true });
2461
2519
  }
2462
2520
 
@@ -2523,27 +2581,15 @@ export class ActorContextHandleAdapter {
2523
2581
  return callNativeSync(() => this.#runtime.actorRegion(this.#ctx));
2524
2582
  }
2525
2583
 
2526
- get conns(): Map<string, NativeConnAdapter> {
2527
- return new Map(
2528
- callNativeSync(() => this.#runtime.actorConns(this.#ctx)).map(
2529
- (conn) => [
2530
- this.#runtime.connId(conn),
2531
- new NativeConnAdapter(
2532
- this.#runtime,
2533
- conn,
2534
- this.#schemas,
2535
- this.#ctx,
2536
- (connId) =>
2537
- callNativeSync(() =>
2538
- this.#runtime.actorQueueHibernationRemoval(
2539
- this.#ctx,
2540
- connId,
2541
- ),
2542
- ),
2543
- ),
2544
- ],
2545
- ),
2546
- );
2584
+ get conns(): ReadonlyMap<string, NativeConnAdapter> {
2585
+ if (!this.#connMap) {
2586
+ this.#connMap = new NativeConnectionMap(
2587
+ this.#runtime,
2588
+ this.#ctx,
2589
+ this.#schemas,
2590
+ );
2591
+ }
2592
+ return this.#connMap;
2547
2593
  }
2548
2594
 
2549
2595
  get log() {
@@ -2800,7 +2846,15 @@ export class ActorContextHandleAdapter {
2800
2846
 
2801
2847
  internalKeepAwake<T>(run: Promise<T> | (() => Promise<T>)): Promise<T> {
2802
2848
  const promise = typeof run === "function" ? run() : run;
2803
- const trackedPromise = promise.then(() => null);
2849
+ // Track only completion, swallowing the outcome. The real result/error
2850
+ // is delivered through the returned `promise`; without a rejection
2851
+ // handler here every workflow yield (which rejects with SleepError)
2852
+ // would be funneled into the registered task and logged as a spurious
2853
+ // "keep_awake promise rejected" warning.
2854
+ const trackedPromise = promise.then(
2855
+ () => null,
2856
+ () => null,
2857
+ );
2804
2858
  try {
2805
2859
  callNativeSync(() =>
2806
2860
  this.#runtime.actorRegisterTask(this.#ctx, trackedPromise),
@@ -2862,6 +2916,7 @@ export class ActorContextHandleAdapter {
2862
2916
  }
2863
2917
 
2864
2918
  sleep(): void {
2919
+ this.#flushStateChange();
2865
2920
  callNativeSync(() => this.#runtime.actorSleep(this.#ctx));
2866
2921
  }
2867
2922
 
@@ -3232,6 +3287,7 @@ function withConnContext(
3232
3287
  function buildActorConfig(
3233
3288
  definition: AnyActorDefinition,
3234
3289
  registryConfig: RegistryConfig,
3290
+ runtimeKind: "napi" | "wasm",
3235
3291
  ): RuntimeActorConfig {
3236
3292
  const config = definition.config as unknown as Record<string, unknown>;
3237
3293
  const options = (config.options ?? {}) as Record<string, unknown>;
@@ -3286,9 +3342,100 @@ function buildActorConfig(
3286
3342
  actions: Object.keys((config.actions ?? {}) as Record<string, unknown>)
3287
3343
  .sort()
3288
3344
  .map((name) => ({ name })),
3345
+ inspectorTabs: buildInspectorTabs(config.inspector, runtimeKind),
3289
3346
  };
3290
3347
  }
3291
3348
 
3349
+ function buildInspectorTabs(
3350
+ inspector: unknown,
3351
+ runtimeKind: "napi" | "wasm",
3352
+ ): Array<RuntimeInspectorTabEntry> | undefined {
3353
+ if (!inspector || typeof inspector !== "object") return undefined;
3354
+ const tabs = (inspector as { tabs?: unknown }).tabs;
3355
+ if (!Array.isArray(tabs) || tabs.length === 0) return undefined;
3356
+ return tabs.map((raw) => {
3357
+ const entry = raw as {
3358
+ id: string;
3359
+ label?: string;
3360
+ source?: string;
3361
+ icon?: string;
3362
+ hidden?: boolean;
3363
+ };
3364
+ if (entry.hidden === true) {
3365
+ return { id: entry.id, hidden: true };
3366
+ }
3367
+
3368
+ if (runtimeKind === "wasm") {
3369
+ if (entry.source !== undefined) {
3370
+ logger().warn(
3371
+ {
3372
+ tabId: entry.id,
3373
+ runtimeKind,
3374
+ },
3375
+ "inspector.tabs[].source is not supported on wasm runners (current host: wasm). Tab descriptors will still appear in the dashboard strip but the tab body will render a not-available placeholder.",
3376
+ );
3377
+ }
3378
+ return {
3379
+ id: entry.id,
3380
+ label: entry.label,
3381
+ icon: entry.icon,
3382
+ source: undefined,
3383
+ };
3384
+ }
3385
+
3386
+ const resolved =
3387
+ entry.source !== undefined
3388
+ ? getNodePath().resolve(entry.source)
3389
+ : undefined;
3390
+ if (resolved !== undefined) {
3391
+ validateInspectorTabSource(entry.id, resolved);
3392
+ }
3393
+ return {
3394
+ id: entry.id,
3395
+ label: entry.label,
3396
+ icon: entry.icon,
3397
+ source: resolved,
3398
+ };
3399
+ });
3400
+ }
3401
+
3402
+ function validateInspectorTabSource(tabId: string, resolved: string): void {
3403
+ if (resolved === getNodePath().parse(resolved).root) {
3404
+ throw new Error(
3405
+ `inspector.tabs[id="${tabId}"].source resolves to the filesystem root (${resolved}). ` +
3406
+ "Point it at the tab's own static-asset directory instead.",
3407
+ );
3408
+ }
3409
+ let stat: import("node:fs").Stats;
3410
+ try {
3411
+ stat = getNodeFsSync().statSync(resolved);
3412
+ } catch (err) {
3413
+ const code = (err as NodeJS.ErrnoException)?.code;
3414
+ if (code === "ENOENT") {
3415
+ throw new Error(
3416
+ `inspector.tabs[id="${tabId}"].source (${resolved}) does not exist.`,
3417
+ );
3418
+ }
3419
+ if (code === "EACCES") {
3420
+ throw new Error(
3421
+ `inspector.tabs[id="${tabId}"].source (${resolved}) is not readable (EACCES).`,
3422
+ );
3423
+ }
3424
+ throw new Error(
3425
+ `inspector.tabs[id="${tabId}"].source (${resolved}) could not be stat'd: ${
3426
+ (err as Error)?.message ?? err
3427
+ }`,
3428
+ );
3429
+ }
3430
+ if (!stat.isDirectory()) {
3431
+ throw new Error(
3432
+ `inspector.tabs[id="${tabId}"].source (${resolved}) must be a directory, got ${
3433
+ stat.isFile() ? "file" : "non-directory"
3434
+ }.`,
3435
+ );
3436
+ }
3437
+ }
3438
+
3292
3439
  export function buildNativeFactory(
3293
3440
  runtime: CoreRuntime,
3294
3441
  registryConfig: RegistryConfig,
@@ -3420,15 +3567,23 @@ export function buildNativeFactory(
3420
3567
  },
3421
3568
  );
3422
3569
  };
3423
- try {
3424
- await runtime.actorVerifyInspectorAuth(
3425
- ctx,
3426
- jsRequest.headers
3427
- .get("authorization")
3428
- ?.replace(/^Bearer\s+/i, "") ?? null,
3429
- );
3430
- } catch (error) {
3431
- return errorResponse(error, 401);
3570
+
3571
+ const isPublicPerActorPath =
3572
+ jsRequest.method === "GET" &&
3573
+ (url.pathname === "/inspector/tab-config" ||
3574
+ url.pathname.startsWith("/inspector/custom-tabs/"));
3575
+
3576
+ if (!isPublicPerActorPath) {
3577
+ try {
3578
+ await runtime.actorVerifyInspectorAuth(
3579
+ ctx,
3580
+ jsRequest.headers
3581
+ .get("authorization")
3582
+ ?.replace(/^Bearer\s+/i, "") ?? null,
3583
+ );
3584
+ } catch (error) {
3585
+ return errorResponse(error, 401);
3586
+ }
3432
3587
  }
3433
3588
 
3434
3589
  const workflowHistory = () =>
@@ -3548,6 +3703,10 @@ export function buildNativeFactory(
3548
3703
  getNativeWorkflowInspector(ctx) !== undefined,
3549
3704
  });
3550
3705
  } catch (error) {
3706
+ logger().error({
3707
+ msg: "error replaying workflow history",
3708
+ error,
3709
+ });
3551
3710
  return errorResponse(error);
3552
3711
  }
3553
3712
  }
@@ -3715,18 +3874,46 @@ export function buildNativeFactory(
3715
3874
  404,
3716
3875
  );
3717
3876
  }
3718
- const body = (await jsRequest.json()) as { args?: unknown[] };
3877
+ const body = (await jsRequest.json()) as {
3878
+ args?: unknown;
3879
+ properties?: unknown;
3880
+ };
3881
+ if (body.args !== undefined && body.properties !== undefined) {
3882
+ return jsonResponse(
3883
+ { error: "use either args or properties, not both" },
3884
+ { status: 400 },
3885
+ );
3886
+ }
3887
+ if (
3888
+ body.properties !== undefined &&
3889
+ (body.properties === null ||
3890
+ typeof body.properties !== "object" ||
3891
+ Array.isArray(body.properties))
3892
+ ) {
3893
+ return jsonResponse(
3894
+ { error: "properties must be an object" },
3895
+ { status: 400 },
3896
+ );
3897
+ }
3898
+ const args =
3899
+ body.properties !== undefined
3900
+ ? [body.properties]
3901
+ : normalizeArgs(body.args);
3719
3902
  try {
3720
3903
  const output = await action(
3721
3904
  actorCtx,
3722
3905
  ...validateActionArgs(
3723
3906
  schemaConfig.actionInputSchemas,
3724
3907
  actionName,
3725
- body.args ?? [],
3908
+ args,
3726
3909
  ),
3727
3910
  );
3728
3911
  return jsonResponse({ output });
3729
3912
  } catch (error) {
3913
+ logger().error({
3914
+ msg: "Error handling inspector action request",
3915
+ error,
3916
+ });
3730
3917
  return errorResponse(error);
3731
3918
  }
3732
3919
  }
@@ -3741,6 +3928,10 @@ export function buildNativeFactory(
3741
3928
  { status: 404 },
3742
3929
  );
3743
3930
  } catch (error) {
3931
+ logger().error({
3932
+ msg: "Error handling inspector request",
3933
+ error,
3934
+ });
3744
3935
  return errorResponse(error);
3745
3936
  } finally {
3746
3937
  await actorCtx.dispose();
@@ -3913,26 +4104,35 @@ export function buildNativeFactory(
3913
4104
  async (error: unknown, payload: { ctx: ActorContextHandle }) => {
3914
4105
  const { ctx } = unwrapTsfnPayload(error, payload);
3915
4106
  const actorCtx = makeActorCtx(ctx);
4107
+ // TODO: Move this save hook into cleanupNativeSleepRuntimeState
4108
+ // so immediate and deferred sleep cleanup share one save-state
4109
+ // path instead of passing a callback through cleanup.
4110
+ const saveActorState = async () => {
4111
+ if (runtime.kind === "wasm") {
4112
+ // Wasm cannot use the native context save helper here because
4113
+ // the runtime owns the serialized state handoff.
4114
+ await runtime.actorSaveState(
4115
+ ctx,
4116
+ actorCtx.serializeForTick("save"),
4117
+ );
4118
+ } else {
4119
+ await actorCtx.saveState({
4120
+ immediate: true,
4121
+ });
4122
+ }
4123
+ };
3916
4124
  try {
3917
4125
  if (onSleep) {
3918
- try {
3919
- await onSleep(actorCtx);
3920
- } finally {
3921
- if (runtime.kind === "wasm") {
3922
- // Wasm cannot use the native context save helper here because
3923
- // the runtime owns the serialized state handoff.
3924
- await runtime.actorSaveState(
3925
- ctx,
3926
- actorCtx.serializeForTick("save"),
3927
- );
3928
- } else {
3929
- await actorCtx.saveState({ immediate: true });
3930
- }
3931
- }
4126
+ await onSleep(actorCtx);
3932
4127
  }
4128
+ await saveActorState();
3933
4129
  } finally {
3934
4130
  try {
3935
- await cleanupNativeSleepRuntimeState(runtime, ctx);
4131
+ await cleanupNativeSleepRuntimeState(
4132
+ runtime,
4133
+ ctx,
4134
+ saveActorState,
4135
+ );
3936
4136
  } finally {
3937
4137
  await actorCtx.dispose();
3938
4138
  }
@@ -3948,6 +4148,11 @@ export function buildNativeFactory(
3948
4148
  await config.onDestroy(actorCtx);
3949
4149
  }
3950
4150
  } finally {
4151
+ const actorId = callNativeSync(() => runtime.actorId(ctx));
4152
+ // Release actorId-keyed state so it does not accumulate per
4153
+ // destroyed actor.
4154
+ nativeRunHandlerActiveByActorId.delete(actorId);
4155
+ disposeRunInspector(config.run, actorId);
3951
4156
  resolveNativeDestroy(runtime, ctx);
3952
4157
  await actorCtx.closeDatabase();
3953
4158
  clearNativeRuntimeState(runtime, ctx);
@@ -4351,7 +4556,11 @@ export function buildNativeFactory(
4351
4556
  try {
4352
4557
  await run(actorCtx);
4353
4558
  } finally {
4354
- nativeRunHandlerActiveByActorId.set(actorId, false);
4559
+ // Delete rather than set(false): an absent entry already
4560
+ // reads as inactive, and deleting keeps this map bounded
4561
+ // to currently-running handlers instead of accumulating an
4562
+ // entry per actor id forever.
4563
+ nativeRunHandlerActiveByActorId.delete(actorId);
4355
4564
  await actorCtx.dispose();
4356
4565
  }
4357
4566
  },
@@ -4562,7 +4771,7 @@ export function buildNativeFactory(
4562
4771
 
4563
4772
  return runtime.createActorFactory(
4564
4773
  callbacks,
4565
- buildActorConfig(definition, registryConfig),
4774
+ buildActorConfig(definition, registryConfig, runtime.kind),
4566
4775
  );
4567
4776
  }
4568
4777
 
@@ -4589,10 +4798,29 @@ export async function buildServeConfig(
4589
4798
  serverlessMaxStartPayloadBytes: config.serverless.maxStartPayloadBytes,
4590
4799
  };
4591
4800
 
4592
- if (config.startEngine) {
4801
+ // Always best-effort resolve the npm-installed engine binary and hand its
4802
+ // path to the core. The core alone decides whether to actually spawn a local
4803
+ // engine (its `should_manage_engine`, based on the endpoint + spawn mode), so
4804
+ // JS must not duplicate that decision here. Only JS knows the npm
4805
+ // `node_modules` layout, so it resolves the path; if no binary is available
4806
+ // (remote-only install, unsupported platform, optional deps skipped), leave
4807
+ // it unset and let the core report `engine.binary_unavailable` if it actually
4808
+ // needs one.
4809
+ try {
4593
4810
  const { getEnginePath } = await loadEngineCli();
4594
4811
  serveConfig.engineBinaryPath = getEnginePath();
4812
+ } catch (error) {
4813
+ // The npm-installed engine binary could not be resolved. The core still
4814
+ // decides whether it needs to spawn a local engine; if it does, it will
4815
+ // fail with engine.binary_unavailable (auto-download is off in the napi
4816
+ // runtime). Warn so the cause is actionable.
4817
+ logger().warn({
4818
+ msg: "could not resolve a local engine binary; if a local engine must be spawned it will fail with engine.binary_unavailable — set RIVET_ENGINE_BINARY_PATH or install the @rivetkit/engine-cli platform package",
4819
+ error: stringifyError(error),
4820
+ });
4595
4821
  }
4822
+ serveConfig.engineHost = config.engineHost;
4823
+ serveConfig.enginePort = config.enginePort;
4596
4824
  if (config.test?.enabled) {
4597
4825
  serveConfig.inspectorTestToken =
4598
4826
  getEnvUniversal("_RIVET_TEST_INSPECTOR_TOKEN") ?? "token";
@@ -4616,6 +4844,13 @@ export async function buildRegistryWithRuntime(
4616
4844
  trySetProcessEnv("_RIVET_TEST_INSPECTOR_TOKEN", "token");
4617
4845
  }
4618
4846
 
4847
+ // Custom inspector tab `source` paths are resolved with node:path while
4848
+ // building actor configs below, so the Node modules must be loaded first.
4849
+ // Native (napi) runtime only; wasm has no filesystem.
4850
+ if (runtime.kind === "napi") {
4851
+ importNodeDependencies();
4852
+ }
4853
+
4619
4854
  const registry = runtime.createRegistry();
4620
4855
 
4621
4856
  for (const [name, definition] of Object.entries(config.use)) {