rivetkit 2.3.0-rc.10 → 2.3.0-rc.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/dist/browser/client.d.ts +105 -56
  2. package/dist/browser/client.js +128 -87
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +40 -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.js +1 -1
  8. package/dist/tsup/agent-os/index.cjs +2103 -2090
  9. package/dist/tsup/agent-os/index.cjs.map +1 -1
  10. package/dist/tsup/agent-os/index.d.cts +106 -66
  11. package/dist/tsup/agent-os/index.d.ts +106 -66
  12. package/dist/tsup/agent-os/index.js +2102 -2089
  13. package/dist/tsup/agent-os/index.js.map +1 -1
  14. package/dist/tsup/{chunk-4K3MV2MW.cjs → chunk-2NXFKPRB.cjs} +10 -10
  15. package/dist/tsup/chunk-2NXFKPRB.cjs.map +1 -0
  16. package/dist/tsup/{chunk-6BI2MS3S.js → chunk-3P2JUHWJ.js} +2 -2
  17. package/dist/tsup/{chunk-KU6VKVEK.js → chunk-4FP4FFB5.js} +18 -6
  18. package/dist/tsup/chunk-4FP4FFB5.js.map +1 -0
  19. package/dist/tsup/{chunk-KIWH5H3K.js → chunk-BRP62GZC.js} +3 -3
  20. package/dist/tsup/chunk-BRP62GZC.js.map +1 -0
  21. package/dist/tsup/{chunk-G34LIR7S.js → chunk-GRFBV2U7.js} +22 -7
  22. package/dist/tsup/chunk-GRFBV2U7.js.map +1 -0
  23. package/dist/tsup/{chunk-PWFGP2US.cjs → chunk-GX6W4MW3.cjs} +138 -24
  24. package/dist/tsup/chunk-GX6W4MW3.cjs.map +1 -0
  25. package/dist/tsup/{chunk-3YY5S6TV.js → chunk-HXUEHHJF.js} +2 -2
  26. package/dist/tsup/chunk-HXUEHHJF.js.map +1 -0
  27. package/dist/tsup/{chunk-PCBNKI2J.js → chunk-JZ7TWV65.js} +1 -1
  28. package/dist/tsup/chunk-JZ7TWV65.js.map +1 -0
  29. package/dist/tsup/{chunk-QAZLM4WT.cjs → chunk-KORQB2IR.cjs} +3 -3
  30. package/dist/tsup/{chunk-QAZLM4WT.cjs.map → chunk-KORQB2IR.cjs.map} +1 -1
  31. package/dist/tsup/{chunk-BM3EOY7M.js → chunk-KRC4L3YB.js} +134 -20
  32. package/dist/tsup/chunk-KRC4L3YB.js.map +1 -0
  33. package/dist/tsup/{chunk-G5RULGYQ.cjs → chunk-LNP7Q6I6.cjs} +24 -9
  34. package/dist/tsup/chunk-LNP7Q6I6.cjs.map +1 -0
  35. package/dist/tsup/{chunk-Z4C3W2CQ.cjs → chunk-LW5HNCWD.cjs} +3 -3
  36. package/dist/tsup/{chunk-Z4C3W2CQ.cjs.map → chunk-LW5HNCWD.cjs.map} +1 -1
  37. package/dist/tsup/{chunk-J5P6S2LC.cjs → chunk-RDBGKI66.cjs} +25 -13
  38. package/dist/tsup/chunk-RDBGKI66.cjs.map +1 -0
  39. package/dist/tsup/{chunk-T6YVRM4K.js → chunk-T3VCJ4PV.js} +3 -1
  40. package/dist/tsup/chunk-T3VCJ4PV.js.map +1 -0
  41. package/dist/tsup/{chunk-WU2O2KIE.js → chunk-TTLUIDVH.js} +61 -77
  42. package/dist/tsup/chunk-TTLUIDVH.js.map +1 -0
  43. package/dist/tsup/{chunk-LD5YASJU.cjs → chunk-VE2X4KMG.cjs} +2 -2
  44. package/dist/tsup/{chunk-LD5YASJU.cjs.map → chunk-VE2X4KMG.cjs.map} +1 -1
  45. package/dist/tsup/{chunk-XV52XUWU.js → chunk-WXYWDLJY.js} +4 -4
  46. package/dist/tsup/chunk-WXYWDLJY.js.map +1 -0
  47. package/dist/tsup/{chunk-WQ4HNA4W.cjs → chunk-XCDCURZ4.cjs} +4 -2
  48. package/dist/tsup/chunk-XCDCURZ4.cjs.map +1 -0
  49. package/dist/tsup/{chunk-TE4VCDNY.cjs → chunk-XG25CGSW.cjs} +217 -233
  50. package/dist/tsup/chunk-XG25CGSW.cjs.map +1 -0
  51. package/dist/tsup/{chunk-3LGP4JSO.cjs → chunk-Y3JBOFBG.cjs} +8 -8
  52. package/dist/tsup/{chunk-3LGP4JSO.cjs.map → chunk-Y3JBOFBG.cjs.map} +1 -1
  53. package/dist/tsup/{chunk-CMYS77J6.js → chunk-YRQ4F5CD.js} +3 -3
  54. package/dist/tsup/{chunk-2NDZ7JCR.cjs → chunk-ZA7FLHKH.cjs} +1 -1
  55. package/dist/tsup/chunk-ZA7FLHKH.cjs.map +1 -0
  56. package/dist/tsup/client/mod.cjs +9 -9
  57. package/dist/tsup/client/mod.d.cts +4 -4
  58. package/dist/tsup/client/mod.d.ts +4 -4
  59. package/dist/tsup/client/mod.js +8 -8
  60. package/dist/tsup/common/log.cjs +3 -3
  61. package/dist/tsup/common/log.js +2 -2
  62. package/dist/tsup/common/websocket.cjs +4 -4
  63. package/dist/tsup/common/websocket.js +3 -3
  64. package/dist/tsup/{config-Ca8dN4cS.d.cts → config-CTwe3WwC.d.cts} +69 -19
  65. package/dist/tsup/{config-CxjGYf4K.d.cts → config-D49x8NpL.d.cts} +1 -2
  66. package/dist/tsup/{config-CxjGYf4K.d.ts → config-D49x8NpL.d.ts} +1 -2
  67. package/dist/tsup/{config-0Ta55UV0.d.ts → config-De5UVu0V.d.ts} +69 -19
  68. package/dist/tsup/{context-B_IWbWne.d.ts → context-DPHISlUi.d.ts} +8 -8
  69. package/dist/tsup/{context-CUrQ9MHc.d.cts → context-Dmj477Uh.d.cts} +8 -8
  70. package/dist/tsup/db/drizzle.cjs +3 -3
  71. package/dist/tsup/db/drizzle.d.cts +1 -1
  72. package/dist/tsup/db/drizzle.d.ts +1 -1
  73. package/dist/tsup/db/drizzle.js +1 -1
  74. package/dist/tsup/db/mod.cjs +2 -2
  75. package/dist/tsup/db/mod.d.cts +2 -2
  76. package/dist/tsup/db/mod.d.ts +2 -2
  77. package/dist/tsup/db/mod.js +1 -1
  78. package/dist/tsup/dynamic/mod.cjs +24 -0
  79. package/dist/tsup/dynamic/mod.cjs.map +1 -0
  80. package/dist/tsup/dynamic/mod.d.cts +37 -0
  81. package/dist/tsup/dynamic/mod.d.ts +37 -0
  82. package/dist/tsup/dynamic/mod.js +24 -0
  83. package/dist/tsup/dynamic/mod.js.map +1 -0
  84. package/dist/tsup/inspector/mod.cjs +6 -6
  85. package/dist/tsup/inspector/mod.js +5 -5
  86. package/dist/tsup/mod.cjs +501 -315
  87. package/dist/tsup/mod.cjs.map +1 -1
  88. package/dist/tsup/mod.d.cts +4 -4
  89. package/dist/tsup/mod.d.ts +4 -4
  90. package/dist/tsup/mod.js +405 -219
  91. package/dist/tsup/mod.js.map +1 -1
  92. package/dist/tsup/test/mod.cjs +21 -18
  93. package/dist/tsup/test/mod.cjs.map +1 -1
  94. package/dist/tsup/test/mod.d.cts +3 -3
  95. package/dist/tsup/test/mod.d.ts +3 -3
  96. package/dist/tsup/test/mod.js +18 -15
  97. package/dist/tsup/test/mod.js.map +1 -1
  98. package/dist/tsup/utils.cjs +3 -3
  99. package/dist/tsup/utils.d.cts +1 -1
  100. package/dist/tsup/utils.d.ts +1 -1
  101. package/dist/tsup/utils.js +2 -2
  102. package/dist/tsup/workflow/mod.cjs +307 -282
  103. package/dist/tsup/workflow/mod.cjs.map +1 -1
  104. package/dist/tsup/workflow/mod.d.cts +5 -5
  105. package/dist/tsup/workflow/mod.d.ts +5 -5
  106. package/dist/tsup/workflow/mod.js +501 -476
  107. package/dist/tsup/workflow/mod.js.map +1 -1
  108. package/package.json +22 -11
  109. package/src/actor/config.ts +48 -41
  110. package/src/actor/contexts/index.ts +7 -2
  111. package/src/actor/definition.ts +11 -14
  112. package/src/actor/driver.ts +3 -3
  113. package/src/actor/errors.ts +9 -3
  114. package/src/actor/instance/mod.ts +22 -30
  115. package/src/actor/keys.ts +1 -1
  116. package/src/actor/mod.ts +20 -20
  117. package/src/actor/schema.ts +2 -2
  118. package/src/agent-os/actor/index.ts +38 -18
  119. package/src/agent-os/actor/preview.ts +1 -2
  120. package/src/agent-os/actor/session.ts +2 -2
  121. package/src/agent-os/config.ts +1 -1
  122. package/src/agent-os/fs/database-vfs.ts +1 -1
  123. package/src/agent-os/index.ts +16 -15
  124. package/src/client/actor-common.ts +90 -54
  125. package/src/client/actor-conn.ts +8 -36
  126. package/src/client/actor-handle.ts +67 -50
  127. package/src/client/actor-query.ts +5 -5
  128. package/src/client/errors.ts +1 -1
  129. package/src/client/lifecycle-errors.ts +2 -4
  130. package/src/client/query.ts +1 -1
  131. package/src/client/queue.ts +8 -3
  132. package/src/client/raw-utils.ts +8 -6
  133. package/src/client/resolve-gateway-target.ts +1 -1
  134. package/src/client/utils.ts +2 -7
  135. package/src/common/actor-websocket.ts +3 -1
  136. package/src/common/bare/actor-persist/v1.ts +205 -163
  137. package/src/common/bare/actor-persist/v2.ts +265 -213
  138. package/src/common/bare/actor-persist/v3.ts +176 -172
  139. package/src/common/bare/actor-persist/v4.ts +254 -253
  140. package/src/common/bare/transport/v1.ts +659 -543
  141. package/src/common/client-protocol-versioned.ts +66 -64
  142. package/src/common/database/config.ts +2 -8
  143. package/src/common/database/native-database.ts +1 -1
  144. package/src/common/database/shared.ts +1 -0
  145. package/src/common/encoding.ts +250 -16
  146. package/src/common/eventsource.ts +1 -1
  147. package/src/common/inline-websocket-adapter.ts +14 -13
  148. package/src/common/log.ts +1 -0
  149. package/src/common/router.ts +13 -17
  150. package/src/common/utils.ts +0 -149
  151. package/src/common/websocket-interface.ts +1 -1
  152. package/src/db/mod.ts +1 -1
  153. package/src/drivers/engine/actor-driver.ts +63 -72
  154. package/src/dynamic/instance.ts +32 -0
  155. package/src/dynamic/internal.ts +50 -0
  156. package/src/dynamic/isolate-runtime.ts +66 -0
  157. package/src/dynamic/mod.ts +32 -0
  158. package/src/engine-client/actor-http-client.ts +1 -1
  159. package/src/engine-client/actor-websocket-client.ts +6 -5
  160. package/src/engine-client/api-endpoints.ts +47 -2
  161. package/src/engine-client/api-utils.ts +2 -2
  162. package/src/engine-client/driver.ts +1 -1
  163. package/src/engine-client/mod.ts +6 -3
  164. package/src/engine-client/ws-proxy.ts +4 -4
  165. package/src/inspector/client.browser.ts +5 -11
  166. package/src/inspector/mod.ts +1 -3
  167. package/src/registry/config/envoy.ts +1 -2
  168. package/src/registry/config/index.ts +7 -7
  169. package/src/registry/index.ts +70 -71
  170. package/src/registry/napi-runtime.ts +7 -2
  171. package/src/registry/native-validation.ts +10 -12
  172. package/src/registry/native.ts +179 -153
  173. package/src/registry/process-metrics.ts +238 -0
  174. package/src/registry/runtime.ts +1 -3
  175. package/src/registry/wasm-runtime.ts +3 -2
  176. package/src/registry/write-through-proxy.ts +40 -0
  177. package/src/serde.ts +2 -2
  178. package/src/serverless/configure.ts +18 -7
  179. package/src/test/mod.ts +11 -8
  180. package/src/utils/endpoint-parser.ts +1 -1
  181. package/src/utils/router.ts +1 -1
  182. package/src/utils/serve.ts +4 -5
  183. package/src/utils.ts +1 -2
  184. package/src/workflow/context.ts +61 -33
  185. package/src/workflow/driver.ts +4 -6
  186. package/src/workflow/inspector.ts +4 -3
  187. package/src/workflow/mod.ts +15 -17
  188. package/dist/tsup/chunk-2NDZ7JCR.cjs.map +0 -1
  189. package/dist/tsup/chunk-3YY5S6TV.js.map +0 -1
  190. package/dist/tsup/chunk-4K3MV2MW.cjs.map +0 -1
  191. package/dist/tsup/chunk-BM3EOY7M.js.map +0 -1
  192. package/dist/tsup/chunk-G34LIR7S.js.map +0 -1
  193. package/dist/tsup/chunk-G5RULGYQ.cjs.map +0 -1
  194. package/dist/tsup/chunk-J5P6S2LC.cjs.map +0 -1
  195. package/dist/tsup/chunk-KIWH5H3K.js.map +0 -1
  196. package/dist/tsup/chunk-KU6VKVEK.js.map +0 -1
  197. package/dist/tsup/chunk-PCBNKI2J.js.map +0 -1
  198. package/dist/tsup/chunk-PWFGP2US.cjs.map +0 -1
  199. package/dist/tsup/chunk-T6YVRM4K.js.map +0 -1
  200. package/dist/tsup/chunk-TE4VCDNY.cjs.map +0 -1
  201. package/dist/tsup/chunk-WQ4HNA4W.cjs.map +0 -1
  202. package/dist/tsup/chunk-WU2O2KIE.js.map +0 -1
  203. package/dist/tsup/chunk-XV52XUWU.js.map +0 -1
  204. /package/dist/tsup/{chunk-6BI2MS3S.js.map → chunk-3P2JUHWJ.js.map} +0 -0
  205. /package/dist/tsup/{chunk-CMYS77J6.js.map → chunk-YRQ4F5CD.js.map} +0 -0
@@ -0,0 +1,238 @@
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?: (quantile: string, valueSeconds: number) => void;
26
+ jsSetEventloopUtilization?: (utilization: number) => void;
27
+ jsAddProcessCpuSeconds?: (mode: string, valueSeconds: number) => void;
28
+ jsSetProcessResidentMemoryBytes?: (bytes: number) => void;
29
+ jsSetHeapBytes?: (kind: string, bytes: number) => void;
30
+ jsSetActiveHandles?: (count: number) => void;
31
+ jsSetActiveRequests?: (count: number) => void;
32
+ };
33
+
34
+ type GcPerformanceEntry = {
35
+ duration: number;
36
+ detail?: { kind?: number };
37
+ kind?: number;
38
+ };
39
+
40
+ const processMetricsNapi = napi as OptionalProcessMetricsNapi;
41
+
42
+ // Some napi process-metrics symbols may be missing on older native binaries
43
+ // (the auto-generated index.js destructures them as `undefined` if the
44
+ // underlying `.node` was built before they were added). Guard each call so
45
+ // the metrics collection runs as a no-op instead of throwing
46
+ // `TypeError: napi.jsXxx is not a function` on every interval tick.
47
+ function callIfFn<T extends unknown[]>(
48
+ fn: ((...args: T) => void) | undefined,
49
+ ...args: T
50
+ ): void {
51
+ if (typeof fn === "function") {
52
+ fn(...args);
53
+ }
54
+ }
55
+
56
+ const SCRAPE_INTERVAL_MS = 5_000;
57
+ const HEARTBEAT_INTERVAL_MS = 100;
58
+ const EVENTLOOP_DELAY_RESOLUTION_MS = 20;
59
+ const NS_PER_SECOND = 1e9;
60
+ const US_PER_SECOND = 1e6;
61
+
62
+ // V8 GC kind bitfield from Node's perf_hooks documentation. A `gc` performance
63
+ // entry's `kind` field is one of these values.
64
+ const GC_KIND_NAMES: Record<number, string> = {
65
+ 1: "minor",
66
+ 2: "major",
67
+ 4: "incremental",
68
+ 8: "weakcb",
69
+ };
70
+
71
+ interface ProcessMetricsState {
72
+ scrapeInterval: NodeJS.Timeout;
73
+ heartbeatInterval: NodeJS.Timeout;
74
+ gcObserver: PerformanceObserver;
75
+ eventLoopHistogram: ReturnType<typeof monitorEventLoopDelay>;
76
+ lastCpuUsage: NodeJS.CpuUsage;
77
+ lastEventLoopUtilization: ReturnType<
78
+ typeof performance.eventLoopUtilization
79
+ >;
80
+ }
81
+
82
+ let state: ProcessMetricsState | undefined;
83
+
84
+ export function startProcessMetrics(): void {
85
+ if (state) {
86
+ return;
87
+ }
88
+
89
+ const eventLoopHistogram = monitorEventLoopDelay({
90
+ resolution: EVENTLOOP_DELAY_RESOLUTION_MS,
91
+ });
92
+ eventLoopHistogram.enable();
93
+
94
+ const gcObserver = new PerformanceObserver((list) => {
95
+ for (const entry of list.getEntries()) {
96
+ const gcEntry = entry as GcPerformanceEntry;
97
+ const kind = gcEntry.detail?.kind ?? gcEntry.kind;
98
+ if (typeof kind !== "number") continue;
99
+ const kindName = GC_KIND_NAMES[kind];
100
+ if (!kindName) continue;
101
+ // `entry.duration` is in milliseconds; convert to seconds.
102
+ callIfFn(
103
+ processMetricsNapi.jsObserveGcDuration,
104
+ kindName,
105
+ gcEntry.duration / 1000,
106
+ );
107
+ }
108
+ });
109
+ gcObserver.observe({ entryTypes: ["gc"], buffered: false });
110
+
111
+ const lastCpuUsage = process.cpuUsage();
112
+ const lastEventLoopUtilization = performance.eventLoopUtilization();
113
+
114
+ const heartbeatInterval = setInterval(() => {
115
+ callIfFn(processMetricsNapi.jsSetEventloopHeartbeatTsMs, Date.now());
116
+ }, HEARTBEAT_INTERVAL_MS);
117
+ heartbeatInterval.unref();
118
+
119
+ const scrapeInterval = setInterval(() => {
120
+ try {
121
+ collectAndPush();
122
+ } catch {
123
+ // Collection errors must never bring down the process; metrics
124
+ // are best-effort.
125
+ }
126
+ }, SCRAPE_INTERVAL_MS);
127
+ scrapeInterval.unref();
128
+
129
+ state = {
130
+ scrapeInterval,
131
+ heartbeatInterval,
132
+ gcObserver,
133
+ eventLoopHistogram,
134
+ lastCpuUsage,
135
+ lastEventLoopUtilization,
136
+ };
137
+
138
+ // Emit one snapshot immediately so freshly-scraped instances have data.
139
+ callIfFn(processMetricsNapi.jsSetEventloopHeartbeatTsMs, Date.now());
140
+ try {
141
+ collectAndPush();
142
+ } catch {
143
+ // As above; best-effort.
144
+ }
145
+ }
146
+
147
+ export function stopProcessMetrics(): void {
148
+ if (!state) {
149
+ return;
150
+ }
151
+ clearInterval(state.scrapeInterval);
152
+ clearInterval(state.heartbeatInterval);
153
+ state.gcObserver.disconnect();
154
+ state.eventLoopHistogram.disable();
155
+ state = undefined;
156
+ }
157
+
158
+ function collectAndPush(): void {
159
+ if (!state) return;
160
+
161
+ // Event loop delay quantiles. `monitorEventLoopDelay()` reports values in
162
+ // nanoseconds; convert to seconds. Reset after reading so the next window
163
+ // reflects only the new interval.
164
+ const hist = state.eventLoopHistogram;
165
+ callIfFn(
166
+ processMetricsNapi.jsSetEventloopLagQuantile,
167
+ "p50",
168
+ hist.percentile(50) / NS_PER_SECOND,
169
+ );
170
+ callIfFn(
171
+ processMetricsNapi.jsSetEventloopLagQuantile,
172
+ "p90",
173
+ hist.percentile(90) / NS_PER_SECOND,
174
+ );
175
+ callIfFn(
176
+ processMetricsNapi.jsSetEventloopLagQuantile,
177
+ "p99",
178
+ hist.percentile(99) / NS_PER_SECOND,
179
+ );
180
+ callIfFn(
181
+ processMetricsNapi.jsSetEventloopLagQuantile,
182
+ "max",
183
+ hist.max / NS_PER_SECOND,
184
+ );
185
+ hist.reset();
186
+
187
+ // Event loop utilization delta over the scrape window.
188
+ const nextElu = performance.eventLoopUtilization();
189
+ const eluDelta = performance.eventLoopUtilization(
190
+ nextElu,
191
+ state.lastEventLoopUtilization,
192
+ );
193
+ state.lastEventLoopUtilization = nextElu;
194
+ callIfFn(processMetricsNapi.jsSetEventloopUtilization, eluDelta.utilization);
195
+
196
+ // CPU usage delta. `process.cpuUsage()` returns microseconds.
197
+ const nextCpu = process.cpuUsage();
198
+ const userDeltaUs = nextCpu.user - state.lastCpuUsage.user;
199
+ const systemDeltaUs = nextCpu.system - state.lastCpuUsage.system;
200
+ state.lastCpuUsage = nextCpu;
201
+ if (userDeltaUs > 0) {
202
+ callIfFn(
203
+ processMetricsNapi.jsAddProcessCpuSeconds,
204
+ "user",
205
+ userDeltaUs / US_PER_SECOND,
206
+ );
207
+ }
208
+ if (systemDeltaUs > 0) {
209
+ callIfFn(
210
+ processMetricsNapi.jsAddProcessCpuSeconds,
211
+ "system",
212
+ systemDeltaUs / US_PER_SECOND,
213
+ );
214
+ }
215
+
216
+ // Memory + heap.
217
+ const mem = process.memoryUsage();
218
+ callIfFn(processMetricsNapi.jsSetProcessResidentMemoryBytes, mem.rss);
219
+ callIfFn(processMetricsNapi.jsSetHeapBytes, "used", mem.heapUsed);
220
+ callIfFn(processMetricsNapi.jsSetHeapBytes, "total", mem.heapTotal);
221
+ const heapLimit = getHeapStatistics().heap_size_limit;
222
+ callIfFn(processMetricsNapi.jsSetHeapBytes, "limit", heapLimit);
223
+
224
+ // libuv active handles + requests. These are unstable Node internals
225
+ // guarded behind underscore-prefixed names; if a future Node release
226
+ // removes them the try/catch above keeps the rest of the collection
227
+ // alive.
228
+ const proc = process as unknown as {
229
+ _getActiveHandles?: () => unknown[];
230
+ _getActiveRequests?: () => unknown[];
231
+ };
232
+ if (typeof proc._getActiveHandles === "function") {
233
+ callIfFn(processMetricsNapi.jsSetActiveHandles, proc._getActiveHandles().length);
234
+ }
235
+ if (typeof proc._getActiveRequests === "function") {
236
+ callIfFn(processMetricsNapi.jsSetActiveRequests, proc._getActiveRequests().length);
237
+ }
238
+ }
@@ -310,9 +310,6 @@ export interface CoreRuntime {
310
310
  config: RuntimeServeConfig,
311
311
  ): Promise<void>;
312
312
  shutdownRegistry(registry: RegistryHandle): Promise<void>;
313
- registryActorStopThresholdMs?(
314
- registry: RegistryHandle,
315
- ): Promise<number | undefined>;
316
313
  handleServerlessRequest(
317
314
  registry: RegistryHandle,
318
315
  req: RuntimeServerlessRequest,
@@ -491,6 +488,7 @@ export interface CoreRuntime {
491
488
  ctx: ActorContextHandle,
492
489
  names: string[],
493
490
  options?: RuntimeQueueWaitOptions | undefined | null,
491
+ signal?: CancellationTokenHandle | undefined | null,
494
492
  ): Promise<void>;
495
493
  actorQueueEnqueueAndWait(
496
494
  ctx: ActorContextHandle,
@@ -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,
@@ -764,9 +764,10 @@ export class WasmCoreRuntime implements CoreRuntime {
764
764
  ctx: ActorContextHandle,
765
765
  names: string[],
766
766
  options?: RuntimeQueueWaitOptions | undefined | null,
767
+ signal?: CancellationTokenHandle | undefined | null,
767
768
  ): Promise<void> {
768
769
  const queue = childHandle(asWasmActorContext(ctx), "queue");
769
- await callHandleAsync(queue, "waitForNamesAvailable", names, options);
770
+ await callHandleAsync(queue, "waitForNamesAvailable", names, options, signal);
770
771
  }
771
772
 
772
773
  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;
@@ -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";
@@ -1,41 +1,42 @@
1
1
  // @ts-nocheck
2
- import type { RunContext } from "@/actor/config";
2
+
3
+ import type {
4
+ BranchConfig,
5
+ BranchOutput,
6
+ EntryKindType,
7
+ LoopConfig,
8
+ LoopResult,
9
+ StepConfig,
10
+ TryBlockConfig,
11
+ TryBlockResult,
12
+ TryStepConfig,
13
+ TryStepResult,
14
+ WorkflowContextInterface,
15
+ WorkflowQueueMessage,
16
+ } from "@rivetkit/workflow-engine";
3
17
  import type {
4
18
  QueueFilterName,
5
19
  QueueNextBatchOptions,
6
20
  QueueNextOptions,
7
21
  QueueResultMessageForName,
8
22
  } from "@/actor/config";
9
- import type { Client } from "@/client/client";
10
- import type { Registry } from "@/registry";
23
+ import { RAW_STATE_SYMBOL, type RunContext } from "@/actor/config";
11
24
  import type {
12
- BaseActorDefinition,
13
25
  AnyActorDefinition,
26
+ BaseActorDefinition,
14
27
  } from "@/actor/definition";
15
- import type {
16
- AnyDatabaseProvider,
17
- InferDatabaseClient,
18
- } from "@/common/database/config";
19
28
  import type {
20
29
  EventSchemaConfig,
21
30
  InferEventArgs,
22
31
  InferSchemaMap,
23
32
  QueueSchemaConfig,
24
33
  } from "@/actor/schema";
25
- import type { WorkflowContextInterface } from "@rivetkit/workflow-engine";
34
+ import type { Client } from "@/client/client";
26
35
  import type {
27
- BranchConfig,
28
- BranchOutput,
29
- EntryKindType,
30
- LoopConfig,
31
- LoopResult,
32
- StepConfig,
33
- TryBlockConfig,
34
- TryBlockResult,
35
- TryStepConfig,
36
- TryStepResult,
37
- WorkflowQueueMessage,
38
- } from "@rivetkit/workflow-engine";
36
+ AnyDatabaseProvider,
37
+ InferDatabaseClient,
38
+ } from "@/common/database/config";
39
+ import type { Registry } from "@/registry";
39
40
  import { WORKFLOW_GUARD_KV_KEY } from "./constants";
40
41
 
41
42
  type WorkflowActorQueueNextOptions<
@@ -80,7 +81,9 @@ type ActorWorkflowLoopConfig<
80
81
  TQueues
81
82
  >,
82
83
  state: S,
83
- ) => Promise<LoopResult<S, T> | (S extends undefined ? void : never)>;
84
+ ) => Promise<
85
+ LoopResult<S, T> | (S extends undefined ? undefined | void : never)
86
+ >;
84
87
  };
85
88
 
86
89
  type ActorWorkflowBranchConfig<
@@ -238,7 +241,7 @@ export class ActorWorkflowContext<
238
241
  }
239
242
 
240
243
  async step<T>(
241
- nameOrConfig: string | Parameters<WorkflowContextInterface["step"]>[0],
244
+ nameOrConfig: string | StepConfig<T>,
242
245
  run?: () => Promise<T>,
243
246
  ): Promise<T> {
244
247
  if (typeof nameOrConfig === "string") {
@@ -247,22 +250,20 @@ export class ActorWorkflowContext<
247
250
  }
248
251
  return await this.#wrapActive(() =>
249
252
  this.#inner.step(nameOrConfig, () =>
250
- this.#withActorAccess(run),
253
+ this.#withActorAccessAndStateRollback(run),
251
254
  ),
252
255
  );
253
256
  }
254
257
  const stepConfig = nameOrConfig as StepConfig<T>;
255
258
  const config: StepConfig<T> = {
256
259
  ...stepConfig,
257
- run: () => this.#withActorAccess(stepConfig.run),
260
+ run: () => this.#withActorAccessAndStateRollback(stepConfig.run),
258
261
  };
259
262
  return await this.#wrapActive(() => this.#inner.step(config));
260
263
  }
261
264
 
262
265
  async tryStep<T>(
263
- nameOrConfig:
264
- | string
265
- | Parameters<WorkflowContextInterface["tryStep"]>[0],
266
+ nameOrConfig: string | TryStepConfig<T>,
266
267
  run?: () => Promise<T>,
267
268
  ): Promise<TryStepResult<T>> {
268
269
  if (typeof nameOrConfig === "string") {
@@ -271,14 +272,14 @@ export class ActorWorkflowContext<
271
272
  }
272
273
  return await this.#wrapActive(() =>
273
274
  this.#inner.tryStep(nameOrConfig, () =>
274
- this.#withActorAccess(run),
275
+ this.#withActorAccessAndStateRollback(run),
275
276
  ),
276
277
  );
277
278
  }
278
279
  const stepConfig = nameOrConfig as TryStepConfig<T>;
279
280
  const config: TryStepConfig<T> = {
280
281
  ...stepConfig,
281
- run: () => this.#withActorAccess(stepConfig.run),
282
+ run: () => this.#withActorAccessAndStateRollback(stepConfig.run),
282
283
  };
283
284
  return await this.#wrapActive(() => this.#inner.tryStep(config));
284
285
  }
@@ -329,13 +330,13 @@ export class ActorWorkflowContext<
329
330
  TEvents,
330
331
  TQueues
331
332
  >,
332
- ) => Promise<LoopResult<undefined, T> | void>,
333
+ ) => Promise<LoopResult<undefined, T> | undefined | void>,
333
334
  ): Promise<T>;
334
335
  async loop<T>(
335
336
  name: string,
336
337
  run: (
337
338
  ctx: WorkflowContextInterface,
338
- ) => Promise<LoopResult<undefined, T> | void>,
339
+ ) => Promise<LoopResult<undefined, T> | undefined | void>,
339
340
  ): Promise<T>;
340
341
  async loop<S, T>(
341
342
  config: ActorWorkflowLoopConfig<
@@ -379,7 +380,7 @@ export class ActorWorkflowContext<
379
380
  TEvents,
380
381
  TQueues
381
382
  >,
382
- ) => Promise<LoopResult<undefined, any> | void>,
383
+ ) => Promise<LoopResult<undefined, any> | undefined | void>,
383
384
  ): Promise<any> {
384
385
  if (typeof nameOrConfig === "string") {
385
386
  if (!run) {
@@ -612,6 +613,33 @@ export class ActorWorkflowContext<
612
613
  }
613
614
  }
614
615
 
616
+ async #withActorAccessAndStateRollback<T>(
617
+ run: () => Promise<T>,
618
+ ): Promise<T> {
619
+ let stateSnapshot: { state: TState } | null = null;
620
+ try {
621
+ stateSnapshot = { state: this.#runCtx[RAW_STATE_SYMBOL]() };
622
+ } catch (error) {
623
+ this.#runCtx.log.debug({
624
+ msg: "failed to get state, likely due to being stateless workflow",
625
+ error,
626
+ });
627
+ }
628
+ if (stateSnapshot) {
629
+ stateSnapshot.state = structuredClone(stateSnapshot.state);
630
+ }
631
+ const varsSnapshot = structuredClone(this.#runCtx.vars);
632
+ try {
633
+ return await this.#withActorAccess(run);
634
+ } catch (error) {
635
+ if (stateSnapshot) {
636
+ this.#runCtx.state = stateSnapshot.state;
637
+ }
638
+ this.#runCtx.vars = varsSnapshot;
639
+ throw error;
640
+ }
641
+ }
642
+
615
643
  #ensureActorAccess(feature: string): void {
616
644
  if (!this.#allowActorAccess) {
617
645
  this.#guardViolation = true;
@@ -1,10 +1,5 @@
1
1
  // @ts-nocheck
2
- import type { RunContext } from "@/actor/config";
3
- import type {
4
- AnyActorInstance,
5
- AnyStaticActorInstance,
6
- } from "@/actor/definition";
7
- import { makeWorkflowKey, workflowStoragePrefix } from "@/actor/keys";
2
+
8
3
  import type {
9
4
  EngineDriver,
10
5
  KVEntry,
@@ -12,6 +7,9 @@ import type {
12
7
  Message,
13
8
  WorkflowMessageDriver,
14
9
  } from "@rivetkit/workflow-engine";
10
+ import type { RunContext } from "@/actor/config";
11
+ import type { AnyStaticActorInstance } from "@/actor/definition";
12
+ import { makeWorkflowKey, workflowStoragePrefix } from "@/actor/keys";
15
13
 
16
14
  const WORKFLOW_STORAGE_PREFIX = workflowStoragePrefix();
17
15