rivetkit 2.3.0-rc.9 → 2.3.1

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 (226) hide show
  1. package/dist/browser/client.d.ts +511 -62
  2. package/dist/browser/client.js +230 -174
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +53 -23
  5. package/dist/browser/inspector/client.js.map +1 -1
  6. package/dist/tsup/actor/errors.cjs +4 -2
  7. package/dist/tsup/actor/errors.cjs.map +1 -1
  8. package/dist/tsup/actor/errors.d.cts +1 -1
  9. package/dist/tsup/actor/errors.d.ts +1 -1
  10. package/dist/tsup/actor/errors.js +3 -1
  11. package/dist/tsup/agent-os/index.cjs +2163 -2087
  12. package/dist/tsup/agent-os/index.cjs.map +1 -1
  13. package/dist/tsup/agent-os/index.d.cts +509 -69
  14. package/dist/tsup/agent-os/index.d.ts +509 -69
  15. package/dist/tsup/agent-os/index.js +2163 -2087
  16. package/dist/tsup/agent-os/index.js.map +1 -1
  17. package/dist/tsup/{chunk-WQ4HNA4W.cjs → chunk-3MHDOUD7.cjs} +73 -3
  18. package/dist/tsup/chunk-3MHDOUD7.cjs.map +1 -0
  19. package/dist/tsup/{chunk-QAZLM4WT.cjs → chunk-4FC7TVS6.cjs} +8 -4
  20. package/dist/tsup/chunk-4FC7TVS6.cjs.map +1 -0
  21. package/dist/tsup/{chunk-4CGA6QJO.cjs → chunk-4UUEB43Y.cjs} +24 -9
  22. package/dist/tsup/chunk-4UUEB43Y.cjs.map +1 -0
  23. package/dist/tsup/{chunk-GVTOE34S.cjs → chunk-5IWLUJ6W.cjs} +222 -235
  24. package/dist/tsup/chunk-5IWLUJ6W.cjs.map +1 -0
  25. package/dist/tsup/{chunk-MMMEZM5J.js → chunk-H6VVZMWN.js} +4 -4
  26. package/dist/tsup/chunk-H6VVZMWN.js.map +1 -0
  27. package/dist/tsup/{chunk-3YY5S6TV.js → chunk-HXUEHHJF.js} +2 -2
  28. package/dist/tsup/chunk-HXUEHHJF.js.map +1 -0
  29. package/dist/tsup/{chunk-H7P7WR2Y.js → chunk-I35VSLEM.js} +6 -6
  30. package/dist/tsup/chunk-I35VSLEM.js.map +1 -0
  31. package/dist/tsup/{chunk-H37XQU3I.js → chunk-JBUZRPY5.js} +2 -2
  32. package/dist/tsup/{chunk-CPA4Y3RG.cjs → chunk-JLJJZYCJ.cjs} +10 -10
  33. package/dist/tsup/chunk-JLJJZYCJ.cjs.map +1 -0
  34. package/dist/tsup/{chunk-PCBNKI2J.js → chunk-JZ7TWV65.js} +1 -1
  35. package/dist/tsup/chunk-JZ7TWV65.js.map +1 -0
  36. package/dist/tsup/{chunk-VRCIXJRN.js → chunk-L2X3YFER.js} +64 -10
  37. package/dist/tsup/chunk-L2X3YFER.js.map +1 -0
  38. package/dist/tsup/{chunk-Y5NSCZA2.cjs → chunk-MNHKOS6L.cjs} +72 -18
  39. package/dist/tsup/chunk-MNHKOS6L.cjs.map +1 -0
  40. package/dist/tsup/{chunk-KJTA3ATT.js → chunk-NERUIBOT.js} +22 -7
  41. package/dist/tsup/chunk-NERUIBOT.js.map +1 -0
  42. package/dist/tsup/{chunk-4WPEZBK4.cjs → chunk-OST76LRW.cjs} +10 -10
  43. package/dist/tsup/chunk-OST76LRW.cjs.map +1 -0
  44. package/dist/tsup/{chunk-MALSPBAF.cjs → chunk-OZBCXBVP.cjs} +3 -3
  45. package/dist/tsup/{chunk-MALSPBAF.cjs.map → chunk-OZBCXBVP.cjs.map} +1 -1
  46. package/dist/tsup/{chunk-F3Q5BFQ6.js → chunk-PT6OIW5E.js} +66 -79
  47. package/dist/tsup/chunk-PT6OIW5E.js.map +1 -0
  48. package/dist/tsup/{chunk-W7EYSYVI.js → chunk-R6KPN5EW.js} +134 -20
  49. package/dist/tsup/chunk-R6KPN5EW.js.map +1 -0
  50. package/dist/tsup/{chunk-VJFRBJVQ.cjs → chunk-V5KMAMX3.cjs} +138 -24
  51. package/dist/tsup/chunk-V5KMAMX3.cjs.map +1 -0
  52. package/dist/tsup/{chunk-LD5YASJU.cjs → chunk-VE2X4KMG.cjs} +2 -2
  53. package/dist/tsup/{chunk-LD5YASJU.cjs.map → chunk-VE2X4KMG.cjs.map} +1 -1
  54. package/dist/tsup/{chunk-T6YVRM4K.js → chunk-XIX5DOZN.js} +72 -2
  55. package/dist/tsup/chunk-XIX5DOZN.js.map +1 -0
  56. package/dist/tsup/{chunk-2NDZ7JCR.cjs → chunk-ZA7FLHKH.cjs} +1 -1
  57. package/dist/tsup/chunk-ZA7FLHKH.cjs.map +1 -0
  58. package/dist/tsup/{chunk-KIWH5H3K.js → chunk-ZZ3WBRPD.js} +7 -3
  59. package/dist/tsup/chunk-ZZ3WBRPD.js.map +1 -0
  60. package/dist/tsup/client/mod.cjs +9 -9
  61. package/dist/tsup/client/mod.d.cts +5 -5
  62. package/dist/tsup/client/mod.d.ts +5 -5
  63. package/dist/tsup/client/mod.js +8 -8
  64. package/dist/tsup/common/log.cjs +3 -3
  65. package/dist/tsup/common/log.js +2 -2
  66. package/dist/tsup/common/websocket.cjs +4 -4
  67. package/dist/tsup/common/websocket.js +3 -3
  68. package/dist/tsup/{config-Ca8dN4cS.d.cts → config-CzvopP5m.d.cts} +544 -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/{config-0Ta55UV0.d.ts → config-DZuT7tcp.d.ts} +544 -23
  72. package/dist/tsup/context-CyAdY-aA.d.ts +128 -0
  73. package/dist/tsup/context-sNB28g0N.d.cts +128 -0
  74. package/dist/tsup/db/drizzle.cjs +3 -3
  75. package/dist/tsup/db/drizzle.d.cts +1 -1
  76. package/dist/tsup/db/drizzle.d.ts +1 -1
  77. package/dist/tsup/db/drizzle.js +1 -1
  78. package/dist/tsup/db/mod.cjs +2 -2
  79. package/dist/tsup/db/mod.d.cts +2 -2
  80. package/dist/tsup/db/mod.d.ts +2 -2
  81. package/dist/tsup/db/mod.js +1 -1
  82. package/dist/tsup/dynamic/mod.cjs +24 -0
  83. package/dist/tsup/dynamic/mod.cjs.map +1 -0
  84. package/dist/tsup/dynamic/mod.d.cts +37 -0
  85. package/dist/tsup/dynamic/mod.d.ts +37 -0
  86. package/dist/tsup/dynamic/mod.js +24 -0
  87. package/dist/tsup/dynamic/mod.js.map +1 -0
  88. package/dist/tsup/inspector/mod.cjs +6 -6
  89. package/dist/tsup/inspector/mod.js +5 -5
  90. package/dist/tsup/inspector-tab/mod.cjs +173 -0
  91. package/dist/tsup/inspector-tab/mod.cjs.map +1 -0
  92. package/dist/tsup/inspector-tab/mod.d.cts +250 -0
  93. package/dist/tsup/inspector-tab/mod.d.ts +250 -0
  94. package/dist/tsup/inspector-tab/mod.js +173 -0
  95. package/dist/tsup/inspector-tab/mod.js.map +1 -0
  96. package/dist/tsup/mod.cjs +758 -348
  97. package/dist/tsup/mod.cjs.map +1 -1
  98. package/dist/tsup/mod.d.cts +5 -5
  99. package/dist/tsup/mod.d.ts +5 -5
  100. package/dist/tsup/mod.js +662 -252
  101. package/dist/tsup/mod.js.map +1 -1
  102. package/dist/tsup/test/mod.cjs +21 -18
  103. package/dist/tsup/test/mod.cjs.map +1 -1
  104. package/dist/tsup/test/mod.d.cts +4 -4
  105. package/dist/tsup/test/mod.d.ts +4 -4
  106. package/dist/tsup/test/mod.js +18 -15
  107. package/dist/tsup/test/mod.js.map +1 -1
  108. package/dist/tsup/{utils-DVekpm4I.d.cts → utils-CqDnC_PS.d.cts} +2 -1
  109. package/dist/tsup/{utils-DVekpm4I.d.ts → utils-CqDnC_PS.d.ts} +2 -1
  110. package/dist/tsup/utils.cjs +3 -3
  111. package/dist/tsup/utils.d.cts +1 -1
  112. package/dist/tsup/utils.d.ts +1 -1
  113. package/dist/tsup/utils.js +2 -2
  114. package/dist/tsup/workflow/mod.cjs +383 -322
  115. package/dist/tsup/workflow/mod.cjs.map +1 -1
  116. package/dist/tsup/workflow/mod.d.cts +8 -8
  117. package/dist/tsup/workflow/mod.d.ts +8 -8
  118. package/dist/tsup/workflow/mod.js +360 -299
  119. package/dist/tsup/workflow/mod.js.map +1 -1
  120. package/package.json +35 -14
  121. package/src/actor/config.ts +173 -51
  122. package/src/actor/contexts/index.ts +7 -2
  123. package/src/actor/definition.ts +17 -19
  124. package/src/actor/driver.ts +3 -3
  125. package/src/actor/errors.ts +20 -3
  126. package/src/actor/instance/mod.ts +26 -34
  127. package/src/actor/keys.ts +1 -1
  128. package/src/actor/mod.ts +22 -20
  129. package/src/actor/schema.ts +2 -2
  130. package/src/agent-os/actor/index.ts +38 -18
  131. package/src/agent-os/actor/preview.ts +1 -2
  132. package/src/agent-os/actor/session.ts +2 -2
  133. package/src/agent-os/config.ts +1 -1
  134. package/src/agent-os/fs/database-vfs.ts +1 -1
  135. package/src/agent-os/index.ts +16 -15
  136. package/src/client/actor-common.ts +87 -54
  137. package/src/client/actor-conn.ts +8 -36
  138. package/src/client/actor-handle.ts +69 -51
  139. package/src/client/actor-query.ts +5 -5
  140. package/src/client/errors.ts +1 -1
  141. package/src/client/lifecycle-errors.ts +2 -4
  142. package/src/client/query.ts +1 -1
  143. package/src/client/queue.ts +8 -3
  144. package/src/client/raw-utils.ts +8 -6
  145. package/src/client/resolve-gateway-target.ts +1 -1
  146. package/src/client/utils.ts +2 -7
  147. package/src/common/actor-websocket.ts +3 -1
  148. package/src/common/bare/actor-persist/v1.ts +205 -163
  149. package/src/common/bare/actor-persist/v2.ts +265 -213
  150. package/src/common/bare/actor-persist/v3.ts +176 -172
  151. package/src/common/bare/actor-persist/v4.ts +254 -253
  152. package/src/common/bare/transport/v1.ts +659 -543
  153. package/src/common/client-protocol-versioned.ts +66 -64
  154. package/src/common/database/config.ts +2 -8
  155. package/src/common/database/native-database.ts +1 -1
  156. package/src/common/database/shared.ts +1 -0
  157. package/src/common/encoding.ts +250 -16
  158. package/src/common/engine.ts +28 -1
  159. package/src/common/eventsource.ts +1 -1
  160. package/src/common/inline-websocket-adapter.ts +14 -13
  161. package/src/common/log.ts +1 -0
  162. package/src/common/router.ts +13 -17
  163. package/src/common/utils.ts +1 -150
  164. package/src/common/websocket-interface.ts +1 -1
  165. package/src/db/mod.ts +1 -1
  166. package/src/devtools-loader/index.ts +4 -7
  167. package/src/devtools-loader/serve-devtools.ts +26 -0
  168. package/src/drivers/engine/actor-driver.ts +58 -56
  169. package/src/dynamic/instance.ts +32 -0
  170. package/src/dynamic/internal.ts +50 -0
  171. package/src/dynamic/isolate-runtime.ts +66 -0
  172. package/src/dynamic/mod.ts +32 -0
  173. package/src/engine-client/actor-http-client.ts +3 -3
  174. package/src/engine-client/actor-websocket-client.ts +6 -5
  175. package/src/engine-client/api-endpoints.ts +51 -2
  176. package/src/engine-client/api-utils.ts +2 -2
  177. package/src/engine-client/driver.ts +1 -1
  178. package/src/engine-client/mod.ts +6 -3
  179. package/src/engine-client/ws-proxy.ts +9 -4
  180. package/src/inspector/client.browser.ts +5 -11
  181. package/src/inspector/mod.ts +1 -3
  182. package/src/inspector-tab/mod.ts +315 -0
  183. package/src/registry/config/envoy.ts +1 -2
  184. package/src/registry/config/index.ts +40 -16
  185. package/src/registry/index.ts +209 -73
  186. package/src/registry/napi-runtime.ts +29 -2
  187. package/src/registry/native-validation.ts +10 -12
  188. package/src/registry/native.ts +433 -198
  189. package/src/registry/process-metrics.ts +250 -0
  190. package/src/registry/runtime.ts +52 -1
  191. package/src/registry/wasm-runtime.ts +29 -2
  192. package/src/registry/write-through-proxy.ts +40 -0
  193. package/src/serde.ts +2 -2
  194. package/src/serverless/configure.ts +18 -7
  195. package/src/test/mod.ts +11 -8
  196. package/src/utils/endpoint-parser.ts +1 -1
  197. package/src/utils/env-vars.ts +37 -0
  198. package/src/utils/router.ts +1 -1
  199. package/src/utils.ts +1 -2
  200. package/src/workflow/context.ts +699 -240
  201. package/src/workflow/driver.ts +23 -12
  202. package/src/workflow/inspector.ts +4 -3
  203. package/src/workflow/mod.ts +37 -23
  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-QAZLM4WT.cjs.map +0 -1
  217. package/dist/tsup/chunk-T6YVRM4K.js.map +0 -1
  218. package/dist/tsup/chunk-VJFRBJVQ.cjs.map +0 -1
  219. package/dist/tsup/chunk-VRCIXJRN.js.map +0 -1
  220. package/dist/tsup/chunk-W7EYSYVI.js.map +0 -1
  221. package/dist/tsup/chunk-WQ4HNA4W.cjs.map +0 -1
  222. package/dist/tsup/chunk-Y5NSCZA2.cjs.map +0 -1
  223. package/dist/tsup/context-B_IWbWne.d.ts +0 -92
  224. package/dist/tsup/context-CUrQ9MHc.d.cts +0 -92
  225. package/src/utils/serve.ts +0 -217
  226. /package/dist/tsup/{chunk-H37XQU3I.js.map → chunk-JBUZRPY5.js.map} +0 -0
@@ -1,6 +1,11 @@
1
- import { ENGINE_ENDPOINT } from "@/common/engine";
1
+ import { isLocalEngineEndpoint } from "@/common/engine";
2
2
  import { configureServerlessPool } from "@/serverless/configure";
3
3
  import { VERSION } from "@/utils";
4
+ import {
5
+ getRivetkitPublicDir,
6
+ getRivetkitRuntimeMode,
7
+ parsePortEnv,
8
+ } from "@/utils/env-vars";
4
9
  import {
5
10
  type RegistryActors,
6
11
  type RegistryConfig,
@@ -44,8 +49,18 @@ export interface RegistryRoutes {
44
49
  prometheusMetrics(request?: Request): Promise<Response>;
45
50
  }
46
51
 
52
+ /**
53
+ * Injectable dependencies for {@link Registry}. Production code uses the
54
+ * defaults. Tests override `buildConfiguredRegistry` to drive lifecycle
55
+ * orchestration against a fake `CoreRuntime` without an engine.
56
+ */
57
+ export interface RegistryDeps {
58
+ buildConfiguredRegistry: typeof buildConfiguredRegistry;
59
+ }
60
+
47
61
  export class Registry<A extends RegistryActors> {
48
62
  #config: RegistryConfigInput<A>;
63
+ #buildConfiguredRegistry: typeof buildConfiguredRegistry;
49
64
  public readonly routes: RegistryRoutes;
50
65
 
51
66
  get config(): RegistryConfigInput<A> {
@@ -65,8 +80,10 @@ export class Registry<A extends RegistryActors> {
65
80
  #shutdownInFlight: Promise<void> | null = null;
66
81
  #signalHandlers: Partial<Record<ShutdownSignal, () => void>> = {};
67
82
 
68
- constructor(config: RegistryConfigInput<A>) {
83
+ constructor(config: RegistryConfigInput<A>, deps?: Partial<RegistryDeps>) {
69
84
  this.#config = config;
85
+ this.#buildConfiguredRegistry =
86
+ deps?.buildConfiguredRegistry ?? buildConfiguredRegistry;
70
87
  this.routes = {
71
88
  health: () => this.#healthRoute(),
72
89
  metadata: () => this.#metadataRoute(),
@@ -75,16 +92,24 @@ export class Registry<A extends RegistryActors> {
75
92
  };
76
93
  }
77
94
 
78
- #ensureServerlessPoolConfigured(config: RegistryConfig): Promise<void> | undefined {
95
+ /**
96
+ * Fires `configureServerlessPool` once per process when the registry
97
+ * config opts into it. Cached on the instance so repeated calls (from
98
+ * `handler()` and `listen()`) only run the upsert once. The retry loop
99
+ * inside `configureServerlessPool` tolerates the engine still warming up.
100
+ */
101
+ #ensureServerlessPoolConfigured(
102
+ config: RegistryConfig,
103
+ ): Promise<void> | undefined {
79
104
  if (!config.configurePool) return undefined;
80
105
 
81
106
  if (!this.#configureServerlessPoolPromise) {
82
- this.#configureServerlessPoolPromise = configureServerlessPool(config).catch(
83
- (error) => {
84
- this.#configureServerlessPoolPromise = undefined;
85
- throw error;
86
- },
87
- );
107
+ this.#configureServerlessPoolPromise = configureServerlessPool(
108
+ config,
109
+ ).catch((error) => {
110
+ this.#configureServerlessPoolPromise = undefined;
111
+ throw error;
112
+ });
88
113
  this.#configureServerlessPoolPromise.catch(() => {});
89
114
  }
90
115
 
@@ -106,7 +131,8 @@ export class Registry<A extends RegistryActors> {
106
131
  this.#printWelcome(config, "serverless");
107
132
 
108
133
  if (!this.#runtimeServerlessPromise) {
109
- this.#runtimeServerlessPromise = buildConfiguredRegistry(config);
134
+ this.#runtimeServerlessPromise =
135
+ this.#buildConfiguredRegistry(config);
110
136
  }
111
137
 
112
138
  const { runtime, registry, serveConfig } =
@@ -120,12 +146,13 @@ export class Registry<A extends RegistryActors> {
120
146
  serveConfig.serverlessBasePath ?? "/api/rivet",
121
147
  );
122
148
  const isEngineMetadataRequest =
123
- request.headers.get("user-agent")?.startsWith("RivetEngine/") ?? false;
149
+ request.headers.get("user-agent")?.startsWith("RivetEngine/") ??
150
+ false;
124
151
 
125
152
  if (isStartRequest) {
126
153
  try {
127
154
  await this.#ensureServerlessPoolConfigured(config);
128
- } catch (error) {
155
+ } catch (_error) {
129
156
  return new Response(
130
157
  JSON.stringify({
131
158
  group: "guard",
@@ -267,7 +294,7 @@ export class Registry<A extends RegistryActors> {
267
294
  if (isMetadataRequest && !isEngineMetadataRequest) {
268
295
  try {
269
296
  await this.#ensureServerlessPoolConfigured(config);
270
- } catch (error) {
297
+ } catch (_error) {
271
298
  return new Response(
272
299
  JSON.stringify({
273
300
  group: "guard",
@@ -303,6 +330,54 @@ export class Registry<A extends RegistryActors> {
303
330
  };
304
331
  }
305
332
 
333
+ /**
334
+ * Bind an HTTP listener provided by the native (Rust) runtime and serve
335
+ * the registry's serverless endpoints over it. Resolves only after the
336
+ * registry is shut down (SIGINT/SIGTERM or `nativeRegistry.shutdown()`).
337
+ *
338
+ * @param opts.port Port to listen on. Defaults to `process.env.RIVET_PORT`
339
+ * if set, otherwise 3000.
340
+ * @param opts.host Address to bind. Defaults to `0.0.0.0`.
341
+ * @param opts.publicDir If set, serves static files from this directory
342
+ * as a fallback below the framework routes.
343
+ *
344
+ * @example
345
+ * ```ts
346
+ * await registry.listen();
347
+ * await registry.listen({ port: 8080, publicDir: "./public" });
348
+ * ```
349
+ */
350
+ public async listen(
351
+ opts: { port?: number; host?: string; publicDir?: string } = {},
352
+ ): Promise<void> {
353
+ const port = opts.port ?? parsePortEnv(process.env.RIVET_PORT) ?? 3000;
354
+ const publicDir = opts.publicDir ?? getRivetkitPublicDir();
355
+ const config = this.parseConfig();
356
+
357
+ // Cache on both promise fields so the shutdown drain sees Mode A and B.
358
+ const configuredRegistryPromise = buildConfiguredRegistry(config);
359
+ this.#runtimeServeConfiguredPromise = configuredRegistryPromise;
360
+ this.#runtimeServerlessPromise = configuredRegistryPromise;
361
+ this.#installSignalHandlers(config);
362
+
363
+ this.#printWelcome(config, "serverless", {
364
+ port,
365
+ host: opts.host,
366
+ publicDir,
367
+ });
368
+
369
+ // Background fire; the retry loop tolerates engine warm-up.
370
+ this.#ensureServerlessPoolConfigured(config);
371
+
372
+ const { runtime, registry, serveConfig } =
373
+ await configuredRegistryPromise;
374
+ await runtime.serveListener(
375
+ registry,
376
+ { port, host: opts.host, publicDir },
377
+ serveConfig,
378
+ );
379
+ }
380
+
306
381
  /**
307
382
  * Returns a health response suitable for mounting in a user-owned router.
308
383
  */
@@ -392,8 +467,11 @@ export class Registry<A extends RegistryActors> {
392
467
  const candidates = [
393
468
  this.#runtimeServerlessPromise,
394
469
  this.#runtimeServeConfiguredPromise,
395
- ].filter((candidate): candidate is ReturnType<typeof buildConfiguredRegistry> =>
396
- candidate !== undefined
470
+ ].filter(
471
+ (
472
+ candidate,
473
+ ): candidate is ReturnType<typeof buildConfiguredRegistry> =>
474
+ candidate !== undefined,
397
475
  );
398
476
 
399
477
  if (candidates.length === 0) return undefined;
@@ -405,35 +483,33 @@ export class Registry<A extends RegistryActors> {
405
483
  */
406
484
  #startEnvoy(config: RegistryConfig, printWelcome: boolean) {
407
485
  if (!this.#runtimeServePromise) {
408
- const configuredRegistryPromise = buildConfiguredRegistry(config);
486
+ const configuredRegistryPromise =
487
+ this.#buildConfiguredRegistry(config);
409
488
  this.#runtimeServeConfiguredPromise = configuredRegistryPromise;
410
489
  this.#runtimeServePromise = configuredRegistryPromise
411
490
  .then(async ({ runtime, registry, serveConfig }) => {
412
491
  await runtime.serveRegistry(registry, serveConfig);
413
492
  })
414
- .catch((err) => {
493
+ .catch((error) => {
415
494
  // Always-attached catch so the stored promise never leaves a
416
495
  // rejection unhandled. Downstream awaits (e.g. #runShutdown's
417
496
  // Promise.race) attach their own catches and still observe
418
497
  // resolution via the race.
419
- logger().warn({ err }, "runtime registry serve errored");
498
+ logger().warn({ error }, "runtime registry serve errored");
420
499
  });
421
500
  // Install signal handlers once an envoy lifecycle has begun. Only
422
501
  // Mode A ever reaches here. Mode B (handler(request)) intentionally
423
502
  // does not install handlers because it runs on Workers/Vercel/Deno
424
503
  // Deploy where `process.on` is absent or forbidden; those platforms
425
504
  // own their own signal policy.
426
- this.#installSignalHandlers(config, configuredRegistryPromise);
505
+ this.#installSignalHandlers(config);
427
506
  }
428
507
  if (printWelcome) {
429
508
  this.#printWelcome(config, "serverful");
430
509
  }
431
510
  }
432
511
 
433
- #installSignalHandlers(
434
- config: RegistryConfig,
435
- configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
436
- ): void {
512
+ #installSignalHandlers(config: RegistryConfig): void {
437
513
  if (this.#shutdownInstalled) return;
438
514
  if (config.shutdown?.disableSignalHandlers) return;
439
515
  // Guard against non-Node runtimes (Workers/Edge) where `process` may
@@ -448,12 +524,7 @@ export class Registry<A extends RegistryActors> {
448
524
  this.#shutdownInstalled = true;
449
525
 
450
526
  const install = (signal: ShutdownSignal) => {
451
- const handler = () =>
452
- this.#onShutdownSignal(
453
- signal,
454
- config,
455
- configuredRegistryPromise,
456
- );
527
+ const handler = () => this.#onShutdownSignal(signal, config);
457
528
  this.#signalHandlers[signal] = handler;
458
529
  process.on(signal, handler);
459
530
  };
@@ -461,37 +532,67 @@ export class Registry<A extends RegistryActors> {
461
532
  install("SIGTERM");
462
533
  }
463
534
 
464
- #onShutdownSignal(
465
- signal: ShutdownSignal,
466
- config: RegistryConfig,
467
- configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
468
- ): void {
535
+ #onShutdownSignal(signal: ShutdownSignal, config: RegistryConfig): void {
469
536
  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
537
+ // Second delivery of the same (or another) shutdown signal, or a
538
+ // drain already started by an explicit `shutdown()` call. Remove
539
+ // our handler only, preserving any user-installed listeners. PID 1
540
+ // must exit directly because re-raised default signals can be
473
541
  // swallowed by the container signal path.
474
542
  this.#removeSignalHandlers();
475
543
  finishShutdownSignal(signal);
476
544
  return;
477
545
  }
478
- this.#shutdownInFlight = this.#runShutdown(
479
- signal,
480
- config,
481
- configuredRegistryPromise,
482
- ).catch((err) => {
546
+ this.#shutdownInFlight = this.#drain(config)
547
+ .catch((err) => {
548
+ logger().warn({ err }, "shutdown error");
549
+ })
550
+ .then(() => {
551
+ this.#removeSignalHandlers();
552
+ finishShutdownSignal(signal);
553
+ });
554
+ }
555
+
556
+ /**
557
+ * Gracefully drains all live registries.
558
+ *
559
+ * Programmatic counterpart to the SIGINT/SIGTERM handlers: tears down
560
+ * every live `CoreRegistry` (both `start()` and `handler()` modes) and
561
+ * waits for the serve promise to resolve, all bounded by the shutdown
562
+ * grace period. Unlike a signal-driven shutdown, this does not re-raise a
563
+ * signal or exit the process. The caller owns process lifetime.
564
+ *
565
+ * Idempotent: concurrent or repeated calls share a single drain. Safe to
566
+ * call even if nothing has been started.
567
+ *
568
+ * @example
569
+ * ```ts
570
+ * const registry = setup({ use: { counter } });
571
+ * registry.start();
572
+ * // ...later, on your own shutdown trigger:
573
+ * await registry.shutdown();
574
+ * ```
575
+ */
576
+ public async shutdown(): Promise<void> {
577
+ if (this.#shutdownInFlight !== null) return this.#shutdownInFlight;
578
+ const config = this.parseConfig();
579
+ // Uninstall our signal handlers so a later SIGINT/SIGTERM does not
580
+ // re-trigger a drain on already-torn-down registries. Subsequent
581
+ // signals fall back to Node's default termination behavior.
582
+ this.#removeSignalHandlers();
583
+ this.#shutdownInFlight = this.#drain(config).catch((err) => {
483
584
  logger().warn({ err }, "shutdown error");
484
585
  });
586
+ return this.#shutdownInFlight;
485
587
  }
486
588
 
487
- async #runShutdown(
488
- signal: ShutdownSignal,
489
- config: RegistryConfig,
490
- configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
491
- ): Promise<void> {
589
+ async #drain(config: RegistryConfig): Promise<void> {
590
+ const modeAPromise = this.#runtimeServeConfiguredPromise;
591
+ const modeBPromise = this.#runtimeServerlessPromise;
592
+
492
593
  const gracePeriodMs =
493
594
  config.shutdown?.gracePeriodMs ??
494
- (await this.#actorStopThresholdMs(configuredRegistryPromise)) ??
595
+ (await this.#actorStopThresholdMs(modeAPromise ?? modeBPromise)) ??
495
596
  30 * 60 * 1000;
496
597
  // Race the entire drain sequence (both modes + serve promise) against
497
598
  // a single grace ceiling. By default, this uses the engine-provided
@@ -499,33 +600,33 @@ export class Registry<A extends RegistryActors> {
499
600
  const drain = async () => {
500
601
  // Shut down every live `CoreRegistry` we know about. Mode A
501
602
  // (`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) {
603
+ // runtime registry, so one drain fans out to both to honor the
604
+ // spec invariant "single shutdown tears down both modes".
605
+ const registries: Promise<void>[] = [];
606
+ if (modeAPromise !== undefined) {
520
607
  registries.push(
521
608
  (async () => {
522
609
  try {
523
- const { runtime, registry } =
524
- await runtimeServerlessPromise;
610
+ const { runtime, registry } = await modeAPromise;
525
611
  await runtime.shutdownRegistry(registry);
526
612
  } catch (err) {
527
613
  logger().warn(
528
614
  { err },
615
+ "runtime registry shutdown errored (mode A)",
616
+ );
617
+ }
618
+ })(),
619
+ );
620
+ }
621
+ if (modeBPromise !== undefined) {
622
+ registries.push(
623
+ (async () => {
624
+ try {
625
+ const { runtime, registry } = await modeBPromise;
626
+ await runtime.shutdownRegistry(registry);
627
+ } catch (err) {
628
+ logger().warn(
629
+ { error: err },
529
630
  "runtime registry shutdown errored (mode B)",
530
631
  );
531
632
  }
@@ -548,13 +649,14 @@ export class Registry<A extends RegistryActors> {
548
649
  setTimeout(resolve, gracePeriodMs).unref?.(),
549
650
  ),
550
651
  ]);
551
- this.#removeSignalHandlers();
552
- finishShutdownSignal(signal);
553
652
  }
554
653
 
555
654
  async #actorStopThresholdMs(
556
- configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
655
+ configuredRegistryPromise:
656
+ | ReturnType<typeof buildConfiguredRegistry>
657
+ | undefined,
557
658
  ): Promise<number | undefined> {
659
+ if (configuredRegistryPromise === undefined) return undefined;
558
660
  try {
559
661
  const { runtime, registry } = await configuredRegistryPromise;
560
662
  const thresholdMs =
@@ -591,6 +693,10 @@ export class Registry<A extends RegistryActors> {
591
693
  /**
592
694
  * Starts the actor envoy for standalone server deployments.
593
695
  *
696
+ * Set `RIVETKIT_RUNTIME_MODE=serverless` to instead bind an HTTP listener
697
+ * via `listen()` (Mode B). Mode A (envoy) and Mode B (listener) are
698
+ * mutually exclusive per registry instance.
699
+ *
594
700
  * @example
595
701
  * ```ts
596
702
  * const registry = setup({ use: { counter } });
@@ -598,6 +704,21 @@ export class Registry<A extends RegistryActors> {
598
704
  * ```
599
705
  */
600
706
  public start() {
707
+ if (getRivetkitRuntimeMode() === "serverless") {
708
+ // start() defaults publicDir to "/public" unless overridden by env.
709
+ const publicDir = getRivetkitPublicDir() ?? "/public";
710
+ // Detached listener; bind failures are fatal so exit hard.
711
+ this.listen({ publicDir }).catch((error) => {
712
+ logger().error({ error }, "auto-listen failed; exiting");
713
+ if (
714
+ typeof process !== "undefined" &&
715
+ typeof process.exit === "function"
716
+ ) {
717
+ process.exit(1);
718
+ }
719
+ });
720
+ return;
721
+ }
601
722
  const config = this.parseConfig();
602
723
  this.#startEnvoy(config, true);
603
724
  }
@@ -605,6 +726,7 @@ export class Registry<A extends RegistryActors> {
605
726
  #printWelcome(
606
727
  config: RegistryConfig,
607
728
  kind: "serverless" | "serverful",
729
+ listener?: { port: number; host?: string; publicDir?: string },
608
730
  ): void {
609
731
  if (config.noWelcome || this.#welcomePrinted) return;
610
732
  this.#welcomePrinted = true;
@@ -625,7 +747,9 @@ export class Registry<A extends RegistryActors> {
625
747
 
626
748
  if (config.endpoint) {
627
749
  const endpointType =
628
- config.endpoint === ENGINE_ENDPOINT ? "local native" : "remote";
750
+ config.startEngine || isLocalEngineEndpoint(config.endpoint)
751
+ ? "local native"
752
+ : "remote";
629
753
  logLine("Endpoint", `${config.endpoint} (${endpointType})`);
630
754
  }
631
755
 
@@ -634,6 +758,15 @@ export class Registry<A extends RegistryActors> {
634
758
  }
635
759
 
636
760
  logLine("Actors", Object.keys(config.use).length.toString());
761
+
762
+ if (listener) {
763
+ const host = listener.host ?? "0.0.0.0";
764
+ logLine("Listening", `http://${host}:${listener.port}`);
765
+ if (listener.publicDir) {
766
+ logLine("Public Dir", listener.publicDir);
767
+ }
768
+ }
769
+
637
770
  console.log();
638
771
  }
639
772
  }
@@ -646,7 +779,10 @@ function isServerlessStartRequest(request: Request, basePath: string): boolean {
646
779
  return parsed.pathname === `${normalizedBase}/start`;
647
780
  }
648
781
 
649
- function isServerlessMetadataRequest(request: Request, basePath: string): boolean {
782
+ function isServerlessMetadataRequest(
783
+ request: Request,
784
+ basePath: string,
785
+ ): boolean {
650
786
  if (request.method !== "GET") return false;
651
787
  const parsed = new URL(request.url);
652
788
  const normalizedBase =
@@ -18,13 +18,14 @@ import type {
18
18
  RuntimeHttpRequest,
19
19
  RuntimeKvEntry,
20
20
  RuntimeKvListOptions,
21
+ RuntimeListenerConfig,
21
22
  RuntimeQueueEnqueueAndWaitOptions,
22
23
  RuntimeQueueMessage,
23
24
  RuntimeQueueNextBatchOptions,
24
25
  RuntimeQueueTryNextBatchOptions,
25
26
  RuntimeQueueWaitOptions,
26
- RuntimeRequestSaveOpts,
27
27
  RuntimeRegistryRouteResponse,
28
+ RuntimeRequestSaveOpts,
28
29
  RuntimeServeConfig,
29
30
  RuntimeServerlessRequest,
30
31
  RuntimeServerlessResponseHead,
@@ -259,6 +260,21 @@ export class NapiCoreRuntime implements CoreRuntime {
259
260
  );
260
261
  }
261
262
 
263
+ async serveListener(
264
+ registry: RegistryHandle,
265
+ listener: RuntimeListenerConfig,
266
+ config: RuntimeServeConfig,
267
+ ): Promise<void> {
268
+ await asNativeRegistry(registry).serveListener(
269
+ {
270
+ port: listener.port,
271
+ host: listener.host,
272
+ publicDir: listener.publicDir,
273
+ },
274
+ config,
275
+ );
276
+ }
277
+
262
278
  createActorFactory(
263
279
  callbacks: object,
264
280
  config?: RuntimeActorConfig | undefined | null,
@@ -452,6 +468,12 @@ export class NapiCoreRuntime implements CoreRuntime {
452
468
  return await asNativeActorContext(ctx).waitForTrackedShutdownWork();
453
469
  }
454
470
 
471
+ async actorWaitForTrackedShutdownWorkUnbounded(
472
+ ctx: ActorContextHandle,
473
+ ): Promise<void> {
474
+ await asNativeActorContext(ctx).waitForTrackedShutdownWorkUnbounded();
475
+ }
476
+
455
477
  actorKeepAwake(ctx: ActorContextHandle, promise: Promise<unknown>): void {
456
478
  asNativeActorContext(ctx).keepAwake(promise);
457
479
  }
@@ -677,10 +699,15 @@ export class NapiCoreRuntime implements CoreRuntime {
677
699
  ctx: ActorContextHandle,
678
700
  names: string[],
679
701
  options?: RuntimeQueueWaitOptions | undefined | null,
702
+ signal?: CancellationTokenHandle | undefined | null,
680
703
  ): Promise<void> {
681
704
  await asNativeActorContext(ctx)
682
705
  .queue()
683
- .waitForNamesAvailable(names, options);
706
+ .waitForNamesAvailable(
707
+ names,
708
+ options,
709
+ signal ? asNativeCancellationToken(signal) : signal,
710
+ );
684
711
  }
685
712
 
686
713
  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
  }