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
@@ -1,6 +1,8 @@
1
- import { ENGINE_ENDPOINT } from "@/common/engine";
1
+ import { Hono } from "hono";
2
+ import { isLocalEngineEndpoint } from "@/common/engine";
2
3
  import { configureServerlessPool } from "@/serverless/configure";
3
- import { VERSION } from "@/utils";
4
+ import { detectRuntime, VERSION } from "@/utils";
5
+ import { crossPlatformServe, loadRuntimeServeStatic } from "@/utils/serve";
4
6
  import {
5
7
  type RegistryActors,
6
8
  type RegistryConfig,
@@ -44,8 +46,18 @@ export interface RegistryRoutes {
44
46
  prometheusMetrics(request?: Request): Promise<Response>;
45
47
  }
46
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;
56
+ }
57
+
47
58
  export class Registry<A extends RegistryActors> {
48
59
  #config: RegistryConfigInput<A>;
60
+ #buildConfiguredRegistry: typeof buildConfiguredRegistry;
49
61
  public readonly routes: RegistryRoutes;
50
62
 
51
63
  get config(): RegistryConfigInput<A> {
@@ -65,8 +77,10 @@ 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;
70
84
  this.routes = {
71
85
  health: () => this.#healthRoute(),
72
86
  metadata: () => this.#metadataRoute(),
@@ -75,16 +89,18 @@ export class Registry<A extends RegistryActors> {
75
89
  };
76
90
  }
77
91
 
78
- #ensureServerlessPoolConfigured(config: RegistryConfig): Promise<void> | undefined {
92
+ #ensureServerlessPoolConfigured(
93
+ config: RegistryConfig,
94
+ ): Promise<void> | undefined {
79
95
  if (!config.configurePool) return undefined;
80
96
 
81
97
  if (!this.#configureServerlessPoolPromise) {
82
- this.#configureServerlessPoolPromise = configureServerlessPool(config).catch(
83
- (error) => {
84
- this.#configureServerlessPoolPromise = undefined;
85
- throw error;
86
- },
87
- );
98
+ this.#configureServerlessPoolPromise = configureServerlessPool(
99
+ config,
100
+ ).catch((error) => {
101
+ this.#configureServerlessPoolPromise = undefined;
102
+ throw error;
103
+ });
88
104
  this.#configureServerlessPoolPromise.catch(() => {});
89
105
  }
90
106
 
@@ -106,7 +122,8 @@ export class Registry<A extends RegistryActors> {
106
122
  this.#printWelcome(config, "serverless");
107
123
 
108
124
  if (!this.#runtimeServerlessPromise) {
109
- this.#runtimeServerlessPromise = buildConfiguredRegistry(config);
125
+ this.#runtimeServerlessPromise =
126
+ this.#buildConfiguredRegistry(config);
110
127
  }
111
128
 
112
129
  const { runtime, registry, serveConfig } =
@@ -120,12 +137,13 @@ export class Registry<A extends RegistryActors> {
120
137
  serveConfig.serverlessBasePath ?? "/api/rivet",
121
138
  );
122
139
  const isEngineMetadataRequest =
123
- request.headers.get("user-agent")?.startsWith("RivetEngine/") ?? false;
140
+ request.headers.get("user-agent")?.startsWith("RivetEngine/") ??
141
+ false;
124
142
 
125
143
  if (isStartRequest) {
126
144
  try {
127
145
  await this.#ensureServerlessPoolConfigured(config);
128
- } catch (error) {
146
+ } catch (_error) {
129
147
  return new Response(
130
148
  JSON.stringify({
131
149
  group: "guard",
@@ -267,7 +285,7 @@ export class Registry<A extends RegistryActors> {
267
285
  if (isMetadataRequest && !isEngineMetadataRequest) {
268
286
  try {
269
287
  await this.#ensureServerlessPoolConfigured(config);
270
- } catch (error) {
288
+ } catch (_error) {
271
289
  return new Response(
272
290
  JSON.stringify({
273
291
  group: "guard",
@@ -303,6 +321,36 @@ export class Registry<A extends RegistryActors> {
303
321
  };
304
322
  }
305
323
 
324
+ /**
325
+ * Starts an HTTP server that dispatches every request through the
326
+ * serverless handler. Uses `crossPlatformServe` to pick the right
327
+ * runtime (Node, Bun, Deno).
328
+ *
329
+ * @param opts.port Port to listen on. Defaults to 3000.
330
+ * @param opts.publicDir If set, serves static files from this directory
331
+ * before falling through to the registry handler.
332
+ *
333
+ * @example
334
+ * ```ts
335
+ * await registry.listen();
336
+ * await registry.listen({ port: 8080, publicDir: "./public" });
337
+ * ```
338
+ */
339
+ public async listen(
340
+ opts: { port?: number; publicDir?: string } = {},
341
+ ): Promise<void> {
342
+ const port = opts.port ?? 3000;
343
+ const config = this.parseConfig();
344
+ const runtime = detectRuntime();
345
+ const app = new Hono();
346
+ if (opts.publicDir) {
347
+ const serveStatic = await loadRuntimeServeStatic(runtime);
348
+ app.use("*", serveStatic({ root: opts.publicDir }));
349
+ }
350
+ app.all("*", (c) => this.handler(c.req.raw));
351
+ await crossPlatformServe(config, port, app, runtime);
352
+ }
353
+
306
354
  /**
307
355
  * Returns a health response suitable for mounting in a user-owned router.
308
356
  */
@@ -392,8 +440,11 @@ export class Registry<A extends RegistryActors> {
392
440
  const candidates = [
393
441
  this.#runtimeServerlessPromise,
394
442
  this.#runtimeServeConfiguredPromise,
395
- ].filter((candidate): candidate is ReturnType<typeof buildConfiguredRegistry> =>
396
- candidate !== undefined
443
+ ].filter(
444
+ (
445
+ candidate,
446
+ ): candidate is ReturnType<typeof buildConfiguredRegistry> =>
447
+ candidate !== undefined,
397
448
  );
398
449
 
399
450
  if (candidates.length === 0) return undefined;
@@ -405,35 +456,33 @@ export class Registry<A extends RegistryActors> {
405
456
  */
406
457
  #startEnvoy(config: RegistryConfig, printWelcome: boolean) {
407
458
  if (!this.#runtimeServePromise) {
408
- const configuredRegistryPromise = buildConfiguredRegistry(config);
459
+ const configuredRegistryPromise =
460
+ this.#buildConfiguredRegistry(config);
409
461
  this.#runtimeServeConfiguredPromise = configuredRegistryPromise;
410
462
  this.#runtimeServePromise = configuredRegistryPromise
411
463
  .then(async ({ runtime, registry, serveConfig }) => {
412
464
  await runtime.serveRegistry(registry, serveConfig);
413
465
  })
414
- .catch((err) => {
466
+ .catch((error) => {
415
467
  // Always-attached catch so the stored promise never leaves a
416
468
  // rejection unhandled. Downstream awaits (e.g. #runShutdown's
417
469
  // Promise.race) attach their own catches and still observe
418
470
  // resolution via the race.
419
- logger().warn({ err }, "runtime registry serve errored");
471
+ logger().warn({ error }, "runtime registry serve errored");
420
472
  });
421
473
  // Install signal handlers once an envoy lifecycle has begun. Only
422
474
  // Mode A ever reaches here. Mode B (handler(request)) intentionally
423
475
  // does not install handlers because it runs on Workers/Vercel/Deno
424
476
  // Deploy where `process.on` is absent or forbidden; those platforms
425
477
  // own their own signal policy.
426
- this.#installSignalHandlers(config, configuredRegistryPromise);
478
+ this.#installSignalHandlers(config);
427
479
  }
428
480
  if (printWelcome) {
429
481
  this.#printWelcome(config, "serverful");
430
482
  }
431
483
  }
432
484
 
433
- #installSignalHandlers(
434
- config: RegistryConfig,
435
- configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
436
- ): void {
485
+ #installSignalHandlers(config: RegistryConfig): void {
437
486
  if (this.#shutdownInstalled) return;
438
487
  if (config.shutdown?.disableSignalHandlers) return;
439
488
  // Guard against non-Node runtimes (Workers/Edge) where `process` may
@@ -448,12 +497,7 @@ export class Registry<A extends RegistryActors> {
448
497
  this.#shutdownInstalled = true;
449
498
 
450
499
  const install = (signal: ShutdownSignal) => {
451
- const handler = () =>
452
- this.#onShutdownSignal(
453
- signal,
454
- config,
455
- configuredRegistryPromise,
456
- );
500
+ const handler = () => this.#onShutdownSignal(signal, config);
457
501
  this.#signalHandlers[signal] = handler;
458
502
  process.on(signal, handler);
459
503
  };
@@ -461,37 +505,67 @@ export class Registry<A extends RegistryActors> {
461
505
  install("SIGTERM");
462
506
  }
463
507
 
464
- #onShutdownSignal(
465
- signal: ShutdownSignal,
466
- config: RegistryConfig,
467
- configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
468
- ): void {
508
+ #onShutdownSignal(signal: ShutdownSignal, config: RegistryConfig): void {
469
509
  if (this.#shutdownInFlight !== null) {
470
- // Second delivery of the same (or another) shutdown signal.
471
- // Remove our handler only, preserving any user-installed listeners.
472
- // 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
473
514
  // swallowed by the container signal path.
474
515
  this.#removeSignalHandlers();
475
516
  finishShutdownSignal(signal);
476
517
  return;
477
518
  }
478
- this.#shutdownInFlight = this.#runShutdown(
479
- signal,
480
- config,
481
- configuredRegistryPromise,
482
- ).catch((err) => {
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) => {
483
557
  logger().warn({ err }, "shutdown error");
484
558
  });
559
+ return this.#shutdownInFlight;
485
560
  }
486
561
 
487
- async #runShutdown(
488
- signal: ShutdownSignal,
489
- config: RegistryConfig,
490
- configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
491
- ): Promise<void> {
562
+ async #drain(config: RegistryConfig): Promise<void> {
563
+ const modeAPromise = this.#runtimeServeConfiguredPromise;
564
+ const modeBPromise = this.#runtimeServerlessPromise;
565
+
492
566
  const gracePeriodMs =
493
567
  config.shutdown?.gracePeriodMs ??
494
- (await this.#actorStopThresholdMs(configuredRegistryPromise)) ??
568
+ (await this.#actorStopThresholdMs(modeAPromise ?? modeBPromise)) ??
495
569
  30 * 60 * 1000;
496
570
  // Race the entire drain sequence (both modes + serve promise) against
497
571
  // a single grace ceiling. By default, this uses the engine-provided
@@ -499,33 +573,33 @@ export class Registry<A extends RegistryActors> {
499
573
  const drain = async () => {
500
574
  // Shut down every live `CoreRegistry` we know about. Mode A
501
575
  // (`start()`) and Mode B (`handler()`) each build a separate
502
- // runtime registry, so one signal handler fans out to both to
503
- // honor the spec invariant "single shutdown tears down both modes".
504
- const registries: Promise<void>[] = [
505
- (async () => {
506
- try {
507
- const { runtime, registry } =
508
- await configuredRegistryPromise;
509
- await runtime.shutdownRegistry(registry);
510
- } catch (err) {
511
- logger().warn(
512
- { err },
513
- "runtime registry shutdown errored (mode A)",
514
- );
515
- }
516
- })(),
517
- ];
518
- const runtimeServerlessPromise = this.#runtimeServerlessPromise;
519
- 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) {
520
580
  registries.push(
521
581
  (async () => {
522
582
  try {
523
- const { runtime, registry } =
524
- await runtimeServerlessPromise;
583
+ const { runtime, registry } = await modeAPromise;
525
584
  await runtime.shutdownRegistry(registry);
526
585
  } catch (err) {
527
586
  logger().warn(
528
587
  { err },
588
+ "runtime registry shutdown errored (mode A)",
589
+ );
590
+ }
591
+ })(),
592
+ );
593
+ }
594
+ if (modeBPromise !== undefined) {
595
+ registries.push(
596
+ (async () => {
597
+ try {
598
+ const { runtime, registry } = await modeBPromise;
599
+ await runtime.shutdownRegistry(registry);
600
+ } catch (err) {
601
+ logger().warn(
602
+ { error: err },
529
603
  "runtime registry shutdown errored (mode B)",
530
604
  );
531
605
  }
@@ -548,13 +622,14 @@ export class Registry<A extends RegistryActors> {
548
622
  setTimeout(resolve, gracePeriodMs).unref?.(),
549
623
  ),
550
624
  ]);
551
- this.#removeSignalHandlers();
552
- finishShutdownSignal(signal);
553
625
  }
554
626
 
555
627
  async #actorStopThresholdMs(
556
- configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
628
+ configuredRegistryPromise:
629
+ | ReturnType<typeof buildConfiguredRegistry>
630
+ | undefined,
557
631
  ): Promise<number | undefined> {
632
+ if (configuredRegistryPromise === undefined) return undefined;
558
633
  try {
559
634
  const { runtime, registry } = await configuredRegistryPromise;
560
635
  const thresholdMs =
@@ -625,7 +700,9 @@ export class Registry<A extends RegistryActors> {
625
700
 
626
701
  if (config.endpoint) {
627
702
  const endpointType =
628
- config.endpoint === ENGINE_ENDPOINT ? "local native" : "remote";
703
+ config.startEngine || isLocalEngineEndpoint(config.endpoint)
704
+ ? "local native"
705
+ : "remote";
629
706
  logLine("Endpoint", `${config.endpoint} (${endpointType})`);
630
707
  }
631
708
 
@@ -646,7 +723,10 @@ function isServerlessStartRequest(request: Request, basePath: string): boolean {
646
723
  return parsed.pathname === `${normalizedBase}/start`;
647
724
  }
648
725
 
649
- function isServerlessMetadataRequest(request: Request, basePath: string): boolean {
726
+ function isServerlessMetadataRequest(
727
+ request: Request,
728
+ basePath: string,
729
+ ): boolean {
650
730
  if (request.method !== "GET") return false;
651
731
  const parsed = new URL(request.url);
652
732
  const normalizedBase =
@@ -23,8 +23,8 @@ import type {
23
23
  RuntimeQueueNextBatchOptions,
24
24
  RuntimeQueueTryNextBatchOptions,
25
25
  RuntimeQueueWaitOptions,
26
- RuntimeRequestSaveOpts,
27
26
  RuntimeRegistryRouteResponse,
27
+ RuntimeRequestSaveOpts,
28
28
  RuntimeServeConfig,
29
29
  RuntimeServerlessRequest,
30
30
  RuntimeServerlessResponseHead,
@@ -452,6 +452,12 @@ export class NapiCoreRuntime implements CoreRuntime {
452
452
  return await asNativeActorContext(ctx).waitForTrackedShutdownWork();
453
453
  }
454
454
 
455
+ async actorWaitForTrackedShutdownWorkUnbounded(
456
+ ctx: ActorContextHandle,
457
+ ): Promise<void> {
458
+ await asNativeActorContext(ctx).waitForTrackedShutdownWorkUnbounded();
459
+ }
460
+
455
461
  actorKeepAwake(ctx: ActorContextHandle, promise: Promise<unknown>): void {
456
462
  asNativeActorContext(ctx).keepAwake(promise);
457
463
  }
@@ -677,10 +683,15 @@ export class NapiCoreRuntime implements CoreRuntime {
677
683
  ctx: ActorContextHandle,
678
684
  names: string[],
679
685
  options?: RuntimeQueueWaitOptions | undefined | null,
686
+ signal?: CancellationTokenHandle | undefined | null,
680
687
  ): Promise<void> {
681
688
  await asNativeActorContext(ctx)
682
689
  .queue()
683
- .waitForNamesAvailable(names, options);
690
+ .waitForNamesAvailable(
691
+ names,
692
+ options,
693
+ signal ? asNativeCancellationToken(signal) : signal,
694
+ );
684
695
  }
685
696
 
686
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
  }