rivetkit 2.3.30 → 3.0.0-preview.2

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 (135) hide show
  1. package/dist/browser/client.d.ts +58 -9
  2. package/dist/browser/client.js +193 -20
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +1 -2
  5. package/dist/browser/inspector/client.js.map +1 -1
  6. package/dist/tsup/{chunk-P2PHHJMH.cjs → chunk-25MUHNWE.cjs} +326 -166
  7. package/dist/tsup/chunk-25MUHNWE.cjs.map +1 -0
  8. package/dist/tsup/{chunk-5SN6GAYX.cjs → chunk-3SPYZMME.cjs} +3 -3
  9. package/dist/tsup/{chunk-5SN6GAYX.cjs.map → chunk-3SPYZMME.cjs.map} +1 -1
  10. package/dist/tsup/{chunk-4SNS3PYZ.js → chunk-3URL73AV.js} +2 -2
  11. package/dist/tsup/{chunk-EKAQ7HG2.js → chunk-442XBCAF.js} +3 -3
  12. package/dist/tsup/{chunk-CORYIKDC.js → chunk-5F7FWAJI.js} +10 -11
  13. package/dist/tsup/chunk-5F7FWAJI.js.map +1 -0
  14. package/dist/tsup/{chunk-I472KTIC.js → chunk-6IGGYL2C.js} +185 -25
  15. package/dist/tsup/chunk-6IGGYL2C.js.map +1 -0
  16. package/dist/tsup/{chunk-JI6GZ2C2.js → chunk-A2CF6PFH.js} +18 -1
  17. package/dist/tsup/chunk-A2CF6PFH.js.map +1 -0
  18. package/dist/tsup/{chunk-OUB7SGSA.js → chunk-ANNVZW6B.js} +2 -2
  19. package/dist/tsup/{chunk-ZIIQQHC3.cjs → chunk-BCG7PTBS.cjs} +4 -4
  20. package/dist/tsup/{chunk-ZIIQQHC3.cjs.map → chunk-BCG7PTBS.cjs.map} +1 -1
  21. package/dist/tsup/{chunk-OJFVABRH.cjs → chunk-CJ3MX5MI.cjs} +7 -7
  22. package/dist/tsup/{chunk-OJFVABRH.cjs.map → chunk-CJ3MX5MI.cjs.map} +1 -1
  23. package/dist/tsup/{chunk-GEUBEODU.cjs → chunk-EPPV7WB2.cjs} +3 -3
  24. package/dist/tsup/{chunk-GEUBEODU.cjs.map → chunk-EPPV7WB2.cjs.map} +1 -1
  25. package/dist/tsup/{chunk-MXV26ZBB.js → chunk-OLZ4KAFL.js} +4 -4
  26. package/dist/tsup/{chunk-5UUOECX2.cjs → chunk-QDRZMCX5.cjs} +10 -10
  27. package/dist/tsup/{chunk-5UUOECX2.cjs.map → chunk-QDRZMCX5.cjs.map} +1 -1
  28. package/dist/tsup/{chunk-MYWZCDML.cjs → chunk-RTWH3AX3.cjs} +19 -2
  29. package/dist/tsup/chunk-RTWH3AX3.cjs.map +1 -0
  30. package/dist/tsup/{chunk-H3SGQEHZ.js → chunk-WGKL7HMH.js} +2 -2
  31. package/dist/tsup/{chunk-6LOYTFPZ.js → chunk-WGQHG26S.js} +2 -2
  32. package/dist/tsup/{chunk-2BIRGEIU.cjs → chunk-XSOHGU4G.cjs} +10 -11
  33. package/dist/tsup/chunk-XSOHGU4G.cjs.map +1 -0
  34. package/dist/tsup/{chunk-ABPZG7QV.cjs → chunk-YZVVID47.cjs} +3 -3
  35. package/dist/tsup/{chunk-ABPZG7QV.cjs.map → chunk-YZVVID47.cjs.map} +1 -1
  36. package/dist/tsup/client/mod.cjs +6 -6
  37. package/dist/tsup/client/mod.d.cts +15 -3
  38. package/dist/tsup/client/mod.d.ts +15 -3
  39. package/dist/tsup/client/mod.js +5 -5
  40. package/dist/tsup/common/log.cjs +2 -2
  41. package/dist/tsup/common/log.js +1 -1
  42. package/dist/tsup/common/websocket.cjs +3 -3
  43. package/dist/tsup/common/websocket.js +2 -2
  44. package/dist/tsup/{config-osdJqQwX.d.cts → config-8NgsxIcT.d.cts} +43 -12
  45. package/dist/tsup/{config-ey2I2zz5.d.ts → config-B2wlj9wx.d.cts} +7 -1
  46. package/dist/tsup/{config-ey2I2zz5.d.cts → config-B2wlj9wx.d.ts} +7 -1
  47. package/dist/tsup/{config-Cv6pjCt-.d.ts → config-DJ3KW-fe.d.ts} +43 -12
  48. package/dist/tsup/{context-CoVbKBuk.d.ts → context-BL8kUGjq.d.ts} +2 -2
  49. package/dist/tsup/{context-DpjrM7n7.d.cts → context-_RoE_YHL.d.cts} +2 -2
  50. package/dist/tsup/db/drizzle.cjs +2 -2
  51. package/dist/tsup/db/drizzle.d.cts +1 -1
  52. package/dist/tsup/db/drizzle.d.ts +1 -1
  53. package/dist/tsup/db/drizzle.js +1 -1
  54. package/dist/tsup/db/mod.cjs +3 -3
  55. package/dist/tsup/db/mod.d.cts +2 -2
  56. package/dist/tsup/db/mod.d.ts +2 -2
  57. package/dist/tsup/db/mod.js +2 -2
  58. package/dist/tsup/dynamic/mod.d.cts +2 -2
  59. package/dist/tsup/dynamic/mod.d.ts +2 -2
  60. package/dist/tsup/inspector/mod.cjs +5 -5
  61. package/dist/tsup/inspector/mod.js +4 -4
  62. package/dist/tsup/inspector/workflow.cjs +4 -4
  63. package/dist/tsup/inspector/workflow.js +3 -3
  64. package/dist/tsup/inspector-tab/mod.d.cts +3 -3
  65. package/dist/tsup/inspector-tab/mod.d.ts +3 -3
  66. package/dist/tsup/mod.cjs +254 -166
  67. package/dist/tsup/mod.cjs.map +1 -1
  68. package/dist/tsup/mod.d.cts +4 -4
  69. package/dist/tsup/mod.d.ts +4 -4
  70. package/dist/tsup/mod.js +168 -80
  71. package/dist/tsup/mod.js.map +1 -1
  72. package/dist/tsup/test/mod.cjs +7 -7
  73. package/dist/tsup/test/mod.d.cts +2 -2
  74. package/dist/tsup/test/mod.d.ts +2 -2
  75. package/dist/tsup/test/mod.js +5 -5
  76. package/dist/tsup/unstable/migrations.d.cts +1 -1
  77. package/dist/tsup/unstable/migrations.d.ts +1 -1
  78. package/dist/tsup/utils.cjs +2 -2
  79. package/dist/tsup/utils.js +1 -1
  80. package/dist/tsup/workflow/mod.cjs +17 -17
  81. package/dist/tsup/workflow/mod.d.cts +4 -4
  82. package/dist/tsup/workflow/mod.d.ts +4 -4
  83. package/dist/tsup/workflow/mod.js +5 -5
  84. package/package.json +9 -10
  85. package/src/actor/keys.ts +86 -0
  86. package/src/client/actor-conn.ts +92 -6
  87. package/src/client/actor-handle.ts +21 -3
  88. package/src/client/client.ts +6 -0
  89. package/src/client/config.ts +12 -1
  90. package/src/client/lifecycle-errors.ts +16 -1
  91. package/src/client/retry-budget.ts +78 -0
  92. package/src/client/utils.ts +13 -2
  93. package/src/common/database/config.ts +7 -1
  94. package/src/common/database/native-database.ts +17 -6
  95. package/src/common/router.ts +1 -1
  96. package/src/engine-api/actors.ts +3 -3
  97. package/src/engine-client/actor-websocket-client.ts +4 -2
  98. package/src/engine-client/api-endpoints.ts +44 -14
  99. package/src/engine-client/mod.ts +2 -2
  100. package/src/registry/config/envoy.ts +1 -1
  101. package/src/registry/config/index.ts +28 -31
  102. package/src/registry/index.ts +51 -19
  103. package/src/registry/napi-runtime.ts +3 -2
  104. package/src/registry/native.ts +126 -34
  105. package/src/registry/runtime.ts +27 -10
  106. package/src/registry/wasm-runtime.ts +6 -3
  107. package/src/utils/env-vars.ts +15 -10
  108. package/dist/tsup/chunk-2BIRGEIU.cjs.map +0 -1
  109. package/dist/tsup/chunk-CORYIKDC.js.map +0 -1
  110. package/dist/tsup/chunk-I472KTIC.js.map +0 -1
  111. package/dist/tsup/chunk-JI6GZ2C2.js.map +0 -1
  112. package/dist/tsup/chunk-MYWZCDML.cjs.map +0 -1
  113. package/dist/tsup/chunk-P2PHHJMH.cjs.map +0 -1
  114. package/src/actor/config.test.ts +0 -78
  115. package/src/actor/keys.test.ts +0 -275
  116. package/src/actor/utils.test.ts +0 -48
  117. package/src/client/test.ts +0 -44
  118. package/src/client/token-provider.test.ts +0 -86
  119. package/src/common/database/mod.test.ts +0 -364
  120. package/src/common/database/native-database.test.ts +0 -220
  121. package/src/common/inline-websocket-adapter.test.ts +0 -51
  122. package/src/db/drizzle.test.ts +0 -164
  123. package/src/registry/config/index.test.ts +0 -25
  124. package/src/registry/run-handler-coordinator.test.ts +0 -252
  125. package/src/registry/runtime.test.ts +0 -124
  126. package/src/unstable/migrations.test.ts +0 -170
  127. package/src/utils/endpoint-parser.test.ts +0 -206
  128. package/src/utils.test.ts +0 -34
  129. package/src/workflow/driver.test.ts +0 -62
  130. /package/dist/tsup/{chunk-4SNS3PYZ.js.map → chunk-3URL73AV.js.map} +0 -0
  131. /package/dist/tsup/{chunk-EKAQ7HG2.js.map → chunk-442XBCAF.js.map} +0 -0
  132. /package/dist/tsup/{chunk-OUB7SGSA.js.map → chunk-ANNVZW6B.js.map} +0 -0
  133. /package/dist/tsup/{chunk-MXV26ZBB.js.map → chunk-OLZ4KAFL.js.map} +0 -0
  134. /package/dist/tsup/{chunk-H3SGQEHZ.js.map → chunk-WGKL7HMH.js.map} +0 -0
  135. /package/dist/tsup/{chunk-6LOYTFPZ.js.map → chunk-WGQHG26S.js.map} +0 -0
@@ -61,6 +61,7 @@ import {
61
61
  import { type ClientRaw, CREATE_ACTOR_CONN_PROXY } from "./client";
62
62
  import { ActorError, isSchedulingError } from "./errors";
63
63
  import { retryOnLifecycleBoundary } from "./lifecycle-errors";
64
+ import { RetryBudget, retryAfterMs, retryDelayMs } from "./retry-budget";
64
65
  import { logger } from "./log";
65
66
  import { outboundTelemetryHeaders } from "./outbound-telemetry";
66
67
  import {
@@ -94,6 +95,7 @@ export class ActorHandleRaw {
94
95
  #queueSendMutex = new AsyncMutex();
95
96
  #currentActorInvocation?: CurrentActorInvocation;
96
97
  #startCallSpan?: StartCallSpan;
98
+ #retryBudget: RetryBudget;
97
99
 
98
100
  /**
99
101
  * Do not call this directly.
@@ -113,6 +115,7 @@ export class ActorHandleRaw {
113
115
  signal?: AbortSignal,
114
116
  currentActorInvocation?: CurrentActorInvocation,
115
117
  startCallSpan?: StartCallSpan,
118
+ retryBudget: RetryBudget = new RetryBudget(),
116
119
  ) {
117
120
  this.#client = client;
118
121
  this.#driver = driver;
@@ -124,6 +127,7 @@ export class ActorHandleRaw {
124
127
  this.#signal = signal;
125
128
  this.#currentActorInvocation = currentActorInvocation;
126
129
  this.#startCallSpan = startCallSpan;
130
+ this.#retryBudget = retryBudget;
127
131
  }
128
132
 
129
133
  async #resolveConnectionParams(): Promise<unknown> {
@@ -166,6 +170,7 @@ export class ActorHandleRaw {
166
170
  this.#actorResolutionState,
167
171
  );
168
172
 
173
+ this.#retryBudget.deposit();
169
174
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
170
175
  let actorId: string | undefined;
171
176
  try {
@@ -205,9 +210,19 @@ export class ActorHandleRaw {
205
210
  metadata,
206
211
  attempt,
207
212
  maxAttempts,
208
- )
213
+ ) &&
214
+ this.#retryBudget.tryWithdraw()
209
215
  ) {
210
- await this.#waitForRetryWindow();
216
+ await new Promise((resolve) =>
217
+ setTimeout(
218
+ resolve,
219
+ retryDelayMs(attempt, {
220
+ initialDelayMs: 25,
221
+ maxDelayMs: 1_000,
222
+ hintMs: retryAfterMs(err),
223
+ }),
224
+ ),
225
+ );
211
226
  continue;
212
227
  }
213
228
 
@@ -309,7 +324,10 @@ export class ActorHandleRaw {
309
324
  if (opts.name === "destroy") {
310
325
  return await run();
311
326
  }
312
- return await retryOnLifecycleBoundary(run, { signal });
327
+ return await retryOnLifecycleBoundary(run, {
328
+ signal,
329
+ budget: this.#retryBudget,
330
+ });
313
331
  };
314
332
  if (!call) {
315
333
  return await send();
@@ -14,6 +14,7 @@ import {
14
14
  import { type ActorHandle, ActorHandleRaw } from "./actor-handle";
15
15
  import { logger } from "./log";
16
16
  import { resolveGatewayTarget } from "./resolve-gateway-target";
17
+ import { type RetryBudgetOptions, RetryBudget } from "./retry-budget";
17
18
 
18
19
  export type { ClientConfig, ClientConfigInput } from "./config";
19
20
 
@@ -181,6 +182,8 @@ export const CREATE_ACTOR_CONN_PROXY = Symbol("createActorConnProxy");
181
182
  export interface ClientRawOptions {
182
183
  encoding?: Encoding;
183
184
  gateway?: ActorGatewayOptions;
185
+ /** Bounds retries of refused requests across every handle of this client. */
186
+ retryBudget?: RetryBudgetOptions;
184
187
  }
185
188
 
186
189
  /** Runtime-supplied wiring for clients created inside an actor. Set only by the native runtime. */
@@ -211,6 +214,7 @@ export class ClientRaw {
211
214
  #gatewayOptions: ActorGatewayOptions;
212
215
  #currentActorInvocation?: CurrentActorInvocation;
213
216
  #startCallSpan?: StartCallSpan;
217
+ #retryBudget: RetryBudget;
214
218
 
215
219
  /**
216
220
  * Creates an instance of Client.
@@ -228,6 +232,7 @@ export class ClientRaw {
228
232
  this.#gatewayOptions = options.gateway ?? {};
229
233
  this.#currentActorInvocation = options.currentActorInvocation;
230
234
  this.#startCallSpan = options.startCallSpan;
235
+ this.#retryBudget = new RetryBudget(options.retryBudget);
231
236
  }
232
237
 
233
238
  /**
@@ -431,6 +436,7 @@ export class ClientRaw {
431
436
  signal,
432
437
  this.#currentActorInvocation,
433
438
  this.#startCallSpan,
439
+ this.#retryBudget,
434
440
  );
435
441
  }
436
442
 
@@ -88,6 +88,17 @@ export const ClientConfigSchemaBase = z.object({
88
88
  // `proxyWebSocket`
89
89
  getUpgradeWebSocket: z.custom<GetUpgradeWebSocket>().optional(),
90
90
 
91
+ /**
92
+ * Bounds retries of refused requests: `percent` retries per 100 requests (default 10), with
93
+ * up to `maxTokens` banked for bursts (default 100). A refusal past the budget is thrown.
94
+ */
95
+ retryBudget: z
96
+ .object({
97
+ percent: z.number().min(0).max(100).optional(),
98
+ maxTokens: z.number().int().min(0).optional(),
99
+ })
100
+ .optional(),
101
+
91
102
  /** Replaces global fetch for actor HTTP gateway requests. */
92
103
  fetch: z.custom<typeof globalThis.fetch>().optional(),
93
104
 
@@ -166,7 +177,7 @@ export function convertRegistryConfigToClientConfig(
166
177
  token: config.token,
167
178
  getToken: undefined,
168
179
  namespace: config.namespace,
169
- poolName: config.envoy.poolName,
180
+ poolName: config.node.poolName,
170
181
  headers: config.headers,
171
182
  gateway: { skipReadyWait: false },
172
183
  encoding: "bare",
@@ -1,4 +1,5 @@
1
1
  import { ActorError, HttpRequestError } from "./errors";
2
+ import { type RetryBudget, retryAfterMs, retryDelayMs } from "./retry-budget";
2
3
 
3
4
  export interface LifecycleBoundaryInfo {
4
5
  kind: "request_retry" | "reconnect_only";
@@ -271,6 +272,11 @@ async function waitWithSignal(ms: number, signal?: AbortSignal) {
271
272
  });
272
273
  }
273
274
 
275
+ /**
276
+ * Runs `run`, retrying retryable lifecycle and refusal errors with jittered exponential backoff
277
+ * that honors the service's `retry_after_ms` hint. With a `budget`, the call credits it once and
278
+ * every retry spends a token; a spent budget returns the error instead of retrying.
279
+ */
274
280
  export async function retryOnLifecycleBoundary<T>(
275
281
  run: () => Promise<T>,
276
282
  opts?: {
@@ -278,11 +284,13 @@ export async function retryOnLifecycleBoundary<T>(
278
284
  initialDelayMs?: number;
279
285
  maxDelayMs?: number;
280
286
  signal?: AbortSignal;
287
+ budget?: RetryBudget;
281
288
  },
282
289
  ): Promise<T> {
283
290
  const maxAttempts = opts?.maxAttempts ?? 5;
284
291
  const initialDelayMs = opts?.initialDelayMs ?? 25;
285
292
  const maxDelayMs = opts?.maxDelayMs ?? 200;
293
+ opts?.budget?.deposit();
286
294
 
287
295
  let lastError: unknown;
288
296
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
@@ -299,8 +307,15 @@ export async function retryOnLifecycleBoundary<T>(
299
307
  if (attempt === maxAttempts - 1) {
300
308
  break;
301
309
  }
310
+ if (opts?.budget && !opts.budget.tryWithdraw()) {
311
+ break;
312
+ }
302
313
 
303
- const delayMs = Math.min(initialDelayMs * 2 ** attempt, maxDelayMs);
314
+ const delayMs = retryDelayMs(attempt, {
315
+ initialDelayMs,
316
+ maxDelayMs,
317
+ hintMs: retryAfterMs(error),
318
+ });
304
319
  await waitWithSignal(delayMs, opts?.signal);
305
320
  }
306
321
  }
@@ -0,0 +1,78 @@
1
+ import { ActorError } from "./errors";
2
+
3
+ /** How many retries a client may spend, as a share of the requests it sends. */
4
+ export interface RetryBudgetOptions {
5
+ /** Retries allowed per 100 requests (default 10). */
6
+ percent?: number;
7
+ /** Retries the client can bank for a burst of refusals; the budget starts full (default 100). */
8
+ maxTokens?: number;
9
+ }
10
+
11
+ const DEFAULT_PERCENT = 10;
12
+ const DEFAULT_MAX_TOKENS = 100;
13
+
14
+ /**
15
+ * Bounds the client's retries of refused requests. Each request adds a fraction of a token and
16
+ * each retry spends one, so retries stay a fixed share of traffic while the service sheds load
17
+ * instead of multiplying it.
18
+ */
19
+ export class RetryBudget {
20
+ #tokens: number;
21
+ readonly #maxTokens: number;
22
+ readonly #deposit: number;
23
+
24
+ constructor(options: RetryBudgetOptions = {}) {
25
+ const percent = options.percent ?? DEFAULT_PERCENT;
26
+ const maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
27
+ if (!(percent >= 0 && percent <= 100)) {
28
+ throw new Error("retryBudget.percent must be in 0..=100");
29
+ }
30
+ if (!(maxTokens >= 0)) {
31
+ throw new Error("retryBudget.maxTokens must be at least 0");
32
+ }
33
+ this.#maxTokens = maxTokens;
34
+ this.#tokens = maxTokens;
35
+ this.#deposit = percent / 100;
36
+ }
37
+
38
+ /** Credits one request sent. */
39
+ deposit(): void {
40
+ this.#tokens = Math.min(this.#maxTokens, this.#tokens + this.#deposit);
41
+ }
42
+
43
+ /** Spends one token. Returns `false` when the budget is spent. */
44
+ tryWithdraw(): boolean {
45
+ // Rounding guards against float drift: ten 0.1 deposits must make one token.
46
+ if (this.#tokens + 1e-9 < 1) {
47
+ return false;
48
+ }
49
+ this.#tokens = Math.max(0, this.#tokens - 1);
50
+ return true;
51
+ }
52
+ }
53
+
54
+ /** The service's retry hint for a refused request, in milliseconds, when it sent one. */
55
+ export function retryAfterMs(error: unknown): number | undefined {
56
+ if (!(error instanceof ActorError)) {
57
+ return undefined;
58
+ }
59
+ const metadata = error.metadata;
60
+ if (metadata === null || typeof metadata !== "object") {
61
+ return undefined;
62
+ }
63
+ const hint = (metadata as { retry_after_ms?: unknown }).retry_after_ms;
64
+ return typeof hint === "number" && hint >= 0 ? hint : undefined;
65
+ }
66
+
67
+ /**
68
+ * The wait before retry `attempt` (0-based): jittered exponential backoff over
69
+ * `[initialDelayMs, maxDelayMs]`, never shorter than the service's hint.
70
+ */
71
+ export function retryDelayMs(
72
+ attempt: number,
73
+ opts: { initialDelayMs: number; maxDelayMs: number; hintMs?: number },
74
+ ): number {
75
+ const backoff = Math.min(opts.initialDelayMs * 2 ** attempt, opts.maxDelayMs);
76
+ const jittered = backoff / 2 + Math.random() * (backoff / 2);
77
+ return Math.max(jittered, opts.hintMs ?? 0);
78
+ }
@@ -19,21 +19,27 @@ export interface ParsedCloseReason {
19
19
  group: string;
20
20
  code: string;
21
21
  rayId?: string;
22
+ /** The service's retry hint, from a `#retry_after_ms=<ms>` suffix. */
23
+ retryAfterMs?: number;
22
24
  }
23
25
 
26
+ const RETRY_AFTER_SUFFIX = /^retry_after_ms=(\d+)$/;
27
+
24
28
  /**
25
29
  * Parses WebSocket close reason string into structured data.
26
30
  *
27
31
  * Expected format examples:
28
32
  * - "guard.actor_runner_failed#t1s80so6h3irenp8ymzltfoittcl00"
29
33
  * - "ws.client_closed"
34
+ * - "guard.service_unavailable#retry_after_ms=1000", a refusal with its retry hint. The hint
35
+ * rides after `#` so older clients still read the retryable `<group>.<code>`.
30
36
  *
31
37
  * Returns undefined if the format is invalid
32
38
  */
33
39
  export function parseWebSocketCloseReason(
34
40
  reason: string,
35
41
  ): ParsedCloseReason | undefined {
36
- const [mainPart, rayId] = reason.split("#");
42
+ const [mainPart, suffix] = reason.split("#");
37
43
  const [group, code] = mainPart.split(".");
38
44
 
39
45
  if (!group || !code) {
@@ -41,10 +47,15 @@ export function parseWebSocketCloseReason(
41
47
  return undefined;
42
48
  }
43
49
 
50
+ const retryAfter = suffix?.match(RETRY_AFTER_SUFFIX);
51
+ if (retryAfter) {
52
+ return { group, code, retryAfterMs: Number(retryAfter[1]) };
53
+ }
54
+
44
55
  return {
45
56
  group,
46
57
  code,
47
- rayId,
58
+ rayId: suffix,
48
59
  };
49
60
  }
50
61
 
@@ -52,7 +52,13 @@ export interface SqliteTransactionOptions {
52
52
  * to change without notice.
53
53
  */
54
54
  export interface SqliteProfilingOptions {
55
- enabled?: boolean;
55
+ /** Exact SQL statements to give fingerprint labels. Profiling defaults to actor
56
+ * aggregates without fingerprint labels. Profile logs remain enabled.
57
+ * RIVETKIT_SQLITE_PROFILING overrides selection with a JSON object containing
58
+ * queries and/or fingerprints arrays. An empty object clears fingerprint selection. */
59
+ queries?: string[];
60
+ /** Exact query fingerprints to label separately, as reported in profile logs. */
61
+ fingerprints?: string[];
56
62
  maxTrackedStatementFingerprints?: number;
57
63
  maxTrackedTransactionFingerprints?: number;
58
64
  maxPrometheusSeries?: number;
@@ -108,6 +108,11 @@ export type StateAwareSqliteDatabase = SqliteDatabase & {
108
108
  ): Promise<SqliteTransactionDatabase>;
109
109
  };
110
110
 
111
+ type NativeSqliteDatabase = StateAwareSqliteDatabase & {
112
+ /** Stop JS calls while leaving the runtime-owned backend open. */
113
+ detachForShutdown(): Promise<void>;
114
+ };
115
+
111
116
  export interface JsNativeTransactionLike {
112
117
  exec(sql: string): Promise<NativeExecResult>;
113
118
  execute(
@@ -311,7 +316,7 @@ class NativeCloseGate {
311
316
  };
312
317
  }
313
318
 
314
- async close(callback: () => Promise<void>): Promise<void> {
319
+ async close(callback?: () => Promise<void>): Promise<void> {
315
320
  if (this.#closed) {
316
321
  return;
317
322
  }
@@ -319,15 +324,17 @@ class NativeCloseGate {
319
324
  if (this.#active > 0) {
320
325
  await new Promise<void>((resolve) => this.#waiters.push(resolve));
321
326
  }
322
- await callback();
327
+ await callback?.();
323
328
  }
324
329
  }
325
330
 
326
331
  export function wrapJsNativeDatabase(
327
332
  database: JsNativeDatabaseLike,
328
- ): StateAwareSqliteDatabase {
333
+ ): NativeSqliteDatabase {
329
334
  const gate = new NativeCloseGate();
330
- let closePromise: Promise<void> | undefined;
335
+ // Detach and explicit close are mutually exclusive terminal operations.
336
+ // Whichever starts first defines whether the backend is physically closed.
337
+ let terminalPromise: Promise<void> | undefined;
331
338
  let lastInsertRowId: number | null = null;
332
339
 
333
340
  const executeNative = async (
@@ -485,8 +492,12 @@ export function wrapJsNativeDatabase(
485
492
  return normalizeNativeMetrics(database.metrics?.());
486
493
  },
487
494
  async close(): Promise<void> {
488
- closePromise ??= gate.close(() => database.close());
489
- await closePromise;
495
+ terminalPromise ??= gate.close(() => database.close());
496
+ await terminalPromise;
497
+ },
498
+ async detachForShutdown(): Promise<void> {
499
+ terminalPromise ??= gate.close();
500
+ await terminalPromise;
490
501
  },
491
502
  };
492
503
  }
@@ -182,7 +182,7 @@ export function handleMetadataRequest(
182
182
  version: VERSION,
183
183
  envoy: {
184
184
  kind: envoyKind,
185
- version: config.envoy.version,
185
+ version: config.node.version,
186
186
  },
187
187
  envoyProtocolVersion: envoyProtocol.VERSION,
188
188
  actorNames: buildActorNames(config),
@@ -6,7 +6,7 @@ export const ActorSchema = z.object({
6
6
  name: z.string(),
7
7
  key: z.string(),
8
8
  namespace_id: RivetIdSchema,
9
- runner_name_selector: z.string(),
9
+ pool: z.string(),
10
10
  create_ts: z.number(),
11
11
  connectable_ts: z.number().nullable().optional(),
12
12
  destroy_ts: z.number().nullable().optional(),
@@ -31,7 +31,7 @@ export type ActorsListResponse = z.infer<typeof ActorsListResponseSchema>;
31
31
  export const ActorsCreateRequestSchema = z.object({
32
32
  datacenter: z.string().optional(),
33
33
  name: z.string(),
34
- runner_name_selector: z.string(),
34
+ pool: z.string(),
35
35
  crash_policy: z.string(),
36
36
  key: z.string().nullable().optional(),
37
37
  input: z.string().nullable().optional(),
@@ -48,7 +48,7 @@ export const ActorsGetOrCreateRequestSchema = z.object({
48
48
  datacenter: z.string().optional(),
49
49
  name: z.string(),
50
50
  key: z.string(),
51
- runner_name_selector: z.string(),
51
+ pool: z.string(),
52
52
  crash_policy: z.string(),
53
53
  input: z.string().nullable().optional(),
54
54
  });
@@ -1,3 +1,4 @@
1
+ import { actorKeyToGatewayQuery } from "@/actor/keys";
1
2
  import {
2
3
  type ClientConfig,
3
4
  DEFAULT_MAX_QUERY_INPUT_SIZE,
@@ -289,8 +290,9 @@ export function buildActorQueryGatewayUrl(
289
290
  }
290
291
 
291
292
  function pushKeyQueryParams(params: URLSearchParams, key: string[]): void {
292
- if (key.length > 0) {
293
- params.append("rvt-key", key.join(","));
293
+ const rvtKey = actorKeyToGatewayQuery(key);
294
+ if (rvtKey !== undefined) {
295
+ params.append("rvt-key", rvtKey);
294
296
  }
295
297
  }
296
298
 
@@ -167,17 +167,6 @@ export interface RunnerConfigsResponse {
167
167
  runner_configs: Record<string, RunnerConfigDatacenters>;
168
168
  }
169
169
 
170
- export async function getRunnerConfig(
171
- config: ClientConfig,
172
- name: string,
173
- ): Promise<RunnerConfigsResponse> {
174
- return apiCall<never, RunnerConfigsResponse>(
175
- config,
176
- "GET",
177
- `/runner-configs?runner_name=${name}`,
178
- );
179
- }
180
-
181
170
  // MARK: Update runner config
182
171
  export interface RegistryConfigRequest {
183
172
  datacenters: Record<
@@ -212,11 +201,11 @@ export async function updateRunnerConfig(
212
201
  runnerName: string,
213
202
  request: RegistryConfigRequest,
214
203
  ): Promise<void> {
215
- return apiCall<RegistryConfigRequest, void>(
204
+ return apiCall<unknown, void>(
216
205
  config,
217
206
  "PUT",
218
- `/runner-configs/${runnerName}`,
219
- request,
207
+ `/pools/${encodeURIComponent(runnerName)}`,
208
+ Object.values(request.datacenters)[0],
220
209
  );
221
210
  }
222
211
 
@@ -237,3 +226,44 @@ export async function kvGet(
237
226
  `/actors/${encodeURIComponent(actorId)}/kv/keys/${encodeURIComponent(key)}`,
238
227
  );
239
228
  }
229
+
230
+ /** Pool changes apply when each node next connects. */
231
+ export interface PoolConfig {
232
+ drain_on_version_upgrade?: boolean;
233
+ actor_eviction_delay?: number;
234
+ actor_eviction_period?: number;
235
+ actor_eviction_rate?: number;
236
+ metadata?: unknown;
237
+ }
238
+
239
+ export function upsertPool(
240
+ config: ClientConfig,
241
+ name: string,
242
+ pool: PoolConfig,
243
+ ): Promise<{ pool: PoolConfig & { name: string } }> {
244
+ return apiCall(config, "PUT", `/pools/${encodeURIComponent(name)}`, pool);
245
+ }
246
+
247
+ export function getPool(
248
+ config: ClientConfig,
249
+ name: string,
250
+ ): Promise<{ pool: PoolConfig & { name: string } }> {
251
+ return apiCall(config, "GET", `/pools/${encodeURIComponent(name)}`);
252
+ }
253
+
254
+ export function listPools(
255
+ config: ClientConfig,
256
+ page?: { limit?: number; cursor?: string },
257
+ ): Promise<{
258
+ pools: Array<PoolConfig & { name: string }>;
259
+ pagination: { cursor: string | null };
260
+ }> {
261
+ const query = new URLSearchParams();
262
+ if (page?.limit !== undefined) query.set("limit", String(page.limit));
263
+ if (page?.cursor !== undefined) query.set("cursor", page.cursor);
264
+ return apiCall(config, "GET", `/pools?${query}`);
265
+ }
266
+
267
+ export function deletePool(config: ClientConfig, name: string): Promise<void> {
268
+ return apiCall(config, "DELETE", `/pools/${encodeURIComponent(name)}`);
269
+ }
@@ -254,7 +254,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
254
254
  datacenter: region,
255
255
  name,
256
256
  key: serializeActorKey(key),
257
- runner_name_selector: poolName ?? this.#config.poolName,
257
+ pool: poolName ?? this.#config.poolName,
258
258
  input: actorInput
259
259
  ? uint8ArrayToBase64(
260
260
  encodeCborCompat(actorInput as JsonCompatValue),
@@ -325,7 +325,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
325
325
  createActor(config, {
326
326
  datacenter: region,
327
327
  name,
328
- runner_name_selector: poolName ?? this.#config.poolName,
328
+ pool: poolName ?? this.#config.poolName,
329
329
  key: serializeActorKey(key),
330
330
  input: input
331
331
  ? uint8ArrayToBase64(
@@ -11,7 +11,7 @@ export const EnvoyConfigSchema = z.object({
11
11
 
12
12
  // Deprecated.
13
13
  totalSlots: z.number().default(() => getRivetTotalSlots() ?? 100000),
14
- envoyKey: z.string().optional(),
14
+ nodeKey: z.string().optional(),
15
15
  });
16
16
  export type EnvoyConfigInput = z.input<typeof EnvoyConfigSchema>;
17
17
  export type EnvoyConfig = z.infer<typeof EnvoyConfigSchema>;