rivetkit 2.3.0-rc.8 → 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 (221) hide show
  1. package/dist/browser/client.d.ts +481 -74
  2. package/dist/browser/client.js +174 -148
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +47 -18
  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 +2160 -2086
  11. package/dist/tsup/agent-os/index.cjs.map +1 -1
  12. package/dist/tsup/agent-os/index.d.cts +479 -73
  13. package/dist/tsup/agent-os/index.d.ts +479 -73
  14. package/dist/tsup/agent-os/index.js +2160 -2086
  15. package/dist/tsup/agent-os/index.js.map +1 -1
  16. package/dist/tsup/{chunk-KY3CERZR.js → chunk-2OTRTA3J.js} +7 -21
  17. package/dist/tsup/chunk-2OTRTA3J.js.map +1 -0
  18. package/dist/tsup/{chunk-HGW6PBWR.cjs → chunk-3677IIOV.cjs} +11 -25
  19. package/dist/tsup/chunk-3677IIOV.cjs.map +1 -0
  20. package/dist/tsup/{chunk-OT7FF6GB.cjs → chunk-47HHIEXH.cjs} +24 -9
  21. package/dist/tsup/chunk-47HHIEXH.cjs.map +1 -0
  22. package/dist/tsup/{chunk-EMFKMVJR.js → chunk-4JDSFJS5.js} +69 -58
  23. package/dist/tsup/chunk-4JDSFJS5.js.map +1 -0
  24. package/dist/tsup/{chunk-7HLFSAJP.cjs → chunk-7QKCIVAY.cjs} +225 -214
  25. package/dist/tsup/chunk-7QKCIVAY.cjs.map +1 -0
  26. package/dist/tsup/{chunk-AWTPTUQ7.cjs → chunk-B6VUNZUD.cjs} +10 -10
  27. package/dist/tsup/{chunk-AWTPTUQ7.cjs.map → chunk-B6VUNZUD.cjs.map} +1 -1
  28. package/dist/tsup/{chunk-D3T3ZBSY.js → chunk-BEI24WTI.js} +2 -2
  29. package/dist/tsup/{chunk-TMLOKTRB.js → chunk-BRP62GZC.js} +1 -1
  30. package/dist/tsup/chunk-BRP62GZC.js.map +1 -0
  31. package/dist/tsup/{chunk-D5G75T7J.js → chunk-DPIMKYNB.js} +61 -2
  32. package/dist/tsup/chunk-DPIMKYNB.js.map +1 -0
  33. package/dist/tsup/{chunk-BATTOVHF.cjs → chunk-DXXJPH55.cjs} +40 -13
  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-4BPKKZJO.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-63WNTDRC.cjs → chunk-KORQB2IR.cjs} +1 -1
  42. package/dist/tsup/{chunk-63WNTDRC.cjs.map → chunk-KORQB2IR.cjs.map} +1 -1
  43. package/dist/tsup/{chunk-6TQSSJ4F.cjs → chunk-LVTBW2RE.cjs} +3 -3
  44. package/dist/tsup/{chunk-6TQSSJ4F.cjs.map → chunk-LVTBW2RE.cjs.map} +1 -1
  45. package/dist/tsup/{chunk-4JU3IPG2.js → chunk-MEHBWPLJ.js} +6 -6
  46. package/dist/tsup/chunk-MEHBWPLJ.js.map +1 -0
  47. package/dist/tsup/{chunk-SRNOPUC6.cjs → chunk-NIY3RSPX.cjs} +62 -3
  48. package/dist/tsup/chunk-NIY3RSPX.cjs.map +1 -0
  49. package/dist/tsup/{chunk-UZXQEGVJ.js → chunk-P2GNQ4RN.js} +4 -4
  50. package/dist/tsup/{chunk-UZXQEGVJ.js.map → chunk-P2GNQ4RN.js.map} +1 -1
  51. package/dist/tsup/{chunk-VUGENVIK.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-GBG63SUG.js → chunk-VTTFNQQI.js} +32 -5
  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-Ak1lv4gF.d.ts → config-BxWAw3iH.d.ts} +512 -27
  68. package/dist/tsup/{config-DU_xj4qZ.d.cts → config-CZQQ-mso.d.cts} +512 -27
  69. package/dist/tsup/{config-CxjGYf4K.d.ts → config-D49x8NpL.d.cts} +1 -2
  70. package/dist/tsup/{config-CxjGYf4K.d.cts → config-D49x8NpL.d.ts} +1 -2
  71. package/dist/tsup/{context-DAAp4Lpg.d.ts → context-Bw7xq8w3.d.cts} +8 -8
  72. package/dist/tsup/{context-Dt_L55q8.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 +615 -348
  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 +511 -244
  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 +279 -279
  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 +380 -380
  118. package/dist/tsup/workflow/mod.js.map +1 -1
  119. package/package.json +29 -9
  120. package/src/actor/config.ts +156 -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 +8 -2
  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/config.ts +1 -1
  132. package/src/agent-os/fs/database-vfs.ts +1 -1
  133. package/src/agent-os/index.ts +16 -15
  134. package/src/client/actor-common.ts +87 -54
  135. package/src/client/actor-conn.ts +11 -11
  136. package/src/client/actor-handle.ts +69 -52
  137. package/src/client/actor-query.ts +5 -5
  138. package/src/client/errors.ts +1 -1
  139. package/src/client/lifecycle-errors.ts +2 -4
  140. package/src/client/query.ts +1 -1
  141. package/src/client/queue.ts +8 -4
  142. package/src/client/raw-utils.ts +8 -6
  143. package/src/client/resolve-gateway-target.ts +1 -1
  144. package/src/client/utils.ts +2 -6
  145. package/src/common/actor-websocket.ts +3 -1
  146. package/src/common/bare/actor-persist/v1.ts +205 -163
  147. package/src/common/bare/actor-persist/v2.ts +265 -213
  148. package/src/common/bare/actor-persist/v3.ts +176 -172
  149. package/src/common/bare/actor-persist/v4.ts +254 -253
  150. package/src/common/bare/transport/v1.ts +659 -543
  151. package/src/common/client-protocol-versioned.ts +66 -64
  152. package/src/common/database/config.ts +2 -8
  153. package/src/common/database/native-database.ts +1 -1
  154. package/src/common/database/shared.ts +1 -0
  155. package/src/common/encoding.ts +13 -17
  156. package/src/common/engine.ts +28 -1
  157. package/src/common/eventsource.ts +1 -1
  158. package/src/common/inline-websocket-adapter.ts +3 -2
  159. package/src/common/router.ts +13 -17
  160. package/src/common/utils.ts +1 -2
  161. package/src/common/websocket-interface.ts +1 -1
  162. package/src/db/mod.ts +1 -1
  163. package/src/devtools-loader/index.ts +4 -7
  164. package/src/devtools-loader/serve-devtools.ts +26 -0
  165. package/src/drivers/engine/actor-driver.ts +48 -46
  166. package/src/dynamic/instance.ts +32 -0
  167. package/src/dynamic/internal.ts +50 -0
  168. package/src/dynamic/isolate-runtime.ts +66 -0
  169. package/src/dynamic/mod.ts +32 -0
  170. package/src/engine-client/actor-http-client.ts +3 -3
  171. package/src/engine-client/actor-websocket-client.ts +5 -5
  172. package/src/engine-client/api-endpoints.ts +51 -2
  173. package/src/engine-client/api-utils.ts +2 -2
  174. package/src/engine-client/driver.ts +1 -1
  175. package/src/engine-client/mod.ts +5 -3
  176. package/src/engine-client/ws-proxy.ts +9 -4
  177. package/src/inspector/client.browser.ts +5 -11
  178. package/src/inspector/mod.ts +1 -3
  179. package/src/inspector-tab/mod.ts +315 -0
  180. package/src/registry/config/envoy.ts +1 -2
  181. package/src/registry/config/index.ts +40 -16
  182. package/src/registry/index.ts +226 -83
  183. package/src/registry/napi-runtime.ts +46 -12
  184. package/src/registry/native-validation.ts +10 -12
  185. package/src/registry/native.ts +307 -164
  186. package/src/registry/process-metrics.ts +90 -23
  187. package/src/registry/runtime.ts +53 -6
  188. package/src/registry/wasm-runtime.ts +30 -3
  189. package/src/serde.ts +1 -1
  190. package/src/serverless/configure.ts +18 -7
  191. package/src/test/mod.ts +11 -8
  192. package/src/utils/endpoint-parser.ts +1 -1
  193. package/src/utils/env-vars.ts +6 -0
  194. package/src/utils/router.ts +1 -1
  195. package/src/utils/serve.ts +4 -5
  196. package/src/utils.ts +1 -2
  197. package/src/workflow/context.ts +30 -29
  198. package/src/workflow/driver.ts +4 -6
  199. package/src/workflow/inspector.ts +2 -2
  200. package/src/workflow/mod.ts +15 -17
  201. package/dist/tsup/chunk-2NDZ7JCR.cjs.map +0 -1
  202. package/dist/tsup/chunk-3YY5S6TV.js.map +0 -1
  203. package/dist/tsup/chunk-4BPKKZJO.cjs.map +0 -1
  204. package/dist/tsup/chunk-4JU3IPG2.js.map +0 -1
  205. package/dist/tsup/chunk-7HLFSAJP.cjs.map +0 -1
  206. package/dist/tsup/chunk-BATTOVHF.cjs.map +0 -1
  207. package/dist/tsup/chunk-D5G75T7J.js.map +0 -1
  208. package/dist/tsup/chunk-EMFKMVJR.js.map +0 -1
  209. package/dist/tsup/chunk-GBG63SUG.js.map +0 -1
  210. package/dist/tsup/chunk-HGW6PBWR.cjs.map +0 -1
  211. package/dist/tsup/chunk-KY3CERZR.js.map +0 -1
  212. package/dist/tsup/chunk-OT7FF6GB.cjs.map +0 -1
  213. package/dist/tsup/chunk-PCBNKI2J.js.map +0 -1
  214. package/dist/tsup/chunk-SRNOPUC6.cjs.map +0 -1
  215. package/dist/tsup/chunk-TMLOKTRB.js.map +0 -1
  216. package/dist/tsup/chunk-VUGENVIK.js.map +0 -1
  217. package/dist/tsup/process-metrics-NW754INA.js +0 -118
  218. package/dist/tsup/process-metrics-NW754INA.js.map +0 -1
  219. package/dist/tsup/process-metrics-TYAGKCEJ.cjs +0 -118
  220. package/dist/tsup/process-metrics-TYAGKCEJ.cjs.map +0 -1
  221. /package/dist/tsup/{chunk-D3T3ZBSY.js.map → chunk-BEI24WTI.js.map} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { Hono } from "hono";
2
- import { ENGINE_ENDPOINT } from "@/common/engine";
2
+ import { isLocalEngineEndpoint } from "@/common/engine";
3
3
  import { configureServerlessPool } from "@/serverless/configure";
4
4
  import { detectRuntime, VERSION } from "@/utils";
5
5
  import { crossPlatformServe, loadRuntimeServeStatic } from "@/utils/serve";
@@ -40,13 +40,25 @@ export interface ServerlessHandler {
40
40
  fetch: FetchHandler;
41
41
  }
42
42
 
43
- export interface RegistryDiagnostics {
44
- mode: string;
45
- envoyActiveActorCount?: number | null;
43
+ export interface RegistryRoutes {
44
+ health(): Promise<Response>;
45
+ metadata(): Promise<Response>;
46
+ prometheusMetrics(request?: Request): Promise<Response>;
47
+ }
48
+
49
+ /**
50
+ * Injectable dependencies for {@link Registry}. Production code uses the
51
+ * defaults. Tests override `buildConfiguredRegistry` to drive lifecycle
52
+ * orchestration against a fake `CoreRuntime` without an engine.
53
+ */
54
+ export interface RegistryDeps {
55
+ buildConfiguredRegistry: typeof buildConfiguredRegistry;
46
56
  }
47
57
 
48
58
  export class Registry<A extends RegistryActors> {
49
59
  #config: RegistryConfigInput<A>;
60
+ #buildConfiguredRegistry: typeof buildConfiguredRegistry;
61
+ public readonly routes: RegistryRoutes;
50
62
 
51
63
  get config(): RegistryConfigInput<A> {
52
64
  return this.#config;
@@ -65,20 +77,30 @@ export class Registry<A extends RegistryActors> {
65
77
  #shutdownInFlight: Promise<void> | null = null;
66
78
  #signalHandlers: Partial<Record<ShutdownSignal, () => void>> = {};
67
79
 
68
- constructor(config: RegistryConfigInput<A>) {
80
+ constructor(config: RegistryConfigInput<A>, deps?: Partial<RegistryDeps>) {
69
81
  this.#config = config;
82
+ this.#buildConfiguredRegistry =
83
+ deps?.buildConfiguredRegistry ?? buildConfiguredRegistry;
84
+ this.routes = {
85
+ health: () => this.#healthRoute(),
86
+ metadata: () => this.#metadataRoute(),
87
+ prometheusMetrics: (request?: Request) =>
88
+ this.#prometheusMetricsRoute(request),
89
+ };
70
90
  }
71
91
 
72
- #ensureServerlessPoolConfigured(config: RegistryConfig): Promise<void> | undefined {
92
+ #ensureServerlessPoolConfigured(
93
+ config: RegistryConfig,
94
+ ): Promise<void> | undefined {
73
95
  if (!config.configurePool) return undefined;
74
96
 
75
97
  if (!this.#configureServerlessPoolPromise) {
76
- this.#configureServerlessPoolPromise = configureServerlessPool(config).catch(
77
- (error) => {
78
- this.#configureServerlessPoolPromise = undefined;
79
- throw error;
80
- },
81
- );
98
+ this.#configureServerlessPoolPromise = configureServerlessPool(
99
+ config,
100
+ ).catch((error) => {
101
+ this.#configureServerlessPoolPromise = undefined;
102
+ throw error;
103
+ });
82
104
  this.#configureServerlessPoolPromise.catch(() => {});
83
105
  }
84
106
 
@@ -100,7 +122,8 @@ export class Registry<A extends RegistryActors> {
100
122
  this.#printWelcome(config, "serverless");
101
123
 
102
124
  if (!this.#runtimeServerlessPromise) {
103
- this.#runtimeServerlessPromise = buildConfiguredRegistry(config);
125
+ this.#runtimeServerlessPromise =
126
+ this.#buildConfiguredRegistry(config);
104
127
  }
105
128
 
106
129
  const { runtime, registry, serveConfig } =
@@ -114,12 +137,13 @@ export class Registry<A extends RegistryActors> {
114
137
  serveConfig.serverlessBasePath ?? "/api/rivet",
115
138
  );
116
139
  const isEngineMetadataRequest =
117
- request.headers.get("user-agent")?.startsWith("RivetEngine/") ?? false;
140
+ request.headers.get("user-agent")?.startsWith("RivetEngine/") ??
141
+ false;
118
142
 
119
143
  if (isStartRequest) {
120
144
  try {
121
145
  await this.#ensureServerlessPoolConfigured(config);
122
- } catch (error) {
146
+ } catch (_error) {
123
147
  return new Response(
124
148
  JSON.stringify({
125
149
  group: "guard",
@@ -261,7 +285,7 @@ export class Registry<A extends RegistryActors> {
261
285
  if (isMetadataRequest && !isEngineMetadataRequest) {
262
286
  try {
263
287
  await this.#ensureServerlessPoolConfigured(config);
264
- } catch (error) {
288
+ } catch (_error) {
265
289
  return new Response(
266
290
  JSON.stringify({
267
291
  group: "guard",
@@ -327,21 +351,104 @@ export class Registry<A extends RegistryActors> {
327
351
  await crossPlatformServe(config, port, app, runtime);
328
352
  }
329
353
 
330
- public async diagnostics(): Promise<RegistryDiagnostics> {
354
+ /**
355
+ * Returns a health response suitable for mounting in a user-owned router.
356
+ */
357
+ async #healthRoute(): Promise<Response> {
358
+ const configured = await this.#activeConfiguredRegistry();
359
+ if (!configured) {
360
+ return jsonRouteResponse(503, {
361
+ status: "not_started",
362
+ runtime: "rivetkit",
363
+ version: VERSION,
364
+ });
365
+ }
366
+
367
+ const { runtime, registry } = configured;
368
+ if (!runtime.registryHealth) {
369
+ return jsonRouteResponse(501, {
370
+ status: "unsupported",
371
+ runtime: "rivetkit",
372
+ version: VERSION,
373
+ });
374
+ }
375
+
376
+ const response = await runtime.registryHealth(registry);
377
+ return new Response(new Uint8Array(response.body), {
378
+ status: response.status,
379
+ headers: response.headers,
380
+ });
381
+ }
382
+
383
+ /**
384
+ * Returns serverless metadata suitable for mounting in a user-owned router.
385
+ */
386
+ async #metadataRoute(): Promise<Response> {
387
+ const configured = await this.#activeConfiguredRegistry();
388
+ if (!configured) {
389
+ return new Response("registry not started\n", {
390
+ status: 503,
391
+ headers: { "content-type": "text/plain; charset=utf-8" },
392
+ });
393
+ }
394
+
395
+ const { runtime, registry } = configured;
396
+ if (!runtime.registryMetadata) {
397
+ return new Response("metadata is not supported by this runtime\n", {
398
+ status: 501,
399
+ headers: { "content-type": "text/plain; charset=utf-8" },
400
+ });
401
+ }
402
+
403
+ const response = await runtime.registryMetadata(registry);
404
+ return new Response(new Uint8Array(response.body), {
405
+ status: response.status,
406
+ headers: response.headers,
407
+ });
408
+ }
409
+
410
+ /**
411
+ * Returns a Prometheus metrics response suitable for mounting in a user-owned router.
412
+ */
413
+ async #prometheusMetricsRoute(_request?: Request): Promise<Response> {
414
+ const configured = await this.#activeConfiguredRegistry();
415
+ if (!configured) {
416
+ return new Response("registry not started\n", {
417
+ status: 503,
418
+ headers: { "content-type": "text/plain; charset=utf-8" },
419
+ });
420
+ }
421
+
422
+ const { runtime, registry } = configured;
423
+ if (!runtime.registryMetrics) {
424
+ return new Response("metrics are not supported by this runtime\n", {
425
+ status: 501,
426
+ headers: { "content-type": "text/plain; charset=utf-8" },
427
+ });
428
+ }
429
+
430
+ const response = await runtime.registryMetrics(registry);
431
+ return new Response(new Uint8Array(response.body), {
432
+ status: response.status,
433
+ headers: response.headers,
434
+ });
435
+ }
436
+
437
+ async #activeConfiguredRegistry(): Promise<
438
+ Awaited<ReturnType<typeof buildConfiguredRegistry>> | undefined
439
+ > {
331
440
  const candidates = [
332
441
  this.#runtimeServerlessPromise,
333
442
  this.#runtimeServeConfiguredPromise,
334
- ].filter((candidate): candidate is ReturnType<typeof buildConfiguredRegistry> =>
335
- candidate !== undefined
443
+ ].filter(
444
+ (
445
+ candidate,
446
+ ): candidate is ReturnType<typeof buildConfiguredRegistry> =>
447
+ candidate !== undefined,
336
448
  );
337
449
 
338
- for (const candidate of candidates) {
339
- const { runtime, registry } = await candidate;
340
- const diagnostics = await runtime.registryDiagnostics?.(registry);
341
- if (diagnostics) return diagnostics;
342
- }
343
-
344
- return { mode: "not_started", envoyActiveActorCount: null };
450
+ if (candidates.length === 0) return undefined;
451
+ return await candidates[0]!;
345
452
  }
346
453
 
347
454
  /**
@@ -349,7 +456,8 @@ export class Registry<A extends RegistryActors> {
349
456
  */
350
457
  #startEnvoy(config: RegistryConfig, printWelcome: boolean) {
351
458
  if (!this.#runtimeServePromise) {
352
- const configuredRegistryPromise = buildConfiguredRegistry(config);
459
+ const configuredRegistryPromise =
460
+ this.#buildConfiguredRegistry(config);
353
461
  this.#runtimeServeConfiguredPromise = configuredRegistryPromise;
354
462
  this.#runtimeServePromise = configuredRegistryPromise
355
463
  .then(async ({ runtime, registry, serveConfig }) => {
@@ -367,17 +475,14 @@ export class Registry<A extends RegistryActors> {
367
475
  // does not install handlers because it runs on Workers/Vercel/Deno
368
476
  // Deploy where `process.on` is absent or forbidden; those platforms
369
477
  // own their own signal policy.
370
- this.#installSignalHandlers(config, configuredRegistryPromise);
478
+ this.#installSignalHandlers(config);
371
479
  }
372
480
  if (printWelcome) {
373
481
  this.#printWelcome(config, "serverful");
374
482
  }
375
483
  }
376
484
 
377
- #installSignalHandlers(
378
- config: RegistryConfig,
379
- configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
380
- ): void {
485
+ #installSignalHandlers(config: RegistryConfig): void {
381
486
  if (this.#shutdownInstalled) return;
382
487
  if (config.shutdown?.disableSignalHandlers) return;
383
488
  // Guard against non-Node runtimes (Workers/Edge) where `process` may
@@ -392,12 +497,7 @@ export class Registry<A extends RegistryActors> {
392
497
  this.#shutdownInstalled = true;
393
498
 
394
499
  const install = (signal: ShutdownSignal) => {
395
- const handler = () =>
396
- this.#onShutdownSignal(
397
- signal,
398
- config,
399
- configuredRegistryPromise,
400
- );
500
+ const handler = () => this.#onShutdownSignal(signal, config);
401
501
  this.#signalHandlers[signal] = handler;
402
502
  process.on(signal, handler);
403
503
  };
@@ -405,37 +505,67 @@ export class Registry<A extends RegistryActors> {
405
505
  install("SIGTERM");
406
506
  }
407
507
 
408
- #onShutdownSignal(
409
- signal: ShutdownSignal,
410
- config: RegistryConfig,
411
- configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
412
- ): void {
508
+ #onShutdownSignal(signal: ShutdownSignal, config: RegistryConfig): void {
413
509
  if (this.#shutdownInFlight !== null) {
414
- // Second delivery of the same (or another) shutdown signal.
415
- // Remove our handler only, preserving any user-installed listeners.
416
- // PID 1 must exit directly because re-raised default signals can be
510
+ // Second delivery of the same (or another) shutdown signal, or a
511
+ // drain already started by an explicit `shutdown()` call. Remove
512
+ // our handler only, preserving any user-installed listeners. PID 1
513
+ // must exit directly because re-raised default signals can be
417
514
  // swallowed by the container signal path.
418
515
  this.#removeSignalHandlers();
419
516
  finishShutdownSignal(signal);
420
517
  return;
421
518
  }
422
- this.#shutdownInFlight = this.#runShutdown(
423
- signal,
424
- config,
425
- configuredRegistryPromise,
426
- ).catch((error) => {
427
- logger().warn({ error }, "shutdown error");
519
+ this.#shutdownInFlight = this.#drain(config)
520
+ .catch((err) => {
521
+ logger().warn({ err }, "shutdown error");
522
+ })
523
+ .then(() => {
524
+ this.#removeSignalHandlers();
525
+ finishShutdownSignal(signal);
526
+ });
527
+ }
528
+
529
+ /**
530
+ * Gracefully drains all live registries.
531
+ *
532
+ * Programmatic counterpart to the SIGINT/SIGTERM handlers: tears down
533
+ * every live `CoreRegistry` (both `start()` and `handler()` modes) and
534
+ * waits for the serve promise to resolve, all bounded by the shutdown
535
+ * grace period. Unlike a signal-driven shutdown, this does not re-raise a
536
+ * signal or exit the process. The caller owns process lifetime.
537
+ *
538
+ * Idempotent: concurrent or repeated calls share a single drain. Safe to
539
+ * call even if nothing has been started.
540
+ *
541
+ * @example
542
+ * ```ts
543
+ * const registry = setup({ use: { counter } });
544
+ * registry.start();
545
+ * // ...later, on your own shutdown trigger:
546
+ * await registry.shutdown();
547
+ * ```
548
+ */
549
+ public async shutdown(): Promise<void> {
550
+ if (this.#shutdownInFlight !== null) return this.#shutdownInFlight;
551
+ const config = this.parseConfig();
552
+ // Uninstall our signal handlers so a later SIGINT/SIGTERM does not
553
+ // re-trigger a drain on already-torn-down registries. Subsequent
554
+ // signals fall back to Node's default termination behavior.
555
+ this.#removeSignalHandlers();
556
+ this.#shutdownInFlight = this.#drain(config).catch((err) => {
557
+ logger().warn({ err }, "shutdown error");
428
558
  });
559
+ return this.#shutdownInFlight;
429
560
  }
430
561
 
431
- async #runShutdown(
432
- signal: ShutdownSignal,
433
- config: RegistryConfig,
434
- configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
435
- ): Promise<void> {
562
+ async #drain(config: RegistryConfig): Promise<void> {
563
+ const modeAPromise = this.#runtimeServeConfiguredPromise;
564
+ const modeBPromise = this.#runtimeServerlessPromise;
565
+
436
566
  const gracePeriodMs =
437
567
  config.shutdown?.gracePeriodMs ??
438
- (await this.#actorStopThresholdMs(configuredRegistryPromise)) ??
568
+ (await this.#actorStopThresholdMs(modeAPromise ?? modeBPromise)) ??
439
569
  30 * 60 * 1000;
440
570
  // Race the entire drain sequence (both modes + serve promise) against
441
571
  // a single grace ceiling. By default, this uses the engine-provided
@@ -443,29 +573,29 @@ export class Registry<A extends RegistryActors> {
443
573
  const drain = async () => {
444
574
  // Shut down every live `CoreRegistry` we know about. Mode A
445
575
  // (`start()`) and Mode B (`handler()`) each build a separate
446
- // runtime registry, so one signal handler fans out to both to
447
- // honor the spec invariant "single shutdown tears down both modes".
448
- const registries: Promise<void>[] = [
449
- (async () => {
450
- try {
451
- const { runtime, registry } =
452
- await configuredRegistryPromise;
453
- await runtime.shutdownRegistry(registry);
454
- } catch (error) {
455
- logger().warn(
456
- { error },
457
- "runtime registry shutdown errored (mode A)",
458
- );
459
- }
460
- })(),
461
- ];
462
- const runtimeServerlessPromise = this.#runtimeServerlessPromise;
463
- if (runtimeServerlessPromise !== undefined) {
576
+ // runtime registry, so one drain fans out to both to honor the
577
+ // spec invariant "single shutdown tears down both modes".
578
+ const registries: Promise<void>[] = [];
579
+ if (modeAPromise !== undefined) {
580
+ registries.push(
581
+ (async () => {
582
+ try {
583
+ const { runtime, registry } = await modeAPromise;
584
+ await runtime.shutdownRegistry(registry);
585
+ } catch (err) {
586
+ logger().warn(
587
+ { err },
588
+ "runtime registry shutdown errored (mode A)",
589
+ );
590
+ }
591
+ })(),
592
+ );
593
+ }
594
+ if (modeBPromise !== undefined) {
464
595
  registries.push(
465
596
  (async () => {
466
597
  try {
467
- const { runtime, registry } =
468
- await runtimeServerlessPromise;
598
+ const { runtime, registry } = await modeBPromise;
469
599
  await runtime.shutdownRegistry(registry);
470
600
  } catch (err) {
471
601
  logger().warn(
@@ -492,13 +622,14 @@ export class Registry<A extends RegistryActors> {
492
622
  setTimeout(resolve, gracePeriodMs).unref?.(),
493
623
  ),
494
624
  ]);
495
- this.#removeSignalHandlers();
496
- finishShutdownSignal(signal);
497
625
  }
498
626
 
499
627
  async #actorStopThresholdMs(
500
- configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
628
+ configuredRegistryPromise:
629
+ | ReturnType<typeof buildConfiguredRegistry>
630
+ | undefined,
501
631
  ): Promise<number | undefined> {
632
+ if (configuredRegistryPromise === undefined) return undefined;
502
633
  try {
503
634
  const { runtime, registry } = await configuredRegistryPromise;
504
635
  const thresholdMs =
@@ -569,7 +700,9 @@ export class Registry<A extends RegistryActors> {
569
700
 
570
701
  if (config.endpoint) {
571
702
  const endpointType =
572
- config.endpoint === ENGINE_ENDPOINT ? "local native" : "remote";
703
+ config.startEngine || isLocalEngineEndpoint(config.endpoint)
704
+ ? "local native"
705
+ : "remote";
573
706
  logLine("Endpoint", `${config.endpoint} (${endpointType})`);
574
707
  }
575
708
 
@@ -590,7 +723,10 @@ function isServerlessStartRequest(request: Request, basePath: string): boolean {
590
723
  return parsed.pathname === `${normalizedBase}/start`;
591
724
  }
592
725
 
593
- function isServerlessMetadataRequest(request: Request, basePath: string): boolean {
726
+ function isServerlessMetadataRequest(
727
+ request: Request,
728
+ basePath: string,
729
+ ): boolean {
594
730
  if (request.method !== "GET") return false;
595
731
  const parsed = new URL(request.url);
596
732
  const normalizedBase =
@@ -598,6 +734,13 @@ function isServerlessMetadataRequest(request: Request, basePath: string): boolea
598
734
  return parsed.pathname === `${normalizedBase}/metadata`;
599
735
  }
600
736
 
737
+ function jsonRouteResponse(status: number, body: unknown): Response {
738
+ return new Response(JSON.stringify(body), {
739
+ status,
740
+ headers: { "content-type": "application/json" },
741
+ });
742
+ }
743
+
601
744
  export function setup<A extends RegistryActors>(
602
745
  input: RegistryConfigInput<A>,
603
746
  ): Registry<A> {
@@ -23,8 +23,8 @@ import type {
23
23
  RuntimeQueueNextBatchOptions,
24
24
  RuntimeQueueTryNextBatchOptions,
25
25
  RuntimeQueueWaitOptions,
26
+ RuntimeRegistryRouteResponse,
26
27
  RuntimeRequestSaveOpts,
27
- RuntimeRegistryDiagnostics,
28
28
  RuntimeServeConfig,
29
29
  RuntimeServerlessRequest,
30
30
  RuntimeServerlessResponseHead,
@@ -202,16 +202,6 @@ export class NapiCoreRuntime implements CoreRuntime {
202
202
  await asNativeRegistry(registry).shutdown();
203
203
  }
204
204
 
205
- async registryDiagnostics(
206
- registry: RegistryHandle,
207
- ): Promise<RuntimeRegistryDiagnostics> {
208
- const diagnostics = await asNativeRegistry(registry).diagnostics();
209
- return {
210
- mode: diagnostics.mode,
211
- envoyActiveActorCount: diagnostics.envoyActiveActorCount,
212
- };
213
- }
214
-
215
205
  async registryActorStopThresholdMs(
216
206
  registry: RegistryHandle,
217
207
  ): Promise<number | undefined> {
@@ -221,6 +211,39 @@ export class NapiCoreRuntime implements CoreRuntime {
221
211
  );
222
212
  }
223
213
 
214
+ async registryHealth(
215
+ registry: RegistryHandle,
216
+ ): Promise<RuntimeRegistryRouteResponse> {
217
+ const response = await asNativeRegistry(registry).health();
218
+ return {
219
+ status: response.status,
220
+ headers: response.headers,
221
+ body: response.body,
222
+ };
223
+ }
224
+
225
+ async registryMetadata(
226
+ registry: RegistryHandle,
227
+ ): Promise<RuntimeRegistryRouteResponse> {
228
+ const response = asNativeRegistry(registry).metadata();
229
+ return {
230
+ status: response.status,
231
+ headers: response.headers,
232
+ body: response.body,
233
+ };
234
+ }
235
+
236
+ async registryMetrics(
237
+ registry: RegistryHandle,
238
+ ): Promise<RuntimeRegistryRouteResponse> {
239
+ const response = asNativeRegistry(registry).metrics();
240
+ return {
241
+ status: response.status,
242
+ headers: response.headers,
243
+ body: response.body,
244
+ };
245
+ }
246
+
224
247
  async handleServerlessRequest(
225
248
  registry: RegistryHandle,
226
249
  req: RuntimeServerlessRequest,
@@ -429,6 +452,12 @@ export class NapiCoreRuntime implements CoreRuntime {
429
452
  return await asNativeActorContext(ctx).waitForTrackedShutdownWork();
430
453
  }
431
454
 
455
+ async actorWaitForTrackedShutdownWorkUnbounded(
456
+ ctx: ActorContextHandle,
457
+ ): Promise<void> {
458
+ await asNativeActorContext(ctx).waitForTrackedShutdownWorkUnbounded();
459
+ }
460
+
432
461
  actorKeepAwake(ctx: ActorContextHandle, promise: Promise<unknown>): void {
433
462
  asNativeActorContext(ctx).keepAwake(promise);
434
463
  }
@@ -654,10 +683,15 @@ export class NapiCoreRuntime implements CoreRuntime {
654
683
  ctx: ActorContextHandle,
655
684
  names: string[],
656
685
  options?: RuntimeQueueWaitOptions | undefined | null,
686
+ signal?: CancellationTokenHandle | undefined | null,
657
687
  ): Promise<void> {
658
688
  await asNativeActorContext(ctx)
659
689
  .queue()
660
- .waitForNamesAvailable(names, options);
690
+ .waitForNamesAvailable(
691
+ names,
692
+ options,
693
+ signal ? asNativeCancellationToken(signal) : signal,
694
+ );
661
695
  }
662
696
 
663
697
  async actorQueueEnqueueAndWait(
@@ -1,12 +1,12 @@
1
1
  import { RivetError } from "@/actor/errors";
2
2
  import {
3
+ type EventSchemaConfig,
3
4
  isEventSchemaDefinition,
4
5
  isQueueSchemaDefinition,
5
6
  isStandardSchema,
6
- validateSchemaSync,
7
- type EventSchemaConfig,
8
7
  type PrimitiveSchema,
9
8
  type QueueSchemaConfig,
9
+ validateSchemaSync,
10
10
  } from "@/actor/schema";
11
11
 
12
12
  const CONN_PARAMS_KEY = "__conn_params__";
@@ -126,19 +126,17 @@ export function validateQueueComplete(
126
126
  response,
127
127
  );
128
128
  if (!result.success) {
129
- throw validationError(`queue \`${name}\` completion response`, result.issues);
129
+ throw validationError(
130
+ `queue \`${name}\` completion response`,
131
+ result.issues,
132
+ );
130
133
  }
131
134
  return result.data;
132
135
  }
133
136
 
134
137
  function validationError(target: string, issues: unknown[]): RivetError {
135
- return new RivetError(
136
- "actor",
137
- "validation_error",
138
- `Invalid ${target}`,
139
- {
140
- public: true,
141
- metadata: { issues },
142
- },
143
- );
138
+ return new RivetError("actor", "validation_error", `Invalid ${target}`, {
139
+ public: true,
140
+ metadata: { issues },
141
+ });
144
142
  }