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.
- package/dist/browser/client.d.ts +58 -9
- package/dist/browser/client.js +193 -20
- package/dist/browser/client.js.map +1 -1
- package/dist/browser/inspector/client.js +1 -2
- package/dist/browser/inspector/client.js.map +1 -1
- package/dist/tsup/{chunk-P2PHHJMH.cjs → chunk-25MUHNWE.cjs} +326 -166
- package/dist/tsup/chunk-25MUHNWE.cjs.map +1 -0
- package/dist/tsup/{chunk-5SN6GAYX.cjs → chunk-3SPYZMME.cjs} +3 -3
- package/dist/tsup/{chunk-5SN6GAYX.cjs.map → chunk-3SPYZMME.cjs.map} +1 -1
- package/dist/tsup/{chunk-4SNS3PYZ.js → chunk-3URL73AV.js} +2 -2
- package/dist/tsup/{chunk-EKAQ7HG2.js → chunk-442XBCAF.js} +3 -3
- package/dist/tsup/{chunk-CORYIKDC.js → chunk-5F7FWAJI.js} +10 -11
- package/dist/tsup/chunk-5F7FWAJI.js.map +1 -0
- package/dist/tsup/{chunk-I472KTIC.js → chunk-6IGGYL2C.js} +185 -25
- package/dist/tsup/chunk-6IGGYL2C.js.map +1 -0
- package/dist/tsup/{chunk-JI6GZ2C2.js → chunk-A2CF6PFH.js} +18 -1
- package/dist/tsup/chunk-A2CF6PFH.js.map +1 -0
- package/dist/tsup/{chunk-OUB7SGSA.js → chunk-ANNVZW6B.js} +2 -2
- package/dist/tsup/{chunk-ZIIQQHC3.cjs → chunk-BCG7PTBS.cjs} +4 -4
- package/dist/tsup/{chunk-ZIIQQHC3.cjs.map → chunk-BCG7PTBS.cjs.map} +1 -1
- package/dist/tsup/{chunk-OJFVABRH.cjs → chunk-CJ3MX5MI.cjs} +7 -7
- package/dist/tsup/{chunk-OJFVABRH.cjs.map → chunk-CJ3MX5MI.cjs.map} +1 -1
- package/dist/tsup/{chunk-GEUBEODU.cjs → chunk-EPPV7WB2.cjs} +3 -3
- package/dist/tsup/{chunk-GEUBEODU.cjs.map → chunk-EPPV7WB2.cjs.map} +1 -1
- package/dist/tsup/{chunk-MXV26ZBB.js → chunk-OLZ4KAFL.js} +4 -4
- package/dist/tsup/{chunk-5UUOECX2.cjs → chunk-QDRZMCX5.cjs} +10 -10
- package/dist/tsup/{chunk-5UUOECX2.cjs.map → chunk-QDRZMCX5.cjs.map} +1 -1
- package/dist/tsup/{chunk-MYWZCDML.cjs → chunk-RTWH3AX3.cjs} +19 -2
- package/dist/tsup/chunk-RTWH3AX3.cjs.map +1 -0
- package/dist/tsup/{chunk-H3SGQEHZ.js → chunk-WGKL7HMH.js} +2 -2
- package/dist/tsup/{chunk-6LOYTFPZ.js → chunk-WGQHG26S.js} +2 -2
- package/dist/tsup/{chunk-2BIRGEIU.cjs → chunk-XSOHGU4G.cjs} +10 -11
- package/dist/tsup/chunk-XSOHGU4G.cjs.map +1 -0
- package/dist/tsup/{chunk-ABPZG7QV.cjs → chunk-YZVVID47.cjs} +3 -3
- package/dist/tsup/{chunk-ABPZG7QV.cjs.map → chunk-YZVVID47.cjs.map} +1 -1
- package/dist/tsup/client/mod.cjs +6 -6
- package/dist/tsup/client/mod.d.cts +15 -3
- package/dist/tsup/client/mod.d.ts +15 -3
- package/dist/tsup/client/mod.js +5 -5
- package/dist/tsup/common/log.cjs +2 -2
- package/dist/tsup/common/log.js +1 -1
- package/dist/tsup/common/websocket.cjs +3 -3
- package/dist/tsup/common/websocket.js +2 -2
- package/dist/tsup/{config-osdJqQwX.d.cts → config-8NgsxIcT.d.cts} +43 -12
- package/dist/tsup/{config-ey2I2zz5.d.ts → config-B2wlj9wx.d.cts} +7 -1
- package/dist/tsup/{config-ey2I2zz5.d.cts → config-B2wlj9wx.d.ts} +7 -1
- package/dist/tsup/{config-Cv6pjCt-.d.ts → config-DJ3KW-fe.d.ts} +43 -12
- package/dist/tsup/{context-CoVbKBuk.d.ts → context-BL8kUGjq.d.ts} +2 -2
- package/dist/tsup/{context-DpjrM7n7.d.cts → context-_RoE_YHL.d.cts} +2 -2
- package/dist/tsup/db/drizzle.cjs +2 -2
- package/dist/tsup/db/drizzle.d.cts +1 -1
- package/dist/tsup/db/drizzle.d.ts +1 -1
- package/dist/tsup/db/drizzle.js +1 -1
- package/dist/tsup/db/mod.cjs +3 -3
- package/dist/tsup/db/mod.d.cts +2 -2
- package/dist/tsup/db/mod.d.ts +2 -2
- package/dist/tsup/db/mod.js +2 -2
- package/dist/tsup/dynamic/mod.d.cts +2 -2
- package/dist/tsup/dynamic/mod.d.ts +2 -2
- package/dist/tsup/inspector/mod.cjs +5 -5
- package/dist/tsup/inspector/mod.js +4 -4
- package/dist/tsup/inspector/workflow.cjs +4 -4
- package/dist/tsup/inspector/workflow.js +3 -3
- package/dist/tsup/inspector-tab/mod.d.cts +3 -3
- package/dist/tsup/inspector-tab/mod.d.ts +3 -3
- package/dist/tsup/mod.cjs +254 -166
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +4 -4
- package/dist/tsup/mod.d.ts +4 -4
- package/dist/tsup/mod.js +168 -80
- package/dist/tsup/mod.js.map +1 -1
- package/dist/tsup/test/mod.cjs +7 -7
- package/dist/tsup/test/mod.d.cts +2 -2
- package/dist/tsup/test/mod.d.ts +2 -2
- package/dist/tsup/test/mod.js +5 -5
- package/dist/tsup/unstable/migrations.d.cts +1 -1
- package/dist/tsup/unstable/migrations.d.ts +1 -1
- package/dist/tsup/utils.cjs +2 -2
- package/dist/tsup/utils.js +1 -1
- package/dist/tsup/workflow/mod.cjs +17 -17
- package/dist/tsup/workflow/mod.d.cts +4 -4
- package/dist/tsup/workflow/mod.d.ts +4 -4
- package/dist/tsup/workflow/mod.js +5 -5
- package/package.json +9 -10
- package/src/actor/keys.ts +86 -0
- package/src/client/actor-conn.ts +92 -6
- package/src/client/actor-handle.ts +21 -3
- package/src/client/client.ts +6 -0
- package/src/client/config.ts +12 -1
- package/src/client/lifecycle-errors.ts +16 -1
- package/src/client/retry-budget.ts +78 -0
- package/src/client/utils.ts +13 -2
- package/src/common/database/config.ts +7 -1
- package/src/common/database/native-database.ts +17 -6
- package/src/common/router.ts +1 -1
- package/src/engine-api/actors.ts +3 -3
- package/src/engine-client/actor-websocket-client.ts +4 -2
- package/src/engine-client/api-endpoints.ts +44 -14
- package/src/engine-client/mod.ts +2 -2
- package/src/registry/config/envoy.ts +1 -1
- package/src/registry/config/index.ts +28 -31
- package/src/registry/index.ts +51 -19
- package/src/registry/napi-runtime.ts +3 -2
- package/src/registry/native.ts +126 -34
- package/src/registry/runtime.ts +27 -10
- package/src/registry/wasm-runtime.ts +6 -3
- package/src/utils/env-vars.ts +15 -10
- package/dist/tsup/chunk-2BIRGEIU.cjs.map +0 -1
- package/dist/tsup/chunk-CORYIKDC.js.map +0 -1
- package/dist/tsup/chunk-I472KTIC.js.map +0 -1
- package/dist/tsup/chunk-JI6GZ2C2.js.map +0 -1
- package/dist/tsup/chunk-MYWZCDML.cjs.map +0 -1
- package/dist/tsup/chunk-P2PHHJMH.cjs.map +0 -1
- package/src/actor/config.test.ts +0 -78
- package/src/actor/keys.test.ts +0 -275
- package/src/actor/utils.test.ts +0 -48
- package/src/client/test.ts +0 -44
- package/src/client/token-provider.test.ts +0 -86
- package/src/common/database/mod.test.ts +0 -364
- package/src/common/database/native-database.test.ts +0 -220
- package/src/common/inline-websocket-adapter.test.ts +0 -51
- package/src/db/drizzle.test.ts +0 -164
- package/src/registry/config/index.test.ts +0 -25
- package/src/registry/run-handler-coordinator.test.ts +0 -252
- package/src/registry/runtime.test.ts +0 -124
- package/src/unstable/migrations.test.ts +0 -170
- package/src/utils/endpoint-parser.test.ts +0 -206
- package/src/utils.test.ts +0 -34
- package/src/workflow/driver.test.ts +0 -62
- /package/dist/tsup/{chunk-4SNS3PYZ.js.map → chunk-3URL73AV.js.map} +0 -0
- /package/dist/tsup/{chunk-EKAQ7HG2.js.map → chunk-442XBCAF.js.map} +0 -0
- /package/dist/tsup/{chunk-OUB7SGSA.js.map → chunk-ANNVZW6B.js.map} +0 -0
- /package/dist/tsup/{chunk-MXV26ZBB.js.map → chunk-OLZ4KAFL.js.map} +0 -0
- /package/dist/tsup/{chunk-H3SGQEHZ.js.map → chunk-WGKL7HMH.js.map} +0 -0
- /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
|
|
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, {
|
|
327
|
+
return await retryOnLifecycleBoundary(run, {
|
|
328
|
+
signal,
|
|
329
|
+
budget: this.#retryBudget,
|
|
330
|
+
});
|
|
313
331
|
};
|
|
314
332
|
if (!call) {
|
|
315
333
|
return await send();
|
package/src/client/client.ts
CHANGED
|
@@ -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
|
|
package/src/client/config.ts
CHANGED
|
@@ -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.
|
|
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 =
|
|
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
|
+
}
|
package/src/client/utils.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
|
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
|
-
):
|
|
333
|
+
): NativeSqliteDatabase {
|
|
329
334
|
const gate = new NativeCloseGate();
|
|
330
|
-
|
|
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
|
-
|
|
489
|
-
await
|
|
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
|
}
|
package/src/common/router.ts
CHANGED
|
@@ -182,7 +182,7 @@ export function handleMetadataRequest(
|
|
|
182
182
|
version: VERSION,
|
|
183
183
|
envoy: {
|
|
184
184
|
kind: envoyKind,
|
|
185
|
-
version: config.
|
|
185
|
+
version: config.node.version,
|
|
186
186
|
},
|
|
187
187
|
envoyProtocolVersion: envoyProtocol.VERSION,
|
|
188
188
|
actorNames: buildActorNames(config),
|
package/src/engine-api/actors.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
-
|
|
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<
|
|
204
|
+
return apiCall<unknown, void>(
|
|
216
205
|
config,
|
|
217
206
|
"PUT",
|
|
218
|
-
`/
|
|
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
|
+
}
|
package/src/engine-client/mod.ts
CHANGED
|
@@ -254,7 +254,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
|
|
|
254
254
|
datacenter: region,
|
|
255
255
|
name,
|
|
256
256
|
key: serializeActorKey(key),
|
|
257
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>;
|