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
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Node.js runtime health metrics.
3
+ *
4
+ * Collects JS-internal data (event loop lag, GC, heap, libuv handles,
5
+ * event loop utilization, CPU) using Node built-ins (`node:perf_hooks`,
6
+ * `process`, `node:v8`, `PerformanceObserver`) and pushes them across NAPI
7
+ * into Rust-side prometheus collectors registered with
8
+ * `rivet_metrics::REGISTRY` so they appear on the existing `/metrics`
9
+ * endpoint.
10
+ *
11
+ * All data collection happens here in TypeScript. The NAPI bridge is pure
12
+ * type marshalling and the Rust side only registers + stores the metrics.
13
+ */
14
+ import {
15
+ monitorEventLoopDelay,
16
+ PerformanceObserver,
17
+ performance,
18
+ } from "node:perf_hooks";
19
+ import { getHeapStatistics } from "node:v8";
20
+ import * as napi from "@rivetkit/rivetkit-napi";
21
+
22
+ type OptionalProcessMetricsNapi = typeof napi & {
23
+ jsObserveGcDuration?: (kind: string, durationSeconds: number) => void;
24
+ jsSetEventloopHeartbeatTsMs?: (timestampMs: number) => void;
25
+ jsSetEventloopLagQuantile?: (
26
+ quantile: string,
27
+ valueSeconds: number,
28
+ ) => void;
29
+ jsSetEventloopUtilization?: (utilization: number) => void;
30
+ jsAddProcessCpuSeconds?: (mode: string, valueSeconds: number) => void;
31
+ jsSetProcessResidentMemoryBytes?: (bytes: number) => void;
32
+ jsSetHeapBytes?: (kind: string, bytes: number) => void;
33
+ jsSetActiveHandles?: (count: number) => void;
34
+ jsSetActiveRequests?: (count: number) => void;
35
+ };
36
+
37
+ type GcPerformanceEntry = {
38
+ duration: number;
39
+ detail?: { kind?: number };
40
+ kind?: number;
41
+ };
42
+
43
+ const processMetricsNapi = napi as OptionalProcessMetricsNapi;
44
+
45
+ // Some napi process-metrics symbols may be missing on older native binaries
46
+ // (the auto-generated index.js destructures them as `undefined` if the
47
+ // underlying `.node` was built before they were added). Guard each call so
48
+ // the metrics collection runs as a no-op instead of throwing
49
+ // `TypeError: napi.jsXxx is not a function` on every interval tick.
50
+ function callIfFn<T extends unknown[]>(
51
+ fn: ((...args: T) => void) | undefined,
52
+ ...args: T
53
+ ): void {
54
+ if (typeof fn === "function") {
55
+ fn(...args);
56
+ }
57
+ }
58
+
59
+ const SCRAPE_INTERVAL_MS = 5_000;
60
+ const HEARTBEAT_INTERVAL_MS = 100;
61
+ const EVENTLOOP_DELAY_RESOLUTION_MS = 20;
62
+ const NS_PER_SECOND = 1e9;
63
+ const US_PER_SECOND = 1e6;
64
+
65
+ // V8 GC kind bitfield from Node's perf_hooks documentation. A `gc` performance
66
+ // entry's `kind` field is one of these values.
67
+ const GC_KIND_NAMES: Record<number, string> = {
68
+ 1: "minor",
69
+ 2: "major",
70
+ 4: "incremental",
71
+ 8: "weakcb",
72
+ };
73
+
74
+ interface ProcessMetricsState {
75
+ scrapeInterval: NodeJS.Timeout;
76
+ heartbeatInterval: NodeJS.Timeout;
77
+ gcObserver: PerformanceObserver;
78
+ eventLoopHistogram: ReturnType<typeof monitorEventLoopDelay>;
79
+ lastCpuUsage: NodeJS.CpuUsage;
80
+ lastEventLoopUtilization: ReturnType<
81
+ typeof performance.eventLoopUtilization
82
+ >;
83
+ }
84
+
85
+ let state: ProcessMetricsState | undefined;
86
+
87
+ export function startProcessMetrics(): void {
88
+ if (state) {
89
+ return;
90
+ }
91
+
92
+ const eventLoopHistogram = monitorEventLoopDelay({
93
+ resolution: EVENTLOOP_DELAY_RESOLUTION_MS,
94
+ });
95
+ eventLoopHistogram.enable();
96
+
97
+ const gcObserver = new PerformanceObserver((list) => {
98
+ for (const entry of list.getEntries()) {
99
+ const gcEntry = entry as GcPerformanceEntry;
100
+ const kind = gcEntry.detail?.kind ?? gcEntry.kind;
101
+ if (typeof kind !== "number") continue;
102
+ const kindName = GC_KIND_NAMES[kind];
103
+ if (!kindName) continue;
104
+ // `entry.duration` is in milliseconds; convert to seconds.
105
+ callIfFn(
106
+ processMetricsNapi.jsObserveGcDuration,
107
+ kindName,
108
+ gcEntry.duration / 1000,
109
+ );
110
+ }
111
+ });
112
+ gcObserver.observe({ entryTypes: ["gc"], buffered: false });
113
+
114
+ const lastCpuUsage = process.cpuUsage();
115
+ const lastEventLoopUtilization = performance.eventLoopUtilization();
116
+
117
+ const heartbeatInterval = setInterval(() => {
118
+ callIfFn(processMetricsNapi.jsSetEventloopHeartbeatTsMs, Date.now());
119
+ }, HEARTBEAT_INTERVAL_MS);
120
+ heartbeatInterval.unref();
121
+
122
+ const scrapeInterval = setInterval(() => {
123
+ try {
124
+ collectAndPush();
125
+ } catch {
126
+ // Collection errors must never bring down the process; metrics
127
+ // are best-effort.
128
+ }
129
+ }, SCRAPE_INTERVAL_MS);
130
+ scrapeInterval.unref();
131
+
132
+ state = {
133
+ scrapeInterval,
134
+ heartbeatInterval,
135
+ gcObserver,
136
+ eventLoopHistogram,
137
+ lastCpuUsage,
138
+ lastEventLoopUtilization,
139
+ };
140
+
141
+ // Emit one snapshot immediately so freshly-scraped instances have data.
142
+ callIfFn(processMetricsNapi.jsSetEventloopHeartbeatTsMs, Date.now());
143
+ try {
144
+ collectAndPush();
145
+ } catch {
146
+ // As above; best-effort.
147
+ }
148
+ }
149
+
150
+ export function stopProcessMetrics(): void {
151
+ if (!state) {
152
+ return;
153
+ }
154
+ clearInterval(state.scrapeInterval);
155
+ clearInterval(state.heartbeatInterval);
156
+ state.gcObserver.disconnect();
157
+ state.eventLoopHistogram.disable();
158
+ state = undefined;
159
+ }
160
+
161
+ function collectAndPush(): void {
162
+ if (!state) return;
163
+
164
+ // Event loop delay quantiles. `monitorEventLoopDelay()` reports values in
165
+ // nanoseconds; convert to seconds. Reset after reading so the next window
166
+ // reflects only the new interval.
167
+ const hist = state.eventLoopHistogram;
168
+ callIfFn(
169
+ processMetricsNapi.jsSetEventloopLagQuantile,
170
+ "p50",
171
+ hist.percentile(50) / NS_PER_SECOND,
172
+ );
173
+ callIfFn(
174
+ processMetricsNapi.jsSetEventloopLagQuantile,
175
+ "p90",
176
+ hist.percentile(90) / NS_PER_SECOND,
177
+ );
178
+ callIfFn(
179
+ processMetricsNapi.jsSetEventloopLagQuantile,
180
+ "p99",
181
+ hist.percentile(99) / NS_PER_SECOND,
182
+ );
183
+ callIfFn(
184
+ processMetricsNapi.jsSetEventloopLagQuantile,
185
+ "max",
186
+ hist.max / NS_PER_SECOND,
187
+ );
188
+ hist.reset();
189
+
190
+ // Event loop utilization delta over the scrape window.
191
+ const nextElu = performance.eventLoopUtilization();
192
+ const eluDelta = performance.eventLoopUtilization(
193
+ nextElu,
194
+ state.lastEventLoopUtilization,
195
+ );
196
+ state.lastEventLoopUtilization = nextElu;
197
+ callIfFn(
198
+ processMetricsNapi.jsSetEventloopUtilization,
199
+ eluDelta.utilization,
200
+ );
201
+
202
+ // CPU usage delta. `process.cpuUsage()` returns microseconds.
203
+ const nextCpu = process.cpuUsage();
204
+ const userDeltaUs = nextCpu.user - state.lastCpuUsage.user;
205
+ const systemDeltaUs = nextCpu.system - state.lastCpuUsage.system;
206
+ state.lastCpuUsage = nextCpu;
207
+ if (userDeltaUs > 0) {
208
+ callIfFn(
209
+ processMetricsNapi.jsAddProcessCpuSeconds,
210
+ "user",
211
+ userDeltaUs / US_PER_SECOND,
212
+ );
213
+ }
214
+ if (systemDeltaUs > 0) {
215
+ callIfFn(
216
+ processMetricsNapi.jsAddProcessCpuSeconds,
217
+ "system",
218
+ systemDeltaUs / US_PER_SECOND,
219
+ );
220
+ }
221
+
222
+ // Memory + heap.
223
+ const mem = process.memoryUsage();
224
+ callIfFn(processMetricsNapi.jsSetProcessResidentMemoryBytes, mem.rss);
225
+ callIfFn(processMetricsNapi.jsSetHeapBytes, "used", mem.heapUsed);
226
+ callIfFn(processMetricsNapi.jsSetHeapBytes, "total", mem.heapTotal);
227
+ const heapLimit = getHeapStatistics().heap_size_limit;
228
+ callIfFn(processMetricsNapi.jsSetHeapBytes, "limit", heapLimit);
229
+
230
+ // libuv active handles + requests. These are unstable Node internals
231
+ // guarded behind underscore-prefixed names; if a future Node release
232
+ // removes them the try/catch above keeps the rest of the collection
233
+ // alive.
234
+ const proc = process as unknown as {
235
+ _getActiveHandles?: () => unknown[];
236
+ _getActiveRequests?: () => unknown[];
237
+ };
238
+ if (typeof proc._getActiveHandles === "function") {
239
+ callIfFn(
240
+ processMetricsNapi.jsSetActiveHandles,
241
+ proc._getActiveHandles().length,
242
+ );
243
+ }
244
+ if (typeof proc._getActiveRequests === "function") {
245
+ callIfFn(
246
+ processMetricsNapi.jsSetActiveRequests,
247
+ proc._getActiveRequests().length,
248
+ );
249
+ }
250
+ }
@@ -1,5 +1,7 @@
1
+ import { stringifyError } from "@/common/utils";
1
2
  import type { SqliteNativeMetrics } from "@/common/database/config";
2
3
  import type { RegistryConfig } from "./config";
4
+ import { logger } from "./log";
3
5
 
4
6
  declare const handleBrand: unique symbol;
5
7
 
@@ -225,6 +227,22 @@ export interface RuntimeActorConfig {
225
227
  preloadMaxWorkflowBytes?: number;
226
228
  preloadMaxConnectionsBytes?: number;
227
229
  actions?: Array<{ name: string }>;
230
+ inspectorTabs?: Array<RuntimeInspectorTabEntry>;
231
+ }
232
+
233
+ export interface RuntimeInspectorTabEntry {
234
+ id: string;
235
+ /** Required for custom entries; omitted for built-in hides. */
236
+ label?: string;
237
+ /**
238
+ * Required for custom entries — absolute path to the source directory.
239
+ * Resolved on the TS side before being handed to the runtime.
240
+ */
241
+ source?: string;
242
+ /** Optional icon id for custom entries. */
243
+ icon?: string;
244
+ /** Set to true for built-in hide entries. */
245
+ hidden?: boolean;
228
246
  }
229
247
 
230
248
  export interface RuntimeServeConfig {
@@ -234,6 +252,8 @@ export interface RuntimeServeConfig {
234
252
  namespace: string;
235
253
  poolName: string;
236
254
  engineBinaryPath?: string;
255
+ engineHost?: string;
256
+ enginePort?: number;
237
257
  handleInspectorHttpInRuntime?: boolean;
238
258
  inspectorTestToken?: string;
239
259
  serverlessBasePath?: string;
@@ -399,6 +419,9 @@ export interface CoreRuntime {
399
419
  ): void;
400
420
  actorWaitUntil(ctx: ActorContextHandle, promise: Promise<unknown>): void;
401
421
  actorWaitForTrackedShutdownWork(ctx: ActorContextHandle): Promise<boolean>;
422
+ actorWaitForTrackedShutdownWorkUnbounded(
423
+ ctx: ActorContextHandle,
424
+ ): Promise<void>;
402
425
  actorKeepAwake(ctx: ActorContextHandle, promise: Promise<unknown>): void;
403
426
  actorBeginKeepAwake(ctx: ActorContextHandle): number;
404
427
  actorEndKeepAwake(ctx: ActorContextHandle, regionId: number): void;
@@ -491,6 +514,7 @@ export interface CoreRuntime {
491
514
  ctx: ActorContextHandle,
492
515
  names: string[],
493
516
  options?: RuntimeQueueWaitOptions | undefined | null,
517
+ signal?: CancellationTokenHandle | undefined | null,
494
518
  ): Promise<void>;
495
519
  actorQueueEnqueueAndWait(
496
520
  ctx: ActorContextHandle,
@@ -577,9 +601,25 @@ export async function buildServeConfig(
577
601
  serverlessMaxStartPayloadBytes: config.serverless.maxStartPayloadBytes,
578
602
  };
579
603
 
580
- if (config.startEngine) {
604
+ // Always best-effort resolve the engine binary path and hand it to the core.
605
+ // The core alone decides whether to actually spawn a local engine, so JS must
606
+ // not duplicate that decision here. `loadEnginePath` throws when no binary is
607
+ // available (remote-only install, unsupported platform, optional deps
608
+ // skipped); in that case leave it unset and let the core report
609
+ // `engine.binary_unavailable` only if it actually needs one.
610
+ try {
581
611
  serveConfig.engineBinaryPath = await loadEnginePath();
612
+ } catch (error) {
613
+ // The engine binary could not be resolved. The core still decides whether
614
+ // it needs to spawn a local engine; if it does, it will fail with
615
+ // engine.binary_unavailable (auto-download is off in the napi runtime).
616
+ logger().warn({
617
+ 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",
618
+ error: stringifyError(error),
619
+ });
582
620
  }
621
+ serveConfig.engineHost = config.engineHost;
622
+ serveConfig.enginePort = config.enginePort;
583
623
  if (config.test?.enabled) {
584
624
  serveConfig.inspectorTestToken =
585
625
  process.env._RIVET_TEST_INSPECTOR_TOKEN ?? "token";
@@ -32,8 +32,8 @@ import type {
32
32
  RuntimeQueueNextBatchOptions,
33
33
  RuntimeQueueTryNextBatchOptions,
34
34
  RuntimeQueueWaitOptions,
35
- RuntimeRequestSaveOpts,
36
35
  RuntimeRegistryRouteResponse,
36
+ RuntimeRequestSaveOpts,
37
37
  RuntimeServeConfig,
38
38
  RuntimeServerlessRequest,
39
39
  RuntimeServerlessResponseHead,
@@ -519,6 +519,15 @@ export class WasmCoreRuntime implements CoreRuntime {
519
519
  );
520
520
  }
521
521
 
522
+ async actorWaitForTrackedShutdownWorkUnbounded(
523
+ ctx: ActorContextHandle,
524
+ ): Promise<void> {
525
+ await callHandle<Promise<void>>(
526
+ asWasmActorContext(ctx),
527
+ "waitForTrackedShutdownWorkUnbounded",
528
+ );
529
+ }
530
+
522
531
  actorKeepAwake(ctx: ActorContextHandle, promise: Promise<unknown>): void {
523
532
  const wasmCtx = asWasmActorContext(ctx);
524
533
  const regionId = callHandle<number>(wasmCtx, "beginKeepAwake");
@@ -764,9 +773,16 @@ export class WasmCoreRuntime implements CoreRuntime {
764
773
  ctx: ActorContextHandle,
765
774
  names: string[],
766
775
  options?: RuntimeQueueWaitOptions | undefined | null,
776
+ signal?: CancellationTokenHandle | undefined | null,
767
777
  ): Promise<void> {
768
778
  const queue = childHandle(asWasmActorContext(ctx), "queue");
769
- await callHandleAsync(queue, "waitForNamesAvailable", names, options);
779
+ await callHandleAsync(
780
+ queue,
781
+ "waitForNamesAvailable",
782
+ names,
783
+ options,
784
+ signal,
785
+ );
770
786
  }
771
787
 
772
788
  async actorQueueEnqueueAndWait(
@@ -0,0 +1,40 @@
1
+ import onChange from "@rivetkit/on-change";
2
+
3
+ /**
4
+ * Creates a proxy that tracks deep mutations on an object and calls `commit`
5
+ * after every change. Uses `@rivetkit/on-change` internally, which correctly
6
+ * detects mutations via methods on Map, Set, Date, TypedArrays, and arrays.
7
+ *
8
+ * If the value is not an object (primitive, null, undefined), it is returned
9
+ * as-is since primitives cannot be proxied or mutated.
10
+ *
11
+ * @param value - The root value to watch.
12
+ * @param commit - Called after every detected mutation with the root object.
13
+ * @param beforeChange - Called before every mutation with the new value being
14
+ * assigned. Throw to reject the change.
15
+ */
16
+ export function createWriteThroughProxy<T>(
17
+ value: T,
18
+ commit: (next: T) => void,
19
+ beforeChange?: (newValue: unknown) => void,
20
+ ): T {
21
+ if (!value || typeof value !== "object") {
22
+ return value;
23
+ }
24
+
25
+ return onChange(
26
+ value as T & Record<string, any>,
27
+ () => {
28
+ commit(value);
29
+ },
30
+ {
31
+ // Rejection is throw-based: beforeChange throws to prevent the
32
+ // mutation. We always return true so on-change applies the change
33
+ // if beforeChange did not throw.
34
+ onValidate(_path: string, newValue: unknown) {
35
+ beforeChange?.(newValue);
36
+ return true;
37
+ },
38
+ },
39
+ ) as T;
40
+ }
package/src/serde.ts CHANGED
@@ -2,8 +2,8 @@ import * as cbor from "cbor-x";
2
2
  import invariant from "invariant";
3
3
  import type { VersionedDataHandler } from "vbare";
4
4
  import type { z } from "zod/v4";
5
+ import type { Encoding, JsonCompatValue } from "@/common/encoding";
5
6
  import { assertUnreachable } from "@/common/utils";
6
- import type { Encoding } from "@/common/encoding";
7
7
  import {
8
8
  encodeJsonCompatValue,
9
9
  jsonParseCompat,
@@ -46,7 +46,7 @@ export function contentTypeForEncoding(encoding: Encoding): string {
46
46
  }
47
47
  }
48
48
 
49
- export function encodeCborCompat(value: unknown): Uint8Array {
49
+ export function encodeCborCompat(value: JsonCompatValue): Uint8Array {
50
50
  return cbor.encode(encodeJsonCompatValue(value));
51
51
  }
52
52
 
@@ -1,9 +1,9 @@
1
1
  import { convertRegistryConfigToClientConfig } from "@/client/config";
2
+ import { stringifyError } from "@/common/utils";
2
3
  import {
3
4
  getDatacenters,
4
5
  updateRunnerConfig,
5
6
  } from "@/engine-client/api-endpoints";
6
- import { stringifyError } from "@/common/utils";
7
7
  import type { RegistryConfig } from "@/registry/config";
8
8
  import { logger } from "@/registry/log";
9
9
 
@@ -16,11 +16,14 @@ function sleep(ms: number): Promise<void> {
16
16
 
17
17
  function configureTimeoutMs() {
18
18
  const value = process.env.RIVET_SERVERLESS_CONFIGURE_TIMEOUT_MS;
19
- if (value === undefined || value === "") return DEFAULT_CONFIGURE_TIMEOUT_MS;
19
+ if (value === undefined || value === "")
20
+ return DEFAULT_CONFIGURE_TIMEOUT_MS;
20
21
 
21
22
  const parsed = Number(value);
22
23
  if (!Number.isFinite(parsed) || parsed < 0) {
23
- throw new Error("RIVET_SERVERLESS_CONFIGURE_TIMEOUT_MS must be a finite non-negative number");
24
+ throw new Error(
25
+ "RIVET_SERVERLESS_CONFIGURE_TIMEOUT_MS must be a finite non-negative number",
26
+ );
24
27
  }
25
28
 
26
29
  return parsed;
@@ -40,13 +43,19 @@ export async function configureServerlessPool(
40
43
  attempts += 1;
41
44
  try {
42
45
  if (!config.namespace) {
43
- throw new Error("namespace is required for serverless configuration");
46
+ throw new Error(
47
+ "namespace is required for serverless configuration",
48
+ );
44
49
  }
45
50
  if (!config.endpoint) {
46
- throw new Error("endpoint is required for serverless configuration");
51
+ throw new Error(
52
+ "endpoint is required for serverless configuration",
53
+ );
47
54
  }
48
55
  if (!config.configurePool) {
49
- throw new Error("configurePool is required for serverless configuration");
56
+ throw new Error(
57
+ "configurePool is required for serverless configuration",
58
+ );
50
59
  }
51
60
 
52
61
  const customConfig = config.configurePool;
@@ -55,7 +64,9 @@ export async function configureServerlessPool(
55
64
  const poolName = customConfig.name ?? "default";
56
65
  const serverlessToken = config.token ?? config.publicToken;
57
66
  const headers = {
58
- ...(serverlessToken ? { "x-rivet-token": serverlessToken } : {}),
67
+ ...(serverlessToken
68
+ ? { "x-rivet-token": serverlessToken }
69
+ : {}),
59
70
  ...(customConfig.headers ?? {}),
60
71
  };
61
72
  const serverlessConfig = {
package/src/test/mod.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import pRetry from "p-retry";
2
2
  import type { TestContext } from "vitest";
3
- import { type Client, createClient } from "@/client/mod";
4
3
  import { convertRegistryConfigToClientConfig } from "@/client/config";
4
+ import { type Client, createClient } from "@/client/mod";
5
5
  import { getMetadata } from "@/engine-client/api-endpoints";
6
6
  import type { Registry } from "@/registry";
7
7
 
@@ -14,13 +14,16 @@ async function waitForRegistryReady(registry: Registry<any>): Promise<void> {
14
14
  registry.parseConfig(),
15
15
  );
16
16
 
17
- await pRetry(async () => {
18
- await getMetadata(clientConfig);
19
- }, {
20
- retries: 20,
21
- minTimeout: 50,
22
- maxTimeout: 250,
23
- });
17
+ await pRetry(
18
+ async () => {
19
+ await getMetadata(clientConfig);
20
+ },
21
+ {
22
+ retries: 20,
23
+ minTimeout: 50,
24
+ maxTimeout: 250,
25
+ },
26
+ );
24
27
  }
25
28
 
26
29
  export async function setupTest<A extends Registry<any>>(
@@ -1,4 +1,4 @@
1
- import { z } from "zod/v4";
1
+ import type { z } from "zod/v4";
2
2
 
3
3
  export interface ParsedEndpoint {
4
4
  endpoint: string;
@@ -22,6 +22,12 @@ export const getRivetTotalSlots = (): number | undefined => {
22
22
  };
23
23
  export const getRivetRunEngine = (): boolean =>
24
24
  getEnvUniversal("RIVET_RUN_ENGINE") === "1";
25
+ export const getRivetRunEngineHost = (): string | undefined =>
26
+ getEnvUniversal("RIVET_RUN_ENGINE_HOST");
27
+ export const getRivetRunEnginePort = (): number | undefined => {
28
+ const value = getEnvUniversal("RIVET_RUN_ENGINE_PORT");
29
+ return value !== undefined ? parseInt(value, 10) : undefined;
30
+ };
25
31
  export const getRivetRunEngineVersion = (): string | undefined =>
26
32
  getEnvUniversal("RIVET_RUN_ENGINE_VERSION");
27
33
  export const getRivetEnvoyKind = (): string | undefined =>
@@ -2,12 +2,12 @@ import { OpenAPIHono } from "@hono/zod-openapi";
2
2
  import type { Hono } from "hono";
3
3
  import { createMiddleware } from "hono/factory";
4
4
  import { cors } from "@/common/cors";
5
+ import { getLogger } from "@/common/log";
5
6
  import {
6
7
  handleRouteError,
7
8
  handleRouteNotFound,
8
9
  loggerMiddleware,
9
10
  } from "@/common/router";
10
- import { getLogger } from "@/common/log";
11
11
 
12
12
  export function logger() {
13
13
  return getLogger("router");
@@ -1,10 +1,9 @@
1
- import type { Hono } from "hono";
2
- import { detectRuntime, stringifyError, type Runtime } from "../utils";
3
- import { RegistryConfig } from "@/registry/config";
4
- import { logger } from "@/registry/log";
5
-
6
1
  // TODO: Go back to dynamic import for this
7
2
  import getPort from "get-port";
3
+ import type { Hono } from "hono";
4
+ import type { RegistryConfig } from "@/registry/config";
5
+ import { logger } from "@/registry/log";
6
+ import { detectRuntime, type Runtime, stringifyError } from "../utils";
8
7
 
9
8
  const DEFAULT_PORT = 6421;
10
9
  export type ServeStatic =
package/src/utils.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { stringifyError } from "@/common/utils";
2
1
  import type { Context as HonoContext, Handler as HonoHandler } from "hono";
3
- import { stringify as uuidstringify } from "uuid";
2
+ import { stringifyError } from "@/common/utils";
4
3
  import pkgJson from "../package.json" with { type: "json" };
5
4
  import { getLogger } from "./common/log";
6
5
  import { assertUnreachable } from "./common/utils";