rivetkit 2.3.0-rc.9 → 2.3.0

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 (222) hide show
  1. package/dist/browser/client.d.ts +498 -62
  2. package/dist/browser/client.js +227 -171
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +50 -20
  5. package/dist/browser/inspector/client.js.map +1 -1
  6. package/dist/tsup/actor/errors.cjs +2 -2
  7. package/dist/tsup/actor/errors.d.cts +1 -1
  8. package/dist/tsup/actor/errors.d.ts +1 -1
  9. package/dist/tsup/actor/errors.js +1 -1
  10. package/dist/tsup/agent-os/index.cjs +2163 -2087
  11. package/dist/tsup/agent-os/index.cjs.map +1 -1
  12. package/dist/tsup/agent-os/index.d.cts +496 -69
  13. package/dist/tsup/agent-os/index.d.ts +496 -69
  14. package/dist/tsup/agent-os/index.js +2163 -2087
  15. package/dist/tsup/agent-os/index.js.map +1 -1
  16. package/dist/tsup/{chunk-W7EYSYVI.js → chunk-2OTRTA3J.js} +134 -20
  17. package/dist/tsup/chunk-2OTRTA3J.js.map +1 -0
  18. package/dist/tsup/{chunk-VJFRBJVQ.cjs → chunk-3677IIOV.cjs} +138 -24
  19. package/dist/tsup/chunk-3677IIOV.cjs.map +1 -0
  20. package/dist/tsup/{chunk-4CGA6QJO.cjs → chunk-47HHIEXH.cjs} +24 -9
  21. package/dist/tsup/chunk-47HHIEXH.cjs.map +1 -0
  22. package/dist/tsup/{chunk-F3Q5BFQ6.js → chunk-4JDSFJS5.js} +66 -79
  23. package/dist/tsup/chunk-4JDSFJS5.js.map +1 -0
  24. package/dist/tsup/{chunk-GVTOE34S.cjs → chunk-7QKCIVAY.cjs} +222 -235
  25. package/dist/tsup/chunk-7QKCIVAY.cjs.map +1 -0
  26. package/dist/tsup/{chunk-CPA4Y3RG.cjs → chunk-B6VUNZUD.cjs} +10 -10
  27. package/dist/tsup/chunk-B6VUNZUD.cjs.map +1 -0
  28. package/dist/tsup/{chunk-H37XQU3I.js → chunk-BEI24WTI.js} +2 -2
  29. package/dist/tsup/{chunk-KIWH5H3K.js → chunk-BRP62GZC.js} +3 -3
  30. package/dist/tsup/chunk-BRP62GZC.js.map +1 -0
  31. package/dist/tsup/{chunk-T6YVRM4K.js → chunk-DPIMKYNB.js} +63 -2
  32. package/dist/tsup/chunk-DPIMKYNB.js.map +1 -0
  33. package/dist/tsup/{chunk-Y5NSCZA2.cjs → chunk-DXXJPH55.cjs} +44 -15
  34. package/dist/tsup/chunk-DXXJPH55.cjs.map +1 -0
  35. package/dist/tsup/{chunk-3YY5S6TV.js → chunk-HXUEHHJF.js} +2 -2
  36. package/dist/tsup/chunk-HXUEHHJF.js.map +1 -0
  37. package/dist/tsup/{chunk-4WPEZBK4.cjs → chunk-I4LN3FNT.cjs} +10 -10
  38. package/dist/tsup/chunk-I4LN3FNT.cjs.map +1 -0
  39. package/dist/tsup/{chunk-PCBNKI2J.js → chunk-JZ7TWV65.js} +1 -1
  40. package/dist/tsup/chunk-JZ7TWV65.js.map +1 -0
  41. package/dist/tsup/{chunk-QAZLM4WT.cjs → chunk-KORQB2IR.cjs} +3 -3
  42. package/dist/tsup/{chunk-QAZLM4WT.cjs.map → chunk-KORQB2IR.cjs.map} +1 -1
  43. package/dist/tsup/{chunk-MALSPBAF.cjs → chunk-LVTBW2RE.cjs} +3 -3
  44. package/dist/tsup/{chunk-MALSPBAF.cjs.map → chunk-LVTBW2RE.cjs.map} +1 -1
  45. package/dist/tsup/{chunk-H7P7WR2Y.js → chunk-MEHBWPLJ.js} +6 -6
  46. package/dist/tsup/chunk-MEHBWPLJ.js.map +1 -0
  47. package/dist/tsup/{chunk-WQ4HNA4W.cjs → chunk-NIY3RSPX.cjs} +64 -3
  48. package/dist/tsup/chunk-NIY3RSPX.cjs.map +1 -0
  49. package/dist/tsup/{chunk-MMMEZM5J.js → chunk-P2GNQ4RN.js} +4 -4
  50. package/dist/tsup/chunk-P2GNQ4RN.js.map +1 -0
  51. package/dist/tsup/{chunk-KJTA3ATT.js → chunk-UMZVD6DQ.js} +22 -7
  52. package/dist/tsup/chunk-UMZVD6DQ.js.map +1 -0
  53. package/dist/tsup/{chunk-LD5YASJU.cjs → chunk-VE2X4KMG.cjs} +2 -2
  54. package/dist/tsup/{chunk-LD5YASJU.cjs.map → chunk-VE2X4KMG.cjs.map} +1 -1
  55. package/dist/tsup/{chunk-VRCIXJRN.js → chunk-VTTFNQQI.js} +36 -7
  56. package/dist/tsup/chunk-VTTFNQQI.js.map +1 -0
  57. package/dist/tsup/{chunk-2NDZ7JCR.cjs → chunk-ZA7FLHKH.cjs} +1 -1
  58. package/dist/tsup/chunk-ZA7FLHKH.cjs.map +1 -0
  59. package/dist/tsup/client/mod.cjs +9 -9
  60. package/dist/tsup/client/mod.d.cts +5 -5
  61. package/dist/tsup/client/mod.d.ts +5 -5
  62. package/dist/tsup/client/mod.js +8 -8
  63. package/dist/tsup/common/log.cjs +3 -3
  64. package/dist/tsup/common/log.js +2 -2
  65. package/dist/tsup/common/websocket.cjs +4 -4
  66. package/dist/tsup/common/websocket.js +3 -3
  67. package/dist/tsup/{config-0Ta55UV0.d.ts → config-BxWAw3iH.d.ts} +529 -23
  68. package/dist/tsup/{config-Ca8dN4cS.d.cts → config-CZQQ-mso.d.cts} +529 -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/{context-B_IWbWne.d.ts → context-Bw7xq8w3.d.cts} +8 -8
  72. package/dist/tsup/{context-CUrQ9MHc.d.cts → context-D8QA76sV.d.ts} +8 -8
  73. package/dist/tsup/db/drizzle.cjs +3 -3
  74. package/dist/tsup/db/drizzle.d.cts +1 -1
  75. package/dist/tsup/db/drizzle.d.ts +1 -1
  76. package/dist/tsup/db/drizzle.js +1 -1
  77. package/dist/tsup/db/mod.cjs +2 -2
  78. package/dist/tsup/db/mod.d.cts +2 -2
  79. package/dist/tsup/db/mod.d.ts +2 -2
  80. package/dist/tsup/db/mod.js +1 -1
  81. package/dist/tsup/dynamic/mod.cjs +24 -0
  82. package/dist/tsup/dynamic/mod.cjs.map +1 -0
  83. package/dist/tsup/dynamic/mod.d.cts +37 -0
  84. package/dist/tsup/dynamic/mod.d.ts +37 -0
  85. package/dist/tsup/dynamic/mod.js +24 -0
  86. package/dist/tsup/dynamic/mod.js.map +1 -0
  87. package/dist/tsup/inspector/mod.cjs +6 -6
  88. package/dist/tsup/inspector/mod.js +5 -5
  89. package/dist/tsup/inspector-tab/mod.cjs +173 -0
  90. package/dist/tsup/inspector-tab/mod.cjs.map +1 -0
  91. package/dist/tsup/inspector-tab/mod.d.cts +250 -0
  92. package/dist/tsup/inspector-tab/mod.d.ts +250 -0
  93. package/dist/tsup/inspector-tab/mod.js +173 -0
  94. package/dist/tsup/inspector-tab/mod.js.map +1 -0
  95. package/dist/tsup/mod.cjs +730 -336
  96. package/dist/tsup/mod.cjs.map +1 -1
  97. package/dist/tsup/mod.d.cts +5 -5
  98. package/dist/tsup/mod.d.ts +5 -5
  99. package/dist/tsup/mod.js +633 -239
  100. package/dist/tsup/mod.js.map +1 -1
  101. package/dist/tsup/test/mod.cjs +21 -18
  102. package/dist/tsup/test/mod.cjs.map +1 -1
  103. package/dist/tsup/test/mod.d.cts +4 -4
  104. package/dist/tsup/test/mod.d.ts +4 -4
  105. package/dist/tsup/test/mod.js +18 -15
  106. package/dist/tsup/test/mod.js.map +1 -1
  107. package/dist/tsup/{utils-DVekpm4I.d.cts → utils-DQosb24I.d.cts} +1 -1
  108. package/dist/tsup/{utils-DVekpm4I.d.ts → utils-DQosb24I.d.ts} +1 -1
  109. package/dist/tsup/utils.cjs +3 -3
  110. package/dist/tsup/utils.d.cts +1 -1
  111. package/dist/tsup/utils.d.ts +1 -1
  112. package/dist/tsup/utils.js +2 -2
  113. package/dist/tsup/workflow/mod.cjs +307 -282
  114. package/dist/tsup/workflow/mod.cjs.map +1 -1
  115. package/dist/tsup/workflow/mod.d.cts +6 -6
  116. package/dist/tsup/workflow/mod.d.ts +6 -6
  117. package/dist/tsup/workflow/mod.js +501 -476
  118. package/dist/tsup/workflow/mod.js.map +1 -1
  119. package/package.json +32 -11
  120. package/src/actor/config.ts +159 -51
  121. package/src/actor/contexts/index.ts +7 -2
  122. package/src/actor/definition.ts +17 -19
  123. package/src/actor/driver.ts +3 -3
  124. package/src/actor/errors.ts +9 -3
  125. package/src/actor/instance/mod.ts +26 -34
  126. package/src/actor/keys.ts +1 -1
  127. package/src/actor/mod.ts +22 -20
  128. package/src/actor/schema.ts +2 -2
  129. package/src/agent-os/actor/index.ts +38 -18
  130. package/src/agent-os/actor/preview.ts +1 -2
  131. package/src/agent-os/actor/session.ts +2 -2
  132. package/src/agent-os/config.ts +1 -1
  133. package/src/agent-os/fs/database-vfs.ts +1 -1
  134. package/src/agent-os/index.ts +16 -15
  135. package/src/client/actor-common.ts +87 -54
  136. package/src/client/actor-conn.ts +8 -36
  137. package/src/client/actor-handle.ts +69 -51
  138. package/src/client/actor-query.ts +5 -5
  139. package/src/client/errors.ts +1 -1
  140. package/src/client/lifecycle-errors.ts +2 -4
  141. package/src/client/query.ts +1 -1
  142. package/src/client/queue.ts +8 -3
  143. package/src/client/raw-utils.ts +8 -6
  144. package/src/client/resolve-gateway-target.ts +1 -1
  145. package/src/client/utils.ts +2 -7
  146. package/src/common/actor-websocket.ts +3 -1
  147. package/src/common/bare/actor-persist/v1.ts +205 -163
  148. package/src/common/bare/actor-persist/v2.ts +265 -213
  149. package/src/common/bare/actor-persist/v3.ts +176 -172
  150. package/src/common/bare/actor-persist/v4.ts +254 -253
  151. package/src/common/bare/transport/v1.ts +659 -543
  152. package/src/common/client-protocol-versioned.ts +66 -64
  153. package/src/common/database/config.ts +2 -8
  154. package/src/common/database/native-database.ts +1 -1
  155. package/src/common/database/shared.ts +1 -0
  156. package/src/common/encoding.ts +250 -16
  157. package/src/common/engine.ts +28 -1
  158. package/src/common/eventsource.ts +1 -1
  159. package/src/common/inline-websocket-adapter.ts +14 -13
  160. package/src/common/log.ts +1 -0
  161. package/src/common/router.ts +13 -17
  162. package/src/common/utils.ts +1 -150
  163. package/src/common/websocket-interface.ts +1 -1
  164. package/src/db/mod.ts +1 -1
  165. package/src/devtools-loader/index.ts +4 -7
  166. package/src/devtools-loader/serve-devtools.ts +26 -0
  167. package/src/drivers/engine/actor-driver.ts +58 -56
  168. package/src/dynamic/instance.ts +32 -0
  169. package/src/dynamic/internal.ts +50 -0
  170. package/src/dynamic/isolate-runtime.ts +66 -0
  171. package/src/dynamic/mod.ts +32 -0
  172. package/src/engine-client/actor-http-client.ts +3 -3
  173. package/src/engine-client/actor-websocket-client.ts +6 -5
  174. package/src/engine-client/api-endpoints.ts +51 -2
  175. package/src/engine-client/api-utils.ts +2 -2
  176. package/src/engine-client/driver.ts +1 -1
  177. package/src/engine-client/mod.ts +6 -3
  178. package/src/engine-client/ws-proxy.ts +9 -4
  179. package/src/inspector/client.browser.ts +5 -11
  180. package/src/inspector/mod.ts +1 -3
  181. package/src/inspector-tab/mod.ts +315 -0
  182. package/src/registry/config/envoy.ts +1 -2
  183. package/src/registry/config/index.ts +40 -16
  184. package/src/registry/index.ts +154 -74
  185. package/src/registry/napi-runtime.ts +13 -2
  186. package/src/registry/native-validation.ts +10 -12
  187. package/src/registry/native.ts +367 -181
  188. package/src/registry/process-metrics.ts +250 -0
  189. package/src/registry/runtime.ts +41 -1
  190. package/src/registry/wasm-runtime.ts +18 -2
  191. package/src/registry/write-through-proxy.ts +40 -0
  192. package/src/serde.ts +2 -2
  193. package/src/serverless/configure.ts +18 -7
  194. package/src/test/mod.ts +11 -8
  195. package/src/utils/endpoint-parser.ts +1 -1
  196. package/src/utils/env-vars.ts +6 -0
  197. package/src/utils/router.ts +1 -1
  198. package/src/utils/serve.ts +4 -5
  199. package/src/utils.ts +1 -2
  200. package/src/workflow/context.ts +61 -33
  201. package/src/workflow/driver.ts +4 -6
  202. package/src/workflow/inspector.ts +4 -3
  203. package/src/workflow/mod.ts +15 -17
  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-T6YVRM4K.js.map +0 -1
  217. package/dist/tsup/chunk-VJFRBJVQ.cjs.map +0 -1
  218. package/dist/tsup/chunk-VRCIXJRN.js.map +0 -1
  219. package/dist/tsup/chunk-W7EYSYVI.js.map +0 -1
  220. package/dist/tsup/chunk-WQ4HNA4W.cjs.map +0 -1
  221. package/dist/tsup/chunk-Y5NSCZA2.cjs.map +0 -1
  222. /package/dist/tsup/{chunk-H37XQU3I.js.map → chunk-BEI24WTI.js.map} +0 -0
@@ -4,6 +4,7 @@ import {
4
4
  CONN_STATE_MANAGER_SYMBOL,
5
5
  getRunFunction,
6
6
  getRunInspectorConfig,
7
+ RAW_STATE_SYMBOL,
7
8
  type WorkflowInspectorConfig,
8
9
  } from "@/actor/config";
9
10
  import type { AnyActorDefinition } from "@/actor/definition";
@@ -32,6 +33,7 @@ import { convertRegistryConfigToClientConfig } from "@/client/config";
32
33
  import { HEADER_CONN_PARAMS } from "@/common/actor-router-consts";
33
34
  import type { AnyDatabaseProvider } from "@/common/database/config";
34
35
  import { wrapJsNativeDatabase } from "@/common/database/native-database";
36
+ import { assertJsonCompatValue, type JsonCompatValue } from "@/common/encoding";
35
37
  import { decodeWorkflowHistoryTransport } from "@/common/inspector-transport";
36
38
  import { deconstructError, stringifyError } from "@/common/utils";
37
39
  import type {
@@ -47,11 +49,7 @@ import type {
47
49
  RuntimeKind,
48
50
  SqliteBackend,
49
51
  } from "@/registry/config";
50
- import {
51
- decodeCborCompat,
52
- decodeCborJsonCompat,
53
- encodeCborCompat,
54
- } from "@/serde";
52
+ import { decodeCborCompat, encodeCborCompat } from "@/serde";
55
53
  import { getEnvUniversal, VERSION } from "@/utils";
56
54
  import { logger } from "./log";
57
55
  import { loadNapiRuntime } from "./napi-runtime";
@@ -73,12 +71,16 @@ import type {
73
71
  RuntimeActorConfig,
74
72
  RuntimeBytes,
75
73
  RuntimeHttpResponse,
74
+ RuntimeInspectorTabEntry,
76
75
  RuntimeQueueMessage,
77
76
  RuntimeServeConfig,
78
77
  RuntimeStateDeltaPayload,
79
78
  WebSocketHandle,
80
79
  } from "./runtime";
81
80
  import { loadWasmRuntime } from "./wasm-runtime";
81
+ import nodeFs from "node:fs";
82
+ import nodePath from "node:path";
83
+ import { createWriteThroughProxy } from "./write-through-proxy";
82
84
 
83
85
  const textEncoder = new TextEncoder();
84
86
  const textDecoder = new TextDecoder();
@@ -345,6 +347,15 @@ function databaseNotConfiguredError(): RivetError {
345
347
  );
346
348
  }
347
349
 
350
+ function databaseClientNotReadyError(): RivetError {
351
+ return new RivetError(
352
+ "actor",
353
+ "database_client_not_ready",
354
+ "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.",
355
+ { public: true },
356
+ );
357
+ }
358
+
348
359
  function stateNotEnabledError(): RivetError {
349
360
  return new RivetError(
350
361
  "actor",
@@ -413,8 +424,33 @@ function clearNativeRuntimeState(
413
424
  async function cleanupNativeSleepRuntimeState(
414
425
  runtime: CoreRuntime,
415
426
  ctx: ActorContextHandle,
427
+ afterTrackedWorkDrained?: () => Promise<void>,
416
428
  ): Promise<void> {
417
- await runtime.actorWaitForTrackedShutdownWork(ctx);
429
+ // The bounded wait gives shutdown work one grace-period chance to finish.
430
+ // Drained means all tracked shutdown work completed before the deadline, so
431
+ // we can save final state and clear runtime state immediately. If it did not
432
+ // drain, close database handles now, then defer the final save and clear until
433
+ // the tracked work finishes without a deadline.
434
+ const drained = await runtime.actorWaitForTrackedShutdownWork(ctx);
435
+ if (!drained) {
436
+ await closeNativeDatabaseClient(runtime, ctx);
437
+ await closeNativeSqlDatabase(runtime, ctx);
438
+ void runtime
439
+ .actorWaitForTrackedShutdownWorkUnbounded(ctx)
440
+ .then(async () => {
441
+ await afterTrackedWorkDrained?.();
442
+ clearNativeRuntimeState(runtime, ctx);
443
+ })
444
+ .catch((error) => {
445
+ logger().warn({
446
+ msg: "deferred native sleep cleanup failed",
447
+ error: stringifyError(error),
448
+ });
449
+ });
450
+ return;
451
+ }
452
+
453
+ await afterTrackedWorkDrained?.();
418
454
  await closeNativeDatabaseClient(runtime, ctx);
419
455
  await closeNativeSqlDatabase(runtime, ctx);
420
456
  clearNativeRuntimeState(runtime, ctx);
@@ -587,11 +623,19 @@ function decodeValue<T>(value?: RuntimeBytes | null): T {
587
623
  return undefined as T;
588
624
  }
589
625
 
590
- return decodeCborJsonCompat(value);
626
+ return decodeCborCompat(value);
591
627
  }
592
628
 
593
629
  function encodeValue(value: unknown): RuntimeBytes {
594
- return encodeCborCompat(value);
630
+ return encodeCborCompat(value as JsonCompatValue);
631
+ }
632
+
633
+ function normalizeArgs(value: unknown): unknown[] {
634
+ return Array.isArray(value)
635
+ ? value
636
+ : value === undefined || value === null
637
+ ? []
638
+ : [value];
595
639
  }
596
640
 
597
641
  function unwrapTsfnPayload<T>(error: unknown, payload: T): T {
@@ -924,6 +968,7 @@ function serializeWorkflowEntryKind(
924
968
  }
925
969
  }
926
970
 
971
+ // TODO: Switch inspector routes to CBOR encoding
927
972
  function serializeWorkflowHistoryForJson(data: ArrayBuffer | null): {
928
973
  nameRegistry: string[];
929
974
  entries: Array<{
@@ -957,7 +1002,7 @@ function serializeWorkflowHistoryForJson(data: ArrayBuffer | null): {
957
1002
 
958
1003
  const history = decodeWorkflowHistoryTransport(data);
959
1004
 
960
- return {
1005
+ return jsonSafe({
961
1006
  nameRegistry: [...history.nameRegistry],
962
1007
  entries: history.entries.map((entry) => ({
963
1008
  id: entry.id,
@@ -987,7 +1032,7 @@ function serializeWorkflowHistoryForJson(data: ArrayBuffer | null): {
987
1032
  ],
988
1033
  ),
989
1034
  ),
990
- };
1035
+ });
991
1036
  }
992
1037
 
993
1038
  function toHttpJsonCompatible<T>(value: T): T {
@@ -1059,60 +1104,7 @@ function wrapNativeCallback<Args extends Array<unknown>, Result>(
1059
1104
 
1060
1105
  function decodeArgs(value?: RuntimeBytes | null): unknown[] {
1061
1106
  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;
1107
+ return normalizeArgs(decoded);
1116
1108
  }
1117
1109
 
1118
1110
  function buildRequest(init: {
@@ -1204,14 +1196,25 @@ class NativeConnAdapter {
1204
1196
  );
1205
1197
  }
1206
1198
 
1199
+ [RAW_STATE_SYMBOL](): unknown {
1200
+ return this.#readState();
1201
+ }
1202
+
1207
1203
  get state(): unknown {
1208
1204
  const nextState = this.#readState();
1209
- return createWriteThroughProxy(nextState, (nextValue) => {
1210
- this.#writeState(nextValue, { writeNative: true });
1211
- });
1205
+ return createWriteThroughProxy(
1206
+ nextState,
1207
+ (nextValue) => {
1208
+ this.#writeState(nextValue, { writeNative: true });
1209
+ },
1210
+ (newValue) => {
1211
+ assertJsonCompatValue(newValue);
1212
+ },
1213
+ );
1212
1214
  }
1213
1215
 
1214
1216
  set state(value: unknown) {
1217
+ assertJsonCompatValue(value);
1215
1218
  this.#writeState(value, { writeNative: true });
1216
1219
  }
1217
1220
 
@@ -1683,7 +1686,12 @@ class NativeQueueAdapter {
1683
1686
  signal?: AbortSignal;
1684
1687
  },
1685
1688
  ) {
1686
- if (!options?.signal) {
1689
+ const { token, cleanup } = await createCancellationTokenHandle(
1690
+ this.#runtime,
1691
+ options?.signal,
1692
+ );
1693
+
1694
+ try {
1687
1695
  await callNative(() =>
1688
1696
  this.#runtime.actorQueueWaitForNamesAvailable(
1689
1697
  this.#ctx,
@@ -1691,57 +1699,11 @@ class NativeQueueAdapter {
1691
1699
  {
1692
1700
  timeoutMs: options?.timeout,
1693
1701
  },
1702
+ token,
1694
1703
  ),
1695
1704
  );
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
- }
1705
+ } finally {
1706
+ cleanup?.();
1745
1707
  }
1746
1708
  }
1747
1709
 
@@ -2323,6 +2285,104 @@ class TrackedWebSocketHandleAdapter implements UniversalWebSocket {
2323
2285
  }
2324
2286
  }
2325
2287
 
2288
+ class NativeConnectionMap implements ReadonlyMap<string, NativeConnAdapter> {
2289
+ #runtime: CoreRuntime;
2290
+ #ctx: ActorContextHandle;
2291
+ #schemas: NativeValidationConfig;
2292
+
2293
+ constructor(
2294
+ runtime: CoreRuntime,
2295
+ ctx: ActorContextHandle,
2296
+ schemas: NativeValidationConfig,
2297
+ ) {
2298
+ this.#runtime = runtime;
2299
+ this.#ctx = ctx;
2300
+ this.#schemas = schemas;
2301
+ }
2302
+
2303
+ #connToAdapter(conn: ConnHandle): NativeConnAdapter {
2304
+ return new NativeConnAdapter(
2305
+ this.#runtime,
2306
+ conn,
2307
+ this.#schemas,
2308
+ this.#ctx,
2309
+ (connId) =>
2310
+ callNativeSync(() =>
2311
+ this.#runtime.actorQueueHibernationRemoval(
2312
+ this.#ctx,
2313
+ connId,
2314
+ ),
2315
+ ),
2316
+ );
2317
+ }
2318
+
2319
+ get size(): number {
2320
+ return callNativeSync(() => this.#runtime.actorConns(this.#ctx)).length;
2321
+ }
2322
+
2323
+ get(key: string): NativeConnAdapter | undefined {
2324
+ const conns = callNativeSync(() => this.#runtime.actorConns(this.#ctx));
2325
+ const conn = conns.find((c) => this.#runtime.connId(c) === key);
2326
+ if (!conn) return undefined;
2327
+ return this.#connToAdapter(conn);
2328
+ }
2329
+
2330
+ has(key: string): boolean {
2331
+ const conns = callNativeSync(() => this.#runtime.actorConns(this.#ctx));
2332
+ return conns.some((c) => this.#runtime.connId(c) === key);
2333
+ }
2334
+
2335
+ keys(): MapIterator<string> {
2336
+ const conns = callNativeSync(() => this.#runtime.actorConns(this.#ctx));
2337
+ return conns
2338
+ .map((c) => this.#runtime.connId(c))
2339
+ [Symbol.iterator]() satisfies MapIterator<string>;
2340
+ }
2341
+
2342
+ values(): MapIterator<NativeConnAdapter> {
2343
+ const conns = callNativeSync(() => this.#runtime.actorConns(this.#ctx));
2344
+ return conns
2345
+ .map((c) => this.#connToAdapter(c))
2346
+ [Symbol.iterator]() satisfies MapIterator<NativeConnAdapter>;
2347
+ }
2348
+
2349
+ entries(): MapIterator<[string, NativeConnAdapter]> {
2350
+ const conns = callNativeSync(() => this.#runtime.actorConns(this.#ctx));
2351
+ return conns
2352
+ .map(
2353
+ (c) =>
2354
+ [this.#runtime.connId(c), this.#connToAdapter(c)] as [
2355
+ string,
2356
+ NativeConnAdapter,
2357
+ ],
2358
+ )
2359
+ [Symbol.iterator]() satisfies MapIterator<
2360
+ [string, NativeConnAdapter]
2361
+ >;
2362
+ }
2363
+
2364
+ forEach(
2365
+ callback: (
2366
+ value: NativeConnAdapter,
2367
+ key: string,
2368
+ map: ReadonlyMap<string, NativeConnAdapter>,
2369
+ ) => void,
2370
+ thisArg?: unknown,
2371
+ ): void {
2372
+ const conns = callNativeSync(() => this.#runtime.actorConns(this.#ctx));
2373
+ for (const conn of conns) {
2374
+ const id = this.#runtime.connId(conn);
2375
+ callback.call(thisArg, this.#connToAdapter(conn), id, this);
2376
+ }
2377
+ }
2378
+
2379
+ [Symbol.iterator](): MapIterator<[string, NativeConnAdapter]> {
2380
+ return this.entries();
2381
+ }
2382
+
2383
+ readonly [Symbol.toStringTag] = "NativeConnectionMap";
2384
+ }
2385
+
2326
2386
  export class ActorContextHandleAdapter {
2327
2387
  #runtime: CoreRuntime;
2328
2388
  #ctx: ActorContextHandle;
@@ -2331,9 +2391,9 @@ export class ActorContextHandleAdapter {
2331
2391
  #abortSignalCleanup?: () => void;
2332
2392
  #client?: AnyClient;
2333
2393
  #clientFactory?: () => AnyClient;
2394
+ #connMap?: NativeConnectionMap;
2334
2395
  #databaseProvider?: Exclude<AnyDatabaseProvider, undefined>;
2335
2396
  #db?: unknown;
2336
- #dbProxy?: unknown;
2337
2397
  #dispatchCancelToken?: CancellationTokenHandle;
2338
2398
  #kv?: NativeKvAdapter;
2339
2399
  #queue?: NativeQueueAdapter;
@@ -2396,32 +2456,25 @@ export class ActorContextHandleAdapter {
2396
2456
  throw databaseNotConfiguredError();
2397
2457
  }
2398
2458
 
2399
- if (!this.#dbProxy) {
2400
- this.#dbProxy = new Proxy(
2401
- {},
2402
- {
2403
- get: (_target, property) => {
2404
- if (property === "then") {
2405
- return undefined;
2406
- }
2459
+ if (this.#db) {
2460
+ return this.#db;
2461
+ }
2407
2462
 
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
- );
2463
+ const runtimeState = getNativeRuntimeState(this.#runtime, this.#ctx);
2464
+ const cachedClient = runtimeState.databaseClient;
2465
+ if (cachedClient) {
2466
+ this.#db = cachedClient.client;
2467
+ return this.#db;
2422
2468
  }
2423
2469
 
2424
- return this.#dbProxy;
2470
+ throw databaseClientNotReadyError();
2471
+ }
2472
+
2473
+ [RAW_STATE_SYMBOL](): unknown {
2474
+ if (!this.#stateEnabled) {
2475
+ throw stateNotEnabledError();
2476
+ }
2477
+ return this.#readState();
2425
2478
  }
2426
2479
 
2427
2480
  get state(): unknown {
@@ -2444,8 +2497,9 @@ export class ActorContextHandleAdapter {
2444
2497
  (nextValue) => {
2445
2498
  this.#writeState(nextValue, { scheduleSave: true });
2446
2499
  },
2447
- () => {
2500
+ (newValue) => {
2448
2501
  this.#assertCanMutateState();
2502
+ assertJsonCompatValue(newValue);
2449
2503
  },
2450
2504
  );
2451
2505
  }
@@ -2457,6 +2511,7 @@ export class ActorContextHandleAdapter {
2457
2511
  throw stateNotEnabledError();
2458
2512
  }
2459
2513
  this.#assertCanMutateState();
2514
+ assertJsonCompatValue(value);
2460
2515
  this.#writeState(value, { scheduleSave: true });
2461
2516
  }
2462
2517
 
@@ -2523,27 +2578,15 @@ export class ActorContextHandleAdapter {
2523
2578
  return callNativeSync(() => this.#runtime.actorRegion(this.#ctx));
2524
2579
  }
2525
2580
 
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
- );
2581
+ get conns(): ReadonlyMap<string, NativeConnAdapter> {
2582
+ if (!this.#connMap) {
2583
+ this.#connMap = new NativeConnectionMap(
2584
+ this.#runtime,
2585
+ this.#ctx,
2586
+ this.#schemas,
2587
+ );
2588
+ }
2589
+ return this.#connMap;
2547
2590
  }
2548
2591
 
2549
2592
  get log() {
@@ -2862,6 +2905,7 @@ export class ActorContextHandleAdapter {
2862
2905
  }
2863
2906
 
2864
2907
  sleep(): void {
2908
+ this.#flushStateChange();
2865
2909
  callNativeSync(() => this.#runtime.actorSleep(this.#ctx));
2866
2910
  }
2867
2911
 
@@ -3286,9 +3330,87 @@ function buildActorConfig(
3286
3330
  actions: Object.keys((config.actions ?? {}) as Record<string, unknown>)
3287
3331
  .sort()
3288
3332
  .map((name) => ({ name })),
3333
+ inspectorTabs: buildInspectorTabs(config.inspector),
3289
3334
  };
3290
3335
  }
3291
3336
 
3337
+ function buildInspectorTabs(
3338
+ inspector: unknown,
3339
+ ): Array<RuntimeInspectorTabEntry> | undefined {
3340
+ if (!inspector || typeof inspector !== "object") return undefined;
3341
+ const tabs = (inspector as { tabs?: unknown }).tabs;
3342
+ if (!Array.isArray(tabs) || tabs.length === 0) return undefined;
3343
+ return tabs.map((raw) => {
3344
+ const entry = raw as {
3345
+ id: string;
3346
+ label?: string;
3347
+ source?: string;
3348
+ icon?: string;
3349
+ hidden?: boolean;
3350
+ };
3351
+ if (entry.hidden === true) {
3352
+ return { id: entry.id, hidden: true };
3353
+ }
3354
+ // Resolve the author's source path against the current working
3355
+ // directory so the Rust runtime gets an absolute path. The author
3356
+ // runs the actor process from their project root by convention.
3357
+ const resolved =
3358
+ entry.source !== undefined
3359
+ ? nodePath.resolve(entry.source)
3360
+ : undefined;
3361
+ if (resolved !== undefined) {
3362
+ validateInspectorTabSource(entry.id, resolved);
3363
+ }
3364
+ return {
3365
+ id: entry.id,
3366
+ label: entry.label,
3367
+ icon: entry.icon,
3368
+ source: resolved,
3369
+ };
3370
+ });
3371
+ }
3372
+
3373
+ function validateInspectorTabSource(tabId: string, resolved: string): void {
3374
+ // Catch obviously dangerous misconfigurations at registry construction
3375
+ // rather than silently exposing the wrong subtree over the unauthenticated
3376
+ // `/inspector/custom-tabs/<id>/*` route. Fail loudly so misconfigured
3377
+ // actors never start.
3378
+ if (resolved === nodePath.parse(resolved).root) {
3379
+ throw new Error(
3380
+ `inspector.tabs[id="${tabId}"].source resolves to the filesystem root (${resolved}). ` +
3381
+ "Point it at the tab's own static-asset directory instead.",
3382
+ );
3383
+ }
3384
+ let stat: import("node:fs").Stats;
3385
+ try {
3386
+ stat = nodeFs.statSync(resolved);
3387
+ } catch (err) {
3388
+ const code = (err as NodeJS.ErrnoException)?.code;
3389
+ if (code === "ENOENT") {
3390
+ throw new Error(
3391
+ `inspector.tabs[id="${tabId}"].source (${resolved}) does not exist.`,
3392
+ );
3393
+ }
3394
+ if (code === "EACCES") {
3395
+ throw new Error(
3396
+ `inspector.tabs[id="${tabId}"].source (${resolved}) is not readable (EACCES).`,
3397
+ );
3398
+ }
3399
+ throw new Error(
3400
+ `inspector.tabs[id="${tabId}"].source (${resolved}) could not be stat'd: ${
3401
+ (err as Error)?.message ?? err
3402
+ }`,
3403
+ );
3404
+ }
3405
+ if (!stat.isDirectory()) {
3406
+ throw new Error(
3407
+ `inspector.tabs[id="${tabId}"].source (${resolved}) must be a directory, got ${
3408
+ stat.isFile() ? "file" : "non-directory"
3409
+ }.`,
3410
+ );
3411
+ }
3412
+ }
3413
+
3292
3414
  export function buildNativeFactory(
3293
3415
  runtime: CoreRuntime,
3294
3416
  registryConfig: RegistryConfig,
@@ -3548,6 +3670,10 @@ export function buildNativeFactory(
3548
3670
  getNativeWorkflowInspector(ctx) !== undefined,
3549
3671
  });
3550
3672
  } catch (error) {
3673
+ logger().error({
3674
+ msg: "error replaying workflow history",
3675
+ error,
3676
+ });
3551
3677
  return errorResponse(error);
3552
3678
  }
3553
3679
  }
@@ -3715,18 +3841,46 @@ export function buildNativeFactory(
3715
3841
  404,
3716
3842
  );
3717
3843
  }
3718
- const body = (await jsRequest.json()) as { args?: unknown[] };
3844
+ const body = (await jsRequest.json()) as {
3845
+ args?: unknown;
3846
+ properties?: unknown;
3847
+ };
3848
+ if (body.args !== undefined && body.properties !== undefined) {
3849
+ return jsonResponse(
3850
+ { error: "use either args or properties, not both" },
3851
+ { status: 400 },
3852
+ );
3853
+ }
3854
+ if (
3855
+ body.properties !== undefined &&
3856
+ (body.properties === null ||
3857
+ typeof body.properties !== "object" ||
3858
+ Array.isArray(body.properties))
3859
+ ) {
3860
+ return jsonResponse(
3861
+ { error: "properties must be an object" },
3862
+ { status: 400 },
3863
+ );
3864
+ }
3865
+ const args =
3866
+ body.properties !== undefined
3867
+ ? [body.properties]
3868
+ : normalizeArgs(body.args);
3719
3869
  try {
3720
3870
  const output = await action(
3721
3871
  actorCtx,
3722
3872
  ...validateActionArgs(
3723
3873
  schemaConfig.actionInputSchemas,
3724
3874
  actionName,
3725
- body.args ?? [],
3875
+ args,
3726
3876
  ),
3727
3877
  );
3728
3878
  return jsonResponse({ output });
3729
3879
  } catch (error) {
3880
+ logger().error({
3881
+ msg: "Error handling inspector action request",
3882
+ error,
3883
+ });
3730
3884
  return errorResponse(error);
3731
3885
  }
3732
3886
  }
@@ -3741,6 +3895,10 @@ export function buildNativeFactory(
3741
3895
  { status: 404 },
3742
3896
  );
3743
3897
  } catch (error) {
3898
+ logger().error({
3899
+ msg: "Error handling inspector request",
3900
+ error,
3901
+ });
3744
3902
  return errorResponse(error);
3745
3903
  } finally {
3746
3904
  await actorCtx.dispose();
@@ -3913,26 +4071,35 @@ export function buildNativeFactory(
3913
4071
  async (error: unknown, payload: { ctx: ActorContextHandle }) => {
3914
4072
  const { ctx } = unwrapTsfnPayload(error, payload);
3915
4073
  const actorCtx = makeActorCtx(ctx);
4074
+ // TODO: Move this save hook into cleanupNativeSleepRuntimeState
4075
+ // so immediate and deferred sleep cleanup share one save-state
4076
+ // path instead of passing a callback through cleanup.
4077
+ const saveActorState = async () => {
4078
+ if (runtime.kind === "wasm") {
4079
+ // Wasm cannot use the native context save helper here because
4080
+ // the runtime owns the serialized state handoff.
4081
+ await runtime.actorSaveState(
4082
+ ctx,
4083
+ actorCtx.serializeForTick("save"),
4084
+ );
4085
+ } else {
4086
+ await actorCtx.saveState({
4087
+ immediate: true,
4088
+ });
4089
+ }
4090
+ };
3916
4091
  try {
3917
4092
  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
- }
4093
+ await onSleep(actorCtx);
3932
4094
  }
4095
+ await saveActorState();
3933
4096
  } finally {
3934
4097
  try {
3935
- await cleanupNativeSleepRuntimeState(runtime, ctx);
4098
+ await cleanupNativeSleepRuntimeState(
4099
+ runtime,
4100
+ ctx,
4101
+ saveActorState,
4102
+ );
3936
4103
  } finally {
3937
4104
  await actorCtx.dispose();
3938
4105
  }
@@ -4589,10 +4756,29 @@ export async function buildServeConfig(
4589
4756
  serverlessMaxStartPayloadBytes: config.serverless.maxStartPayloadBytes,
4590
4757
  };
4591
4758
 
4592
- if (config.startEngine) {
4759
+ // Always best-effort resolve the npm-installed engine binary and hand its
4760
+ // path to the core. The core alone decides whether to actually spawn a local
4761
+ // engine (its `should_manage_engine`, based on the endpoint + spawn mode), so
4762
+ // JS must not duplicate that decision here. Only JS knows the npm
4763
+ // `node_modules` layout, so it resolves the path; if no binary is available
4764
+ // (remote-only install, unsupported platform, optional deps skipped), leave
4765
+ // it unset and let the core report `engine.binary_unavailable` if it actually
4766
+ // needs one.
4767
+ try {
4593
4768
  const { getEnginePath } = await loadEngineCli();
4594
4769
  serveConfig.engineBinaryPath = getEnginePath();
4770
+ } catch (error) {
4771
+ // The npm-installed engine binary could not be resolved. The core still
4772
+ // decides whether it needs to spawn a local engine; if it does, it will
4773
+ // fail with engine.binary_unavailable (auto-download is off in the napi
4774
+ // runtime). Warn so the cause is actionable.
4775
+ logger().warn({
4776
+ 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",
4777
+ error: stringifyError(error),
4778
+ });
4595
4779
  }
4780
+ serveConfig.engineHost = config.engineHost;
4781
+ serveConfig.enginePort = config.enginePort;
4596
4782
  if (config.test?.enabled) {
4597
4783
  serveConfig.inspectorTestToken =
4598
4784
  getEnvUniversal("_RIVET_TEST_INSPECTOR_TOKEN") ?? "token";