rivetkit 2.3.0-rc.10 → 2.3.0-rc.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/dist/browser/client.d.ts +448 -62
  2. package/dist/browser/client.js +131 -89
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +40 -20
  5. package/dist/browser/inspector/client.js.map +1 -1
  6. package/dist/tsup/actor/errors.cjs +2 -2
  7. package/dist/tsup/actor/errors.js +1 -1
  8. package/dist/tsup/agent-os/index.cjs +2103 -2090
  9. package/dist/tsup/agent-os/index.cjs.map +1 -1
  10. package/dist/tsup/agent-os/index.d.cts +446 -69
  11. package/dist/tsup/agent-os/index.d.ts +446 -69
  12. package/dist/tsup/agent-os/index.js +2102 -2089
  13. package/dist/tsup/agent-os/index.js.map +1 -1
  14. package/dist/tsup/{chunk-TE4VCDNY.cjs → chunk-2U6RLFKX.cjs} +219 -234
  15. package/dist/tsup/chunk-2U6RLFKX.cjs.map +1 -0
  16. package/dist/tsup/{chunk-4K3MV2MW.cjs → chunk-2ZTBRZRS.cjs} +10 -10
  17. package/dist/tsup/chunk-2ZTBRZRS.cjs.map +1 -0
  18. package/dist/tsup/{chunk-KU6VKVEK.js → chunk-3EVVOYFD.js} +19 -7
  19. package/dist/tsup/chunk-3EVVOYFD.js.map +1 -0
  20. package/dist/tsup/{chunk-3LGP4JSO.cjs → chunk-6KTMKPNU.cjs} +8 -8
  21. package/dist/tsup/{chunk-3LGP4JSO.cjs.map → chunk-6KTMKPNU.cjs.map} +1 -1
  22. package/dist/tsup/{chunk-WU2O2KIE.js → chunk-7UZF56RS.js} +63 -78
  23. package/dist/tsup/chunk-7UZF56RS.js.map +1 -0
  24. package/dist/tsup/{chunk-KIWH5H3K.js → chunk-BRP62GZC.js} +3 -3
  25. package/dist/tsup/chunk-BRP62GZC.js.map +1 -0
  26. package/dist/tsup/{chunk-G5RULGYQ.cjs → chunk-C7AAIILH.cjs} +24 -9
  27. package/dist/tsup/chunk-C7AAIILH.cjs.map +1 -0
  28. package/dist/tsup/{chunk-XV52XUWU.js → chunk-EWVOWEMD.js} +4 -4
  29. package/dist/tsup/chunk-EWVOWEMD.js.map +1 -0
  30. package/dist/tsup/{chunk-3YY5S6TV.js → chunk-HXUEHHJF.js} +2 -2
  31. package/dist/tsup/chunk-HXUEHHJF.js.map +1 -0
  32. package/dist/tsup/{chunk-PCBNKI2J.js → chunk-JZ7TWV65.js} +1 -1
  33. package/dist/tsup/chunk-JZ7TWV65.js.map +1 -0
  34. package/dist/tsup/{chunk-QAZLM4WT.cjs → chunk-KORQB2IR.cjs} +3 -3
  35. package/dist/tsup/{chunk-QAZLM4WT.cjs.map → chunk-KORQB2IR.cjs.map} +1 -1
  36. package/dist/tsup/{chunk-CMYS77J6.js → chunk-OLIJHKLL.js} +3 -3
  37. package/dist/tsup/{chunk-BM3EOY7M.js → chunk-OOB32JVG.js} +134 -20
  38. package/dist/tsup/chunk-OOB32JVG.js.map +1 -0
  39. package/dist/tsup/{chunk-G34LIR7S.js → chunk-QKSGGKGQ.js} +22 -7
  40. package/dist/tsup/chunk-QKSGGKGQ.js.map +1 -0
  41. package/dist/tsup/{chunk-PWFGP2US.cjs → chunk-SS56HFM2.cjs} +138 -24
  42. package/dist/tsup/chunk-SS56HFM2.cjs.map +1 -0
  43. package/dist/tsup/{chunk-Z4C3W2CQ.cjs → chunk-UETC5RF7.cjs} +3 -3
  44. package/dist/tsup/{chunk-Z4C3W2CQ.cjs.map → chunk-UETC5RF7.cjs.map} +1 -1
  45. package/dist/tsup/{chunk-LD5YASJU.cjs → chunk-VE2X4KMG.cjs} +2 -2
  46. package/dist/tsup/{chunk-LD5YASJU.cjs.map → chunk-VE2X4KMG.cjs.map} +1 -1
  47. package/dist/tsup/{chunk-6BI2MS3S.js → chunk-VLXRFJ7P.js} +2 -2
  48. package/dist/tsup/{chunk-J5P6S2LC.cjs → chunk-VNMIAPPF.cjs} +26 -14
  49. package/dist/tsup/chunk-VNMIAPPF.cjs.map +1 -0
  50. package/dist/tsup/{chunk-WQ4HNA4W.cjs → chunk-WHYBAEWG.cjs} +4 -2
  51. package/dist/tsup/chunk-WHYBAEWG.cjs.map +1 -0
  52. package/dist/tsup/{chunk-T6YVRM4K.js → chunk-WIMUFZVJ.js} +3 -1
  53. package/dist/tsup/chunk-WIMUFZVJ.js.map +1 -0
  54. package/dist/tsup/{chunk-2NDZ7JCR.cjs → chunk-ZA7FLHKH.cjs} +1 -1
  55. package/dist/tsup/chunk-ZA7FLHKH.cjs.map +1 -0
  56. package/dist/tsup/client/mod.cjs +9 -9
  57. package/dist/tsup/client/mod.d.cts +4 -4
  58. package/dist/tsup/client/mod.d.ts +4 -4
  59. package/dist/tsup/client/mod.js +8 -8
  60. package/dist/tsup/common/log.cjs +3 -3
  61. package/dist/tsup/common/log.js +2 -2
  62. package/dist/tsup/common/websocket.cjs +4 -4
  63. package/dist/tsup/common/websocket.js +3 -3
  64. package/dist/tsup/{config-Ca8dN4cS.d.cts → config-BtAh7oBu.d.cts} +409 -22
  65. package/dist/tsup/{config-CxjGYf4K.d.cts → config-D49x8NpL.d.cts} +1 -2
  66. package/dist/tsup/{config-CxjGYf4K.d.ts → config-D49x8NpL.d.ts} +1 -2
  67. package/dist/tsup/{config-0Ta55UV0.d.ts → config-DKgPGC0f.d.ts} +409 -22
  68. package/dist/tsup/{context-B_IWbWne.d.ts → context-C-6dGebY.d.ts} +8 -8
  69. package/dist/tsup/{context-CUrQ9MHc.d.cts → context-Cfjl5pgz.d.cts} +8 -8
  70. package/dist/tsup/db/drizzle.cjs +3 -3
  71. package/dist/tsup/db/drizzle.d.cts +1 -1
  72. package/dist/tsup/db/drizzle.d.ts +1 -1
  73. package/dist/tsup/db/drizzle.js +1 -1
  74. package/dist/tsup/db/mod.cjs +2 -2
  75. package/dist/tsup/db/mod.d.cts +2 -2
  76. package/dist/tsup/db/mod.d.ts +2 -2
  77. package/dist/tsup/db/mod.js +1 -1
  78. package/dist/tsup/dynamic/mod.cjs +24 -0
  79. package/dist/tsup/dynamic/mod.cjs.map +1 -0
  80. package/dist/tsup/dynamic/mod.d.cts +37 -0
  81. package/dist/tsup/dynamic/mod.d.ts +37 -0
  82. package/dist/tsup/dynamic/mod.js +24 -0
  83. package/dist/tsup/dynamic/mod.js.map +1 -0
  84. package/dist/tsup/inspector/mod.cjs +6 -6
  85. package/dist/tsup/inspector/mod.js +5 -5
  86. package/dist/tsup/mod.cjs +606 -325
  87. package/dist/tsup/mod.cjs.map +1 -1
  88. package/dist/tsup/mod.d.cts +4 -4
  89. package/dist/tsup/mod.d.ts +4 -4
  90. package/dist/tsup/mod.js +510 -229
  91. package/dist/tsup/mod.js.map +1 -1
  92. package/dist/tsup/test/mod.cjs +21 -18
  93. package/dist/tsup/test/mod.cjs.map +1 -1
  94. package/dist/tsup/test/mod.d.cts +3 -3
  95. package/dist/tsup/test/mod.d.ts +3 -3
  96. package/dist/tsup/test/mod.js +18 -15
  97. package/dist/tsup/test/mod.js.map +1 -1
  98. package/dist/tsup/utils.cjs +3 -3
  99. package/dist/tsup/utils.d.cts +1 -1
  100. package/dist/tsup/utils.d.ts +1 -1
  101. package/dist/tsup/utils.js +2 -2
  102. package/dist/tsup/workflow/mod.cjs +307 -282
  103. package/dist/tsup/workflow/mod.cjs.map +1 -1
  104. package/dist/tsup/workflow/mod.d.cts +5 -5
  105. package/dist/tsup/workflow/mod.d.ts +5 -5
  106. package/dist/tsup/workflow/mod.js +501 -476
  107. package/dist/tsup/workflow/mod.js.map +1 -1
  108. package/package.json +22 -11
  109. package/src/actor/config.ts +68 -51
  110. package/src/actor/contexts/index.ts +7 -2
  111. package/src/actor/definition.ts +17 -19
  112. package/src/actor/driver.ts +3 -3
  113. package/src/actor/errors.ts +9 -3
  114. package/src/actor/instance/mod.ts +22 -30
  115. package/src/actor/keys.ts +1 -1
  116. package/src/actor/mod.ts +20 -20
  117. package/src/actor/schema.ts +2 -2
  118. package/src/agent-os/actor/index.ts +38 -18
  119. package/src/agent-os/actor/preview.ts +1 -2
  120. package/src/agent-os/actor/session.ts +2 -2
  121. package/src/agent-os/config.ts +1 -1
  122. package/src/agent-os/fs/database-vfs.ts +1 -1
  123. package/src/agent-os/index.ts +16 -15
  124. package/src/client/actor-common.ts +87 -54
  125. package/src/client/actor-conn.ts +8 -36
  126. package/src/client/actor-handle.ts +69 -51
  127. package/src/client/actor-query.ts +5 -5
  128. package/src/client/errors.ts +1 -1
  129. package/src/client/lifecycle-errors.ts +2 -4
  130. package/src/client/query.ts +1 -1
  131. package/src/client/queue.ts +8 -3
  132. package/src/client/raw-utils.ts +8 -6
  133. package/src/client/resolve-gateway-target.ts +1 -1
  134. package/src/client/utils.ts +2 -7
  135. package/src/common/actor-websocket.ts +3 -1
  136. package/src/common/bare/actor-persist/v1.ts +205 -163
  137. package/src/common/bare/actor-persist/v2.ts +265 -213
  138. package/src/common/bare/actor-persist/v3.ts +176 -172
  139. package/src/common/bare/actor-persist/v4.ts +254 -253
  140. package/src/common/bare/transport/v1.ts +659 -543
  141. package/src/common/client-protocol-versioned.ts +66 -64
  142. package/src/common/database/config.ts +2 -8
  143. package/src/common/database/native-database.ts +1 -1
  144. package/src/common/database/shared.ts +1 -0
  145. package/src/common/encoding.ts +250 -16
  146. package/src/common/eventsource.ts +1 -1
  147. package/src/common/inline-websocket-adapter.ts +14 -13
  148. package/src/common/log.ts +1 -0
  149. package/src/common/router.ts +13 -17
  150. package/src/common/utils.ts +1 -150
  151. package/src/common/websocket-interface.ts +1 -1
  152. package/src/db/mod.ts +1 -1
  153. package/src/drivers/engine/actor-driver.ts +58 -56
  154. package/src/dynamic/instance.ts +32 -0
  155. package/src/dynamic/internal.ts +50 -0
  156. package/src/dynamic/isolate-runtime.ts +66 -0
  157. package/src/dynamic/mod.ts +32 -0
  158. package/src/engine-client/actor-http-client.ts +1 -1
  159. package/src/engine-client/actor-websocket-client.ts +6 -5
  160. package/src/engine-client/api-endpoints.ts +51 -2
  161. package/src/engine-client/api-utils.ts +2 -2
  162. package/src/engine-client/driver.ts +1 -1
  163. package/src/engine-client/mod.ts +6 -3
  164. package/src/engine-client/ws-proxy.ts +4 -4
  165. package/src/inspector/client.browser.ts +5 -11
  166. package/src/inspector/mod.ts +1 -3
  167. package/src/registry/config/envoy.ts +1 -2
  168. package/src/registry/config/index.ts +3 -9
  169. package/src/registry/index.ts +150 -72
  170. package/src/registry/napi-runtime.ts +13 -2
  171. package/src/registry/native-validation.ts +10 -12
  172. package/src/registry/native.ts +231 -173
  173. package/src/registry/process-metrics.ts +250 -0
  174. package/src/registry/runtime.ts +4 -0
  175. package/src/registry/wasm-runtime.ts +18 -2
  176. package/src/registry/write-through-proxy.ts +40 -0
  177. package/src/serde.ts +2 -2
  178. package/src/serverless/configure.ts +18 -7
  179. package/src/test/mod.ts +11 -8
  180. package/src/utils/endpoint-parser.ts +1 -1
  181. package/src/utils/router.ts +1 -1
  182. package/src/utils/serve.ts +4 -5
  183. package/src/utils.ts +1 -2
  184. package/src/workflow/context.ts +61 -33
  185. package/src/workflow/driver.ts +4 -6
  186. package/src/workflow/inspector.ts +4 -3
  187. package/src/workflow/mod.ts +15 -17
  188. package/dist/tsup/chunk-2NDZ7JCR.cjs.map +0 -1
  189. package/dist/tsup/chunk-3YY5S6TV.js.map +0 -1
  190. package/dist/tsup/chunk-4K3MV2MW.cjs.map +0 -1
  191. package/dist/tsup/chunk-BM3EOY7M.js.map +0 -1
  192. package/dist/tsup/chunk-G34LIR7S.js.map +0 -1
  193. package/dist/tsup/chunk-G5RULGYQ.cjs.map +0 -1
  194. package/dist/tsup/chunk-J5P6S2LC.cjs.map +0 -1
  195. package/dist/tsup/chunk-KIWH5H3K.js.map +0 -1
  196. package/dist/tsup/chunk-KU6VKVEK.js.map +0 -1
  197. package/dist/tsup/chunk-PCBNKI2J.js.map +0 -1
  198. package/dist/tsup/chunk-PWFGP2US.cjs.map +0 -1
  199. package/dist/tsup/chunk-T6YVRM4K.js.map +0 -1
  200. package/dist/tsup/chunk-TE4VCDNY.cjs.map +0 -1
  201. package/dist/tsup/chunk-WQ4HNA4W.cjs.map +0 -1
  202. package/dist/tsup/chunk-WU2O2KIE.js.map +0 -1
  203. package/dist/tsup/chunk-XV52XUWU.js.map +0 -1
  204. /package/dist/tsup/{chunk-CMYS77J6.js.map → chunk-OLIJHKLL.js.map} +0 -0
  205. /package/dist/tsup/{chunk-6BI2MS3S.js.map → chunk-VLXRFJ7P.js.map} +0 -0
@@ -2,23 +2,24 @@ import {
2
2
  type ClientConfig,
3
3
  DEFAULT_MAX_QUERY_INPUT_SIZE,
4
4
  } from "@/client/config";
5
+ import type { ActorGatewayQuery, CrashPolicy } from "@/client/query";
5
6
  import {
7
+ WS_PROTOCOL_ACTOR,
6
8
  WS_PROTOCOL_CONN_PARAMS,
7
9
  WS_PROTOCOL_ENCODING,
8
10
  WS_PROTOCOL_STANDARD as WS_PROTOCOL_RIVETKIT,
9
- WS_PROTOCOL_TARGET,
10
- WS_PROTOCOL_ACTOR,
11
11
  WS_PROTOCOL_SKIP_READY_WAIT,
12
+ WS_PROTOCOL_TARGET,
12
13
  WS_PROTOCOL_TEST_ACK_HOOK,
13
14
  WS_PROTOCOL_TOKEN,
14
15
  } from "@/common/actor-router-consts";
16
+ import type { JsonCompatValue } from "@/common/encoding";
15
17
  import { importWebSocket } from "@/common/websocket";
16
18
  import { setRemoteHibernatableWebSocketAckTestHooks } from "@/common/websocket-test-hooks";
17
- import type { ActorGatewayQuery, CrashPolicy } from "@/client/query";
18
19
  import type { Encoding, UniversalWebSocket } from "@/mod";
19
20
  import { encodeCborCompat, uint8ArrayToBase64 } from "@/serde";
20
21
  import { combineUrlPath } from "@/utils";
21
- import { shouldSkipReadyWait, type GatewayRequestOptions } from "./driver";
22
+ import { type GatewayRequestOptions, shouldSkipReadyWait } from "./driver";
22
23
  import { logger } from "./log";
23
24
 
24
25
  class BufferedRemoteWebSocket implements UniversalWebSocket {
@@ -302,7 +303,7 @@ function pushInputQueryParam(
302
303
  return;
303
304
  }
304
305
 
305
- const encodedInput = encodeCborCompat(input);
306
+ const encodedInput = encodeCborCompat(input as JsonCompatValue);
306
307
  if (encodedInput.byteLength > maxInputSize) {
307
308
  throw new Error(
308
309
  `Actor query input exceeds maxInputSize (${encodedInput.byteLength} > ${maxInputSize} bytes). Increase client maxInputSize to allow larger query payloads.`,
@@ -107,12 +107,59 @@ export async function getDatacenters(
107
107
  return apiCall<never, DatacentersResponse>(config, "GET", `/datacenters`);
108
108
  }
109
109
 
110
+ // MARK: Get runner configs
111
+ export interface RunnerConfig {
112
+ normal?: {
113
+ drain_on_version_upgrade?: boolean;
114
+ actor_eviction_period?: number;
115
+ actor_eviction_rate?: number;
116
+ };
117
+ serverless?: {
118
+ url: string;
119
+ headers: Record<string, string>;
120
+ drain_grace_period?: number;
121
+ max_runners: number;
122
+ min_runners: number;
123
+ request_lifespan: number;
124
+ runners_margin: number;
125
+ slots_per_runner: number;
126
+ metadata_poll_interval?: number;
127
+ drain_on_version_upgrade?: boolean;
128
+ actor_eviction_period?: number;
129
+ actor_eviction_rate?: number;
130
+ };
131
+ protocol_version?: number;
132
+ }
133
+
134
+ export interface RunnerConfigDatacenters {
135
+ datacenters: Record<string, RunnerConfig>;
136
+ }
137
+
138
+ export interface RunnerConfigsResponse {
139
+ runner_configs: Record<string, RunnerConfigDatacenters>;
140
+ }
141
+
142
+ export async function getRunnerConfig(
143
+ config: ClientConfig,
144
+ name: string,
145
+ ): Promise<RunnerConfigsResponse> {
146
+ return apiCall<never, RunnerConfigsResponse>(
147
+ config,
148
+ "GET",
149
+ `/runner-configs?runner_name=${name}`,
150
+ );
151
+ }
152
+
110
153
  // MARK: Update runner config
111
154
  export interface RegistryConfigRequest {
112
155
  datacenters: Record<
113
156
  string,
114
157
  {
115
- normal?: Record<string, unknown>;
158
+ normal?: {
159
+ drain_on_version_upgrade?: boolean;
160
+ actor_eviction_period?: number;
161
+ actor_eviction_rate?: number;
162
+ };
116
163
  serverless?: {
117
164
  url: string;
118
165
  headers: Record<string, string>;
@@ -123,9 +170,11 @@ export interface RegistryConfigRequest {
123
170
  runners_margin: number;
124
171
  slots_per_runner: number;
125
172
  metadata_poll_interval?: number;
173
+ drain_on_version_upgrade?: boolean;
174
+ actor_eviction_period?: number;
175
+ actor_eviction_rate?: number;
126
176
  };
127
177
  metadata?: Record<string, unknown>;
128
- drain_on_version_upgrade?: boolean;
129
178
  }
130
179
  >;
131
180
  }
@@ -1,10 +1,10 @@
1
1
  import { z } from "zod/v4";
2
+ import { RivetError } from "@/actor/errors";
2
3
  import type { ClientConfig } from "@/client/config";
3
4
  import { sendHttpRequest } from "@/client/utils";
4
- import { RivetError } from "@/actor/errors";
5
+ import type { RegistryConfig } from "@/registry/config";
5
6
  import { combineUrlPath } from "@/utils";
6
7
  import { logger } from "./log";
7
- import { RegistryConfig } from "@/registry/config";
8
8
 
9
9
  export { RivetError as EngineApiError };
10
10
 
@@ -1,8 +1,8 @@
1
1
  import type { Hono, Context as HonoContext } from "hono";
2
2
  import type { ActorKey, Encoding, UniversalWebSocket } from "@/actor/mod";
3
+ import type { ActorQuery, CrashPolicy } from "@/client/query";
3
4
  import type { RegistryConfig } from "@/registry/config";
4
5
  import type { GetUpgradeWebSocket } from "@/utils";
5
- import type { ActorQuery, CrashPolicy } from "@/client/query";
6
6
 
7
7
  export type GatewayTarget = { directId: string } | ActorQuery;
8
8
 
@@ -7,9 +7,9 @@ import {
7
7
  PATH_WEBSOCKET_BASE,
8
8
  PATH_WEBSOCKET_PREFIX,
9
9
  } from "@/common/actor-router-consts";
10
+ import type { JsonCompatValue } from "@/common/encoding";
10
11
  import { noopNext } from "@/common/utils";
11
12
  import type { Actor as ApiActor } from "@/engine-api/actors";
12
- import { shouldSkipReadyWait } from "@/engine-client/driver";
13
13
  import type {
14
14
  ActorOutput,
15
15
  CreateInput,
@@ -22,6 +22,7 @@ import type {
22
22
  ListActorsInput,
23
23
  RuntimeDisplayInformation,
24
24
  } from "@/engine-client/driver";
25
+ import { shouldSkipReadyWait } from "@/engine-client/driver";
25
26
  import type { Encoding, UniversalWebSocket } from "@/mod";
26
27
  import { encodeCborCompat, uint8ArrayToBase64 } from "@/serde";
27
28
  import { combineUrlPath, type GetUpgradeWebSocket } from "@/utils";
@@ -181,7 +182,9 @@ export class RemoteEngineControlClient implements EngineControlClient {
181
182
  key: serializeActorKey(key),
182
183
  runner_name_selector: this.#config.poolName,
183
184
  input: actorInput
184
- ? uint8ArrayToBase64(encodeCborCompat(actorInput))
185
+ ? uint8ArrayToBase64(
186
+ encodeCborCompat(actorInput as JsonCompatValue),
187
+ )
185
188
  : undefined,
186
189
  crash_policy: crashPolicy ?? "sleep",
187
190
  });
@@ -215,7 +218,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
215
218
  runner_name_selector: this.#config.poolName,
216
219
  key: serializeActorKey(key),
217
220
  input: input
218
- ? uint8ArrayToBase64(encodeCborCompat(input))
221
+ ? uint8ArrayToBase64(encodeCborCompat(input as JsonCompatValue))
219
222
  : undefined,
220
223
  crash_policy: crashPolicy ?? "sleep",
221
224
  });
@@ -9,7 +9,7 @@ import { logger } from "./log";
9
9
  * Returns Hono `upgradeWebSocket` args that will proxy requests from the client to a destination address.
10
10
  */
11
11
  export async function createWebSocketProxy(
12
- c: HonoContext,
12
+ _c: HonoContext,
13
13
  targetUrl: string,
14
14
  protocols: string[],
15
15
  ): Promise<UpgradeWebSocketArgs> {
@@ -23,7 +23,7 @@ export async function createWebSocketProxy(
23
23
  const state: WsState = {};
24
24
 
25
25
  return {
26
- onOpen: async (event: any, clientWs: WSContext) => {
26
+ onOpen: async (_event: any, clientWs: WSContext) => {
27
27
  logger().debug({ msg: "client websocket connected", targetUrl });
28
28
 
29
29
  if (clientWs.readyState !== 1) {
@@ -137,7 +137,7 @@ export async function createWebSocketProxy(
137
137
  }
138
138
  },
139
139
 
140
- onClose: (event: any, clientWs: WSContext) => {
140
+ onClose: (event: any, _clientWs: WSContext) => {
141
141
  logger().debug({
142
142
  msg: "client websocket closed",
143
143
  targetUrl,
@@ -159,7 +159,7 @@ export async function createWebSocketProxy(
159
159
  }
160
160
  },
161
161
 
162
- onError: (event: any, clientWs: WSContext) => {
162
+ onError: (event: any, _clientWs: WSContext) => {
163
163
  logger().error({ msg: "client websocket error", targetUrl, event });
164
164
 
165
165
  if (state.targetWs) {
@@ -198,7 +198,9 @@ const v3ToServerToV4 = (v3Data: v3.ToServer): v4.ToServer =>
198
198
 
199
199
  const v4ToServerToV3 = (v4Data: v4.ToServer): v3.ToServer => {
200
200
  if (v4Data.body.tag === "WorkflowReplayRequest") {
201
- throw new Error("Cannot convert v4-only workflow replay requests to v3");
201
+ throw new Error(
202
+ "Cannot convert v4-only workflow replay requests to v3",
203
+ );
202
204
  }
203
205
 
204
206
  return v4Data as unknown as v3.ToServer;
@@ -238,11 +240,7 @@ export const TO_SERVER_VERSIONED = createVersionedDataHandler<v4.ToServer>({
238
240
  v2ToServerToV3,
239
241
  v3ToServerToV4,
240
242
  ],
241
- serializeConverters: () => [
242
- v4ToServerToV3,
243
- v3ToServerToV2,
244
- v2ToServerToV1,
245
- ],
243
+ serializeConverters: () => [v4ToServerToV3, v3ToServerToV2, v2ToServerToV1],
246
244
  });
247
245
 
248
246
  export const TO_CLIENT_VERSIONED = createVersionedDataHandler<v4.ToClient>({
@@ -279,9 +277,5 @@ export const TO_CLIENT_VERSIONED = createVersionedDataHandler<v4.ToClient>({
279
277
  v2ToClientToV3,
280
278
  v3ToClientToV4,
281
279
  ],
282
- serializeConverters: () => [
283
- v4ToClientToV3,
284
- v3ToClientToV2,
285
- v2ToClientToV1,
286
- ],
280
+ serializeConverters: () => [v4ToClientToV3, v3ToClientToV2, v2ToClientToV1],
287
281
  });
@@ -1,4 +1,5 @@
1
1
  export * from "@/common/bare/generated/inspector/v4";
2
+ export type { WorkflowHistory as TransportWorkflowHistory } from "@/common/bare/transport/v1";
2
3
  export {
3
4
  decodeWorkflowHistoryTransport,
4
5
  encodeWorkflowHistoryTransport,
@@ -7,6 +8,3 @@ export {
7
8
  createWorkflowInspectorAdapter,
8
9
  type WorkflowInspectorAdapter,
9
10
  } from "@/workflow/inspector";
10
- export type {
11
- WorkflowHistory as TransportWorkflowHistory,
12
- } from "@/common/bare/transport/v1";
@@ -1,11 +1,10 @@
1
1
  import { z } from "zod/v4";
2
2
  import { getLogger } from "@/common/log";
3
3
  import {
4
- isDev,
5
4
  getNodeEnv,
5
+ getRivetEnvoyVersion,
6
6
  getRivetPool,
7
7
  getRivetTotalSlots,
8
- getRivetEnvoyVersion,
9
8
  } from "@/utils/env-vars";
10
9
 
11
10
  let warnedMissingVersion = false;
@@ -33,9 +33,7 @@ import {
33
33
 
34
34
  export const ActorsSchema = z.record(
35
35
  z.string(),
36
- z.custom<
37
- BaseActorDefinition<any, any, any, any, any, any, any, any, any>
38
- >(),
36
+ z.custom<AnyActorDefinition>(),
39
37
  );
40
38
  export type RegistryActors = z.infer<typeof ActorsSchema>;
41
39
 
@@ -267,13 +265,9 @@ export const RegistryConfigSchema = z
267
265
  * after calling `CoreRegistry::shutdown()`. Defaults to the
268
266
  * engine-provided actor stop threshold once the envoy connects.
269
267
  *
270
- * Must be >= rivetkit-core's drain timeout (20s) + margin.
268
+ * Must be long enough for rivetkit-core to drain the envoy.
271
269
  */
272
- gracePeriodMs: z
273
- .number()
274
- .int()
275
- .min(1_000)
276
- .optional(),
270
+ gracePeriodMs: z.number().int().min(1_000).optional(),
277
271
  /**
278
272
  * If true, rivetkit will not install SIGINT/SIGTERM handlers.
279
273
  * Use when the host application owns signal policy and will
@@ -1,6 +1,8 @@
1
+ import { Hono } from "hono";
1
2
  import { ENGINE_ENDPOINT } 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 =
@@ -646,7 +721,10 @@ function isServerlessStartRequest(request: Request, basePath: string): boolean {
646
721
  return parsed.pathname === `${normalizedBase}/start`;
647
722
  }
648
723
 
649
- function isServerlessMetadataRequest(request: Request, basePath: string): boolean {
724
+ function isServerlessMetadataRequest(
725
+ request: Request,
726
+ basePath: string,
727
+ ): boolean {
650
728
  if (request.method !== "GET") return false;
651
729
  const parsed = new URL(request.url);
652
730
  const normalizedBase =