rivetkit 2.3.0-rc.5 → 2.3.0-rc.6

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 (113) hide show
  1. package/dist/browser/client.d.ts +21 -18
  2. package/dist/browser/client.js +735 -170
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +1 -1
  5. package/dist/browser/inspector/client.js.map +1 -1
  6. package/dist/tsup/actor/errors.cjs +4 -2
  7. package/dist/tsup/actor/errors.cjs.map +1 -1
  8. package/dist/tsup/actor/errors.d.cts +1 -74
  9. package/dist/tsup/actor/errors.d.ts +1 -74
  10. package/dist/tsup/actor/errors.js +3 -1
  11. package/dist/tsup/agent-os/index.cjs +1 -1
  12. package/dist/tsup/agent-os/index.cjs.map +1 -1
  13. package/dist/tsup/agent-os/index.d.cts +18 -7
  14. package/dist/tsup/agent-os/index.d.ts +18 -7
  15. package/dist/tsup/agent-os/index.js +1 -1
  16. package/dist/tsup/agent-os/index.js.map +1 -1
  17. package/dist/tsup/{chunk-2GANBXVP.cjs → chunk-2G64KSZQ.cjs} +10 -10
  18. package/dist/tsup/{chunk-2GANBXVP.cjs.map → chunk-2G64KSZQ.cjs.map} +1 -1
  19. package/dist/tsup/{chunk-N2DQSJIW.js → chunk-6S25NVAP.js} +13 -46
  20. package/dist/tsup/chunk-6S25NVAP.js.map +1 -0
  21. package/dist/tsup/{chunk-NATOT3ET.js → chunk-CAF6JDJE.js} +4 -4
  22. package/dist/tsup/{chunk-PGYEMIOE.js → chunk-DEO7MMWQ.js} +2 -2
  23. package/dist/tsup/{chunk-SULB574D.js → chunk-EMO6E3PJ.js} +3 -3
  24. package/dist/tsup/{chunk-FTZIZ3JG.cjs → chunk-ENK7C66G.cjs} +838 -236
  25. package/dist/tsup/chunk-ENK7C66G.cjs.map +1 -0
  26. package/dist/tsup/{chunk-JY73X7VU.js → chunk-FLODVLYW.js} +690 -88
  27. package/dist/tsup/chunk-FLODVLYW.js.map +1 -0
  28. package/dist/tsup/{chunk-OVJX4IFY.cjs → chunk-HTR4YLNT.cjs} +4 -4
  29. package/dist/tsup/{chunk-OVJX4IFY.cjs.map → chunk-HTR4YLNT.cjs.map} +1 -1
  30. package/dist/tsup/{chunk-LELRJK66.cjs → chunk-JALSAX7Z.cjs} +3 -3
  31. package/dist/tsup/{chunk-LELRJK66.cjs.map → chunk-JALSAX7Z.cjs.map} +1 -1
  32. package/dist/tsup/{chunk-JRCZDHXT.cjs → chunk-K5BA2LEO.cjs} +19 -52
  33. package/dist/tsup/chunk-K5BA2LEO.cjs.map +1 -0
  34. package/dist/tsup/{chunk-K34B3OVG.js → chunk-KIWH5H3K.js} +30 -9
  35. package/dist/tsup/chunk-KIWH5H3K.js.map +1 -0
  36. package/dist/tsup/{chunk-NW2J4SOL.cjs → chunk-LIXXFXVR.cjs} +5 -5
  37. package/dist/tsup/{chunk-NW2J4SOL.cjs.map → chunk-LIXXFXVR.cjs.map} +1 -1
  38. package/dist/tsup/{chunk-HR547GVH.cjs → chunk-M5C7YNI5.cjs} +8 -8
  39. package/dist/tsup/{chunk-HR547GVH.cjs.map → chunk-M5C7YNI5.cjs.map} +1 -1
  40. package/dist/tsup/{chunk-V3QNBJ7N.cjs → chunk-QAZLM4WT.cjs} +31 -10
  41. package/dist/tsup/chunk-QAZLM4WT.cjs.map +1 -0
  42. package/dist/tsup/{chunk-LDTT6WKJ.js → chunk-RTC2AZGB.js} +2 -2
  43. package/dist/tsup/{chunk-UXTP4EBU.js → chunk-ZI5QJMKO.js} +2 -2
  44. package/dist/tsup/client/mod.cjs +7 -7
  45. package/dist/tsup/client/mod.d.cts +3 -4
  46. package/dist/tsup/client/mod.d.ts +3 -4
  47. package/dist/tsup/client/mod.js +6 -6
  48. package/dist/tsup/common/log.cjs +3 -3
  49. package/dist/tsup/common/log.js +2 -2
  50. package/dist/tsup/common/websocket.cjs +4 -4
  51. package/dist/tsup/common/websocket.js +3 -3
  52. package/dist/tsup/{config-CvQUtDp9.d.ts → config-0Ta55UV0.d.ts} +10 -9
  53. package/dist/tsup/{config-C-a9vrke.d.cts → config-Ca8dN4cS.d.cts} +10 -9
  54. package/dist/tsup/{context-A7R0bsZL.d.ts → context-B_IWbWne.d.ts} +1 -1
  55. package/dist/tsup/{context-CA3r-pf2.d.cts → context-CUrQ9MHc.d.cts} +1 -1
  56. package/dist/tsup/inspector/mod.cjs +6 -6
  57. package/dist/tsup/inspector/mod.js +5 -5
  58. package/dist/tsup/mod.cjs +252 -207
  59. package/dist/tsup/mod.cjs.map +1 -1
  60. package/dist/tsup/mod.d.cts +4 -5
  61. package/dist/tsup/mod.d.ts +4 -5
  62. package/dist/tsup/mod.js +183 -138
  63. package/dist/tsup/mod.js.map +1 -1
  64. package/dist/tsup/test/mod.cjs +10 -10
  65. package/dist/tsup/test/mod.d.cts +2 -3
  66. package/dist/tsup/test/mod.d.ts +2 -3
  67. package/dist/tsup/test/mod.js +6 -6
  68. package/dist/tsup/utils-DVekpm4I.d.cts +103 -0
  69. package/dist/tsup/utils-DVekpm4I.d.ts +103 -0
  70. package/dist/tsup/utils.cjs +3 -3
  71. package/dist/tsup/utils.d.cts +1 -1
  72. package/dist/tsup/utils.d.ts +1 -1
  73. package/dist/tsup/utils.js +2 -2
  74. package/dist/tsup/workflow/mod.cjs +9 -9
  75. package/dist/tsup/workflow/mod.d.cts +4 -5
  76. package/dist/tsup/workflow/mod.d.ts +4 -5
  77. package/dist/tsup/workflow/mod.js +5 -5
  78. package/package.json +8 -8
  79. package/src/actor/errors.ts +53 -7
  80. package/src/client/actor-conn.ts +52 -29
  81. package/src/client/actor-handle.ts +57 -23
  82. package/src/client/errors.ts +2 -1
  83. package/src/client/raw-utils.ts +2 -4
  84. package/src/client/utils.ts +32 -3
  85. package/src/common/actor-router-consts.ts +4 -0
  86. package/src/common/bare/generated/client-protocol/v4.ts +599 -0
  87. package/src/common/client-protocol-versioned.ts +125 -18
  88. package/src/common/client-protocol-zod.ts +7 -0
  89. package/src/common/client-protocol.ts +1 -1
  90. package/src/common/database/native-database.test.ts +35 -0
  91. package/src/common/database/native-database.ts +8 -4
  92. package/src/common/router.ts +38 -8
  93. package/src/common/utils.ts +9 -52
  94. package/src/drivers/engine/actor-driver.ts +18 -17
  95. package/src/registry/config/index.ts +3 -5
  96. package/src/registry/index.ts +156 -19
  97. package/src/registry/napi-runtime.ts +44 -6
  98. package/src/registry/native.ts +5 -152
  99. package/src/registry/runtime.ts +16 -5
  100. package/src/registry/wasm-runtime.ts +22 -2
  101. package/dist/tsup/chunk-FTZIZ3JG.cjs.map +0 -1
  102. package/dist/tsup/chunk-JRCZDHXT.cjs.map +0 -1
  103. package/dist/tsup/chunk-JY73X7VU.js.map +0 -1
  104. package/dist/tsup/chunk-K34B3OVG.js.map +0 -1
  105. package/dist/tsup/chunk-N2DQSJIW.js.map +0 -1
  106. package/dist/tsup/chunk-V3QNBJ7N.cjs.map +0 -1
  107. package/dist/tsup/utils-fwx3o3K9.d.cts +0 -18
  108. package/dist/tsup/utils-fwx3o3K9.d.ts +0 -18
  109. /package/dist/tsup/{chunk-NATOT3ET.js.map → chunk-CAF6JDJE.js.map} +0 -0
  110. /package/dist/tsup/{chunk-PGYEMIOE.js.map → chunk-DEO7MMWQ.js.map} +0 -0
  111. /package/dist/tsup/{chunk-SULB574D.js.map → chunk-EMO6E3PJ.js.map} +0 -0
  112. /package/dist/tsup/{chunk-LDTT6WKJ.js.map → chunk-RTC2AZGB.js.map} +0 -0
  113. /package/dist/tsup/{chunk-UXTP4EBU.js.map → chunk-ZI5QJMKO.js.map} +0 -0
@@ -78,7 +78,7 @@ import {
78
78
  import { logger } from "./log";
79
79
 
80
80
  const ENVOY_SSE_PING_INTERVAL = 1000;
81
- const ENVOY_STOP_WAIT_MS = 15_000;
81
+ const FALLBACK_ENVOY_STOP_WAIT_MS = 15_000;
82
82
  const INITIAL_SLEEP_TIMEOUT_MS = 250;
83
83
  const REMOTE_ACK_HOOK_QUERY_PARAM = "__rivetkitAckHook";
84
84
 
@@ -847,6 +847,7 @@ export class EngineActorDriver implements ActorDriver {
847
847
  return;
848
848
  }
849
849
  this.#isShuttingDown = true;
850
+ const envoyStopWaitMs = this.#envoyStopWaitMs();
850
851
 
851
852
  logger().info({ msg: "stopping engine actor driver", immediate });
852
853
  if (!immediate) {
@@ -861,7 +862,7 @@ export class EngineActorDriver implements ActorDriver {
861
862
  this.startSleep(actorId);
862
863
  }
863
864
 
864
- const actorSleepDeadline = Date.now() + ENVOY_STOP_WAIT_MS;
865
+ const actorSleepDeadline = Date.now() + envoyStopWaitMs;
865
866
  while (this.#actors.size > 0 && Date.now() < actorSleepDeadline) {
866
867
  await new Promise((resolve) => setTimeout(resolve, 50));
867
868
  }
@@ -870,7 +871,7 @@ export class EngineActorDriver implements ActorDriver {
870
871
  logger().warn({
871
872
  msg: "timed out waiting for actors to stop before envoy drain",
872
873
  remainingActors: this.#actors.size,
873
- waitMs: ENVOY_STOP_WAIT_MS,
874
+ waitMs: envoyStopWaitMs,
874
875
  });
875
876
  // Snapshot so concurrent removals from `stopActor` do not
876
877
  // invalidate the iterator.
@@ -912,13 +913,13 @@ export class EngineActorDriver implements ActorDriver {
912
913
  const stopped = await Promise.race([
913
914
  this.#envoyStopped.promise.then(() => true),
914
915
  new Promise<false>((resolve) =>
915
- setTimeout(() => resolve(false), ENVOY_STOP_WAIT_MS),
916
+ setTimeout(() => resolve(false), envoyStopWaitMs),
916
917
  ),
917
918
  ]);
918
919
  if (!stopped) {
919
920
  logger().warn({
920
921
  msg: "timed out waiting for envoy shutdown",
921
- waitMs: ENVOY_STOP_WAIT_MS,
922
+ waitMs: envoyStopWaitMs,
922
923
  });
923
924
  }
924
925
 
@@ -929,6 +930,16 @@ export class EngineActorDriver implements ActorDriver {
929
930
  await this.#envoy.started();
930
931
  }
931
932
 
933
+ #envoyStopWaitMs(): number {
934
+ const actorStopThreshold = Number(
935
+ this.#envoy.getProtocolMetadata()?.actorStopThreshold,
936
+ );
937
+ if (Number.isFinite(actorStopThreshold) && actorStopThreshold > 0) {
938
+ return actorStopThreshold;
939
+ }
940
+ return FALLBACK_ENVOY_STOP_WAIT_MS;
941
+ }
942
+
932
943
  async #bindHibernatableConnectSocket(
933
944
  binding: HibernatableConnectBinding,
934
945
  isRestoringHibernatable: boolean,
@@ -2190,12 +2201,7 @@ export class EngineActorDriver implements ActorDriver {
2190
2201
  isRestoringHibernatable,
2191
2202
  );
2192
2203
  } catch (error) {
2193
- const { group, code } = deconstructError(
2194
- error,
2195
- logger(),
2196
- {},
2197
- false,
2198
- );
2204
+ const { group, code } = deconstructError(error, false);
2199
2205
  logger().error({
2200
2206
  msg: "failed to bind dynamic hibernatable websocket",
2201
2207
  actorId,
@@ -2233,12 +2239,7 @@ export class EngineActorDriver implements ActorDriver {
2233
2239
  },
2234
2240
  );
2235
2241
  } catch (error) {
2236
- const { group, code } = deconstructError(
2237
- error,
2238
- logger(),
2239
- {},
2240
- false,
2241
- );
2242
+ const { group, code } = deconstructError(error, false);
2242
2243
  logger().error({
2243
2244
  msg: "failed to open dynamic websocket",
2244
2245
  actorId,
@@ -264,8 +264,8 @@ export const RegistryConfigSchema = z
264
264
  .object({
265
265
  /**
266
266
  * Wait this many milliseconds for the serve promise to resolve
267
- * after calling `CoreRegistry::shutdown()`. Defaults to 30s,
268
- * matching Kubernetes `terminationGracePeriodSeconds`.
267
+ * after calling `CoreRegistry::shutdown()`. Defaults to the
268
+ * engine-provided actor stop threshold once the envoy connects.
269
269
  *
270
270
  * Must be >= rivetkit-core's drain timeout (20s) + margin.
271
271
  */
@@ -273,8 +273,7 @@ export const RegistryConfigSchema = z
273
273
  .number()
274
274
  .int()
275
275
  .min(1_000)
276
- .optional()
277
- .default(30_000),
276
+ .optional(),
278
277
  /**
279
278
  * If true, rivetkit will not install SIGINT/SIGTERM handlers.
280
279
  * Use when the host application owns signal policy and will
@@ -284,7 +283,6 @@ export const RegistryConfigSchema = z
284
283
  })
285
284
  .optional()
286
285
  .default(() => ({
287
- gracePeriodMs: 30_000,
288
286
  disableSignalHandlers: false,
289
287
  })),
290
288
  })
@@ -13,6 +13,22 @@ import type { RuntimeServerlessResponseHead } from "./runtime";
13
13
 
14
14
  type ShutdownSignal = "SIGINT" | "SIGTERM";
15
15
 
16
+ function signalExitCode(signal: ShutdownSignal): number {
17
+ switch (signal) {
18
+ case "SIGINT":
19
+ return 130;
20
+ case "SIGTERM":
21
+ return 143;
22
+ }
23
+ }
24
+
25
+ function finishShutdownSignal(signal: ShutdownSignal): void {
26
+ if (process.pid === 1) {
27
+ process.exit(signalExitCode(signal));
28
+ }
29
+ process.kill(process.pid, signal);
30
+ }
31
+
16
32
  export type FetchHandler = (
17
33
  request: Request,
18
34
  ...args: any
@@ -22,13 +38,15 @@ export interface ServerlessHandler {
22
38
  fetch: FetchHandler;
23
39
  }
24
40
 
25
- export interface RegistryDiagnostics {
26
- mode: string;
27
- envoyActiveActorCount?: number | null;
41
+ export interface RegistryRoutes {
42
+ health(): Promise<Response>;
43
+ metadata(): Promise<Response>;
44
+ prometheusMetrics(request?: Request): Promise<Response>;
28
45
  }
29
46
 
30
47
  export class Registry<A extends RegistryActors> {
31
48
  #config: RegistryConfigInput<A>;
49
+ public readonly routes: RegistryRoutes;
32
50
 
33
51
  get config(): RegistryConfigInput<A> {
34
52
  return this.#config;
@@ -49,6 +67,12 @@ export class Registry<A extends RegistryActors> {
49
67
 
50
68
  constructor(config: RegistryConfigInput<A>) {
51
69
  this.#config = config;
70
+ this.routes = {
71
+ health: () => this.#healthRoute(),
72
+ metadata: () => this.#metadataRoute(),
73
+ prometheusMetrics: (request?: Request) =>
74
+ this.#prometheusMetricsRoute(request),
75
+ };
52
76
  }
53
77
 
54
78
  #ensureServerlessPoolConfigured(config: RegistryConfig): Promise<void> | undefined {
@@ -279,7 +303,92 @@ export class Registry<A extends RegistryActors> {
279
303
  };
280
304
  }
281
305
 
282
- public async diagnostics(): Promise<RegistryDiagnostics> {
306
+ /**
307
+ * Returns a health response suitable for mounting in a user-owned router.
308
+ */
309
+ async #healthRoute(): Promise<Response> {
310
+ const configured = await this.#activeConfiguredRegistry();
311
+ if (!configured) {
312
+ return jsonRouteResponse(503, {
313
+ status: "not_started",
314
+ runtime: "rivetkit",
315
+ version: VERSION,
316
+ });
317
+ }
318
+
319
+ const { runtime, registry } = configured;
320
+ if (!runtime.registryHealth) {
321
+ return jsonRouteResponse(501, {
322
+ status: "unsupported",
323
+ runtime: "rivetkit",
324
+ version: VERSION,
325
+ });
326
+ }
327
+
328
+ const response = await runtime.registryHealth(registry);
329
+ return new Response(new Uint8Array(response.body), {
330
+ status: response.status,
331
+ headers: response.headers,
332
+ });
333
+ }
334
+
335
+ /**
336
+ * Returns serverless metadata suitable for mounting in a user-owned router.
337
+ */
338
+ async #metadataRoute(): Promise<Response> {
339
+ const configured = await this.#activeConfiguredRegistry();
340
+ if (!configured) {
341
+ return new Response("registry not started\n", {
342
+ status: 503,
343
+ headers: { "content-type": "text/plain; charset=utf-8" },
344
+ });
345
+ }
346
+
347
+ const { runtime, registry } = configured;
348
+ if (!runtime.registryMetadata) {
349
+ return new Response("metadata is not supported by this runtime\n", {
350
+ status: 501,
351
+ headers: { "content-type": "text/plain; charset=utf-8" },
352
+ });
353
+ }
354
+
355
+ const response = await runtime.registryMetadata(registry);
356
+ return new Response(new Uint8Array(response.body), {
357
+ status: response.status,
358
+ headers: response.headers,
359
+ });
360
+ }
361
+
362
+ /**
363
+ * Returns a Prometheus metrics response suitable for mounting in a user-owned router.
364
+ */
365
+ async #prometheusMetricsRoute(_request?: Request): Promise<Response> {
366
+ const configured = await this.#activeConfiguredRegistry();
367
+ if (!configured) {
368
+ return new Response("registry not started\n", {
369
+ status: 503,
370
+ headers: { "content-type": "text/plain; charset=utf-8" },
371
+ });
372
+ }
373
+
374
+ const { runtime, registry } = configured;
375
+ if (!runtime.registryMetrics) {
376
+ return new Response("metrics are not supported by this runtime\n", {
377
+ status: 501,
378
+ headers: { "content-type": "text/plain; charset=utf-8" },
379
+ });
380
+ }
381
+
382
+ const response = await runtime.registryMetrics(registry);
383
+ return new Response(new Uint8Array(response.body), {
384
+ status: response.status,
385
+ headers: response.headers,
386
+ });
387
+ }
388
+
389
+ async #activeConfiguredRegistry(): Promise<
390
+ Awaited<ReturnType<typeof buildConfiguredRegistry>> | undefined
391
+ > {
283
392
  const candidates = [
284
393
  this.#runtimeServerlessPromise,
285
394
  this.#runtimeServeConfiguredPromise,
@@ -287,13 +396,8 @@ export class Registry<A extends RegistryActors> {
287
396
  candidate !== undefined
288
397
  );
289
398
 
290
- for (const candidate of candidates) {
291
- const { runtime, registry } = await candidate;
292
- const diagnostics = await runtime.registryDiagnostics?.(registry);
293
- if (diagnostics) return diagnostics;
294
- }
295
-
296
- return { mode: "not_started", envoyActiveActorCount: null };
399
+ if (candidates.length === 0) return undefined;
400
+ return await candidates[0]!;
297
401
  }
298
402
 
299
403
  /**
@@ -364,10 +468,11 @@ export class Registry<A extends RegistryActors> {
364
468
  ): void {
365
469
  if (this.#shutdownInFlight !== null) {
366
470
  // Second delivery of the same (or another) shutdown signal.
367
- // Remove our handler only (preserving any user-installed listeners)
368
- // and re-raise so Node proceeds with its default exit path.
471
+ // Remove our handler only, preserving any user-installed listeners.
472
+ // PID 1 must exit directly because re-raised default signals can be
473
+ // swallowed by the container signal path.
369
474
  this.#removeSignalHandlers();
370
- process.kill(process.pid, signal);
475
+ finishShutdownSignal(signal);
371
476
  return;
372
477
  }
373
478
  this.#shutdownInFlight = this.#runShutdown(
@@ -384,11 +489,13 @@ export class Registry<A extends RegistryActors> {
384
489
  config: RegistryConfig,
385
490
  configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
386
491
  ): Promise<void> {
387
- const gracePeriodMs = config.shutdown?.gracePeriodMs ?? 30_000;
492
+ const gracePeriodMs =
493
+ config.shutdown?.gracePeriodMs ??
494
+ (await this.#actorStopThresholdMs(configuredRegistryPromise)) ??
495
+ 30 * 60 * 1000;
388
496
  // Race the entire drain sequence (both modes + serve promise) against
389
- // a single grace ceiling. Without this, each mode's Rust-side drain
390
- // (20s) could stack sequentially and blow past gracePeriodMs before
391
- // we re-raise the signal.
497
+ // a single grace ceiling. By default, this uses the engine-provided
498
+ // actor stop threshold, matching Pegboard's hard cutoff for actors.
392
499
  const drain = async () => {
393
500
  // Shut down every live `CoreRegistry` we know about. Mode A
394
501
  // (`start()`) and Mode B (`handler()`) each build a separate
@@ -442,7 +549,30 @@ export class Registry<A extends RegistryActors> {
442
549
  ),
443
550
  ]);
444
551
  this.#removeSignalHandlers();
445
- process.kill(process.pid, signal);
552
+ finishShutdownSignal(signal);
553
+ }
554
+
555
+ async #actorStopThresholdMs(
556
+ configuredRegistryPromise: ReturnType<typeof buildConfiguredRegistry>,
557
+ ): Promise<number | undefined> {
558
+ try {
559
+ const { runtime, registry } = await configuredRegistryPromise;
560
+ const thresholdMs =
561
+ await runtime.registryActorStopThresholdMs?.(registry);
562
+ if (
563
+ thresholdMs !== undefined &&
564
+ Number.isFinite(thresholdMs) &&
565
+ thresholdMs > 0
566
+ ) {
567
+ return thresholdMs;
568
+ }
569
+ } catch (err) {
570
+ logger().warn(
571
+ { err },
572
+ "failed to read actor stop threshold for shutdown grace",
573
+ );
574
+ }
575
+ return undefined;
446
576
  }
447
577
 
448
578
  #removeSignalHandlers(): void {
@@ -524,6 +654,13 @@ function isServerlessMetadataRequest(request: Request, basePath: string): boolea
524
654
  return parsed.pathname === `${normalizedBase}/metadata`;
525
655
  }
526
656
 
657
+ function jsonRouteResponse(status: number, body: unknown): Response {
658
+ return new Response(JSON.stringify(body), {
659
+ status,
660
+ headers: { "content-type": "application/json" },
661
+ });
662
+ }
663
+
527
664
  export function setup<A extends RegistryActors>(
528
665
  input: RegistryConfigInput<A>,
529
666
  ): Registry<A> {
@@ -24,7 +24,7 @@ import type {
24
24
  RuntimeQueueTryNextBatchOptions,
25
25
  RuntimeQueueWaitOptions,
26
26
  RuntimeRequestSaveOpts,
27
- RuntimeRegistryDiagnostics,
27
+ RuntimeRegistryRouteResponse,
28
28
  RuntimeServeConfig,
29
29
  RuntimeServerlessRequest,
30
30
  RuntimeServerlessResponseHead,
@@ -202,13 +202,45 @@ export class NapiCoreRuntime implements CoreRuntime {
202
202
  await asNativeRegistry(registry).shutdown();
203
203
  }
204
204
 
205
- async registryDiagnostics(
205
+ async registryActorStopThresholdMs(
206
206
  registry: RegistryHandle,
207
- ): Promise<RuntimeRegistryDiagnostics> {
208
- const diagnostics = await asNativeRegistry(registry).diagnostics();
207
+ ): Promise<number | undefined> {
208
+ return (
209
+ (await asNativeRegistry(registry).actorStopThresholdMs()) ??
210
+ undefined
211
+ );
212
+ }
213
+
214
+ async registryHealth(
215
+ registry: RegistryHandle,
216
+ ): Promise<RuntimeRegistryRouteResponse> {
217
+ const response = await asNativeRegistry(registry).health();
218
+ return {
219
+ status: response.status,
220
+ headers: response.headers,
221
+ body: response.body,
222
+ };
223
+ }
224
+
225
+ async registryMetadata(
226
+ registry: RegistryHandle,
227
+ ): Promise<RuntimeRegistryRouteResponse> {
228
+ const response = asNativeRegistry(registry).metadata();
209
229
  return {
210
- mode: diagnostics.mode,
211
- envoyActiveActorCount: diagnostics.envoyActiveActorCount,
230
+ status: response.status,
231
+ headers: response.headers,
232
+ body: response.body,
233
+ };
234
+ }
235
+
236
+ async registryMetrics(
237
+ registry: RegistryHandle,
238
+ ): Promise<RuntimeRegistryRouteResponse> {
239
+ const response = asNativeRegistry(registry).metrics();
240
+ return {
241
+ status: response.status,
242
+ headers: response.headers,
243
+ body: response.body,
212
244
  };
213
245
  }
214
246
 
@@ -414,6 +446,12 @@ export class NapiCoreRuntime implements CoreRuntime {
414
446
  asNativeActorContext(ctx).waitUntil(promise);
415
447
  }
416
448
 
449
+ async actorWaitForTrackedShutdownWork(
450
+ ctx: ActorContextHandle,
451
+ ): Promise<boolean> {
452
+ return await asNativeActorContext(ctx).waitForTrackedShutdownWork();
453
+ }
454
+
417
455
  actorKeepAwake(ctx: ActorContextHandle, promise: Promise<unknown>): void {
418
456
  asNativeActorContext(ctx).keepAwake(promise);
419
457
  }
@@ -29,22 +29,9 @@ import {
29
29
  createClientWithDriver,
30
30
  } from "@/client/client";
31
31
  import { convertRegistryConfigToClientConfig } from "@/client/config";
32
- import {
33
- HEADER_CONN_PARAMS,
34
- HEADER_ENCODING,
35
- } from "@/common/actor-router-consts";
36
- import type * as protocol from "@/common/client-protocol";
37
- import {
38
- CURRENT_VERSION as CLIENT_PROTOCOL_CURRENT_VERSION,
39
- HTTP_RESPONSE_ERROR_VERSIONED,
40
- } from "@/common/client-protocol-versioned";
41
- import {
42
- type HttpResponseError as HttpResponseErrorJson,
43
- HttpResponseErrorSchema,
44
- } from "@/common/client-protocol-zod";
32
+ import { HEADER_CONN_PARAMS } from "@/common/actor-router-consts";
45
33
  import type { AnyDatabaseProvider } from "@/common/database/config";
46
34
  import { wrapJsNativeDatabase } from "@/common/database/native-database";
47
- import type { Encoding } from "@/common/encoding";
48
35
  import { decodeWorkflowHistoryTransport } from "@/common/inspector-transport";
49
36
  import { deconstructError, stringifyError } from "@/common/utils";
50
37
  import type {
@@ -61,11 +48,9 @@ import type {
61
48
  SqliteBackend,
62
49
  } from "@/registry/config";
63
50
  import {
64
- contentTypeForEncoding,
65
51
  decodeCborCompat,
66
52
  decodeCborJsonCompat,
67
53
  encodeCborCompat,
68
- serializeWithEncoding,
69
54
  } from "@/serde";
70
55
  import { getEnvUniversal, VERSION } from "@/utils";
71
56
  import { logger } from "./log";
@@ -269,9 +254,6 @@ type NativeDatabaseClientState = {
269
254
  type NativeActorRuntimeState = {
270
255
  sql?: ReturnType<typeof wrapJsNativeDatabase>;
271
256
  databaseClient?: NativeDatabaseClientState;
272
- keepAwakeCount?: number;
273
- deferSleepCleanupUntilKeepAwakeIdle?: boolean;
274
- deferredSleepCleanupActorCtx?: ActorContextHandleAdapter;
275
257
  varsInitialized?: boolean;
276
258
  vars?: unknown;
277
259
  destroyGate?: NativeDestroyGate;
@@ -424,26 +406,12 @@ async function cleanupNativeSleepRuntimeState(
424
406
  runtime: CoreRuntime,
425
407
  ctx: ActorContextHandle,
426
408
  ): Promise<void> {
409
+ await runtime.actorWaitForTrackedShutdownWork(ctx);
427
410
  await closeNativeDatabaseClient(runtime, ctx);
428
411
  await closeNativeSqlDatabase(runtime, ctx);
429
412
  clearNativeRuntimeState(runtime, ctx);
430
413
  }
431
414
 
432
- async function cleanupDeferredNativeSleepRuntimeState(
433
- runtime: CoreRuntime,
434
- ctx: ActorContextHandle,
435
- runtimeState: NativeActorRuntimeState,
436
- ): Promise<void> {
437
- runtimeState.deferSleepCleanupUntilKeepAwakeIdle = false;
438
- const actorCtx = runtimeState.deferredSleepCleanupActorCtx;
439
- runtimeState.deferredSleepCleanupActorCtx = undefined;
440
- try {
441
- await cleanupNativeSleepRuntimeState(runtime, ctx);
442
- } finally {
443
- await actorCtx?.dispose();
444
- }
445
- }
446
-
447
415
  function closeNativeSqlDatabase(
448
416
  runtime: CoreRuntime,
449
417
  ctx: ActorContextHandle,
@@ -670,28 +638,7 @@ function isStructuredBridgeError(
670
638
  function encodeNativeCallbackError(error: unknown): Error {
671
639
  const structuredError = isStructuredBridgeError(error)
672
640
  ? error
673
- : deconstructError(error, logger(), {
674
- bridge: "native_callback",
675
- });
676
- let stack: string | undefined;
677
- if (error instanceof Error) {
678
- try {
679
- stack = error.stack;
680
- } catch {
681
- stack = undefined;
682
- }
683
- }
684
-
685
- logger().warn({
686
- msg: "native callback error encoded for bridge",
687
- group: structuredError.group,
688
- code: structuredError.code,
689
- message: structuredError.message,
690
- metadata: structuredError.metadata,
691
- originalError: stringifyError(error),
692
- stack,
693
- bridge: "native_callback",
694
- });
641
+ : deconstructError(error, true);
695
642
 
696
643
  const bridgeError = new Error(encodeBridgeRivetError(structuredError), {
697
644
  cause: error instanceof Error ? error : undefined,
@@ -2807,11 +2754,6 @@ export class ActorContextHandleAdapter {
2807
2754
  }
2808
2755
 
2809
2756
  keepAwake<T>(promise: Promise<T>): Promise<T> {
2810
- const runtimeState = getNativeRuntimeState(this.#runtime, this.#ctx);
2811
- // Increment before native registration so sleep cleanup observes JS work
2812
- // even if the promise settles immediately.
2813
- runtimeState.keepAwakeCount = (runtimeState.keepAwakeCount ?? 0) + 1;
2814
- let registered = false;
2815
2757
  const trackedPromise = Promise.resolve(promise)
2816
2758
  .catch((error) => {
2817
2759
  logger().warn({
@@ -2819,36 +2761,12 @@ export class ActorContextHandleAdapter {
2819
2761
  error: stringifyError(error),
2820
2762
  });
2821
2763
  })
2822
- .finally(async () => {
2823
- if (!registered) {
2824
- return;
2825
- }
2826
- runtimeState.keepAwakeCount = Math.max(
2827
- (runtimeState.keepAwakeCount ?? 1) - 1,
2828
- 0,
2829
- );
2830
- if (
2831
- runtimeState.keepAwakeCount === 0 &&
2832
- runtimeState.deferSleepCleanupUntilKeepAwakeIdle
2833
- ) {
2834
- await cleanupDeferredNativeSleepRuntimeState(
2835
- this.#runtime,
2836
- this.#ctx,
2837
- runtimeState,
2838
- );
2839
- }
2840
- })
2841
2764
  .then(() => null);
2842
2765
  try {
2843
2766
  callNativeSync(() =>
2844
2767
  this.#runtime.actorKeepAwake(this.#ctx, trackedPromise),
2845
2768
  );
2846
- registered = true;
2847
2769
  } catch (error) {
2848
- runtimeState.keepAwakeCount = Math.max(
2849
- (runtimeState.keepAwakeCount ?? 1) - 1,
2850
- 0,
2851
- );
2852
2770
  if (!isClosedTaskRegistrationError(error)) {
2853
2771
  throw error;
2854
2772
  }
@@ -3265,52 +3183,6 @@ function withConnContext(
3265
3183
  );
3266
3184
  }
3267
3185
 
3268
- function buildNativeRequestErrorResponse(
3269
- encoding: Encoding,
3270
- path: string,
3271
- error: unknown,
3272
- ): Response {
3273
- const { statusCode, group, code, message, metadata } = deconstructError(
3274
- error,
3275
- logger(),
3276
- {
3277
- path,
3278
- runtime: "native",
3279
- },
3280
- false,
3281
- );
3282
- const body = serializeWithEncoding<
3283
- protocol.HttpResponseError,
3284
- HttpResponseErrorJson,
3285
- { group: string; code: string; message: string; metadata?: unknown }
3286
- >(
3287
- encoding,
3288
- { group, code, message, metadata },
3289
- HTTP_RESPONSE_ERROR_VERSIONED,
3290
- CLIENT_PROTOCOL_CURRENT_VERSION,
3291
- HttpResponseErrorSchema,
3292
- (value) => value,
3293
- (value) => ({
3294
- group: value.group,
3295
- code: value.code,
3296
- message: value.message,
3297
- metadata:
3298
- value.metadata === undefined
3299
- ? null
3300
- : runtimeBytesToArrayBuffer(
3301
- encodeCborCompat(value.metadata),
3302
- ),
3303
- }),
3304
- );
3305
-
3306
- return new Response(body, {
3307
- status: statusCode,
3308
- headers: {
3309
- "Content-Type": contentTypeForEncoding(encoding),
3310
- },
3311
- });
3312
- }
3313
-
3314
3186
  function buildActorConfig(
3315
3187
  definition: AnyActorDefinition,
3316
3188
  registryConfig: RegistryConfig,
@@ -4013,12 +3885,9 @@ export function buildNativeFactory(
4013
3885
  }
4014
3886
  }
4015
3887
  } finally {
4016
- const runtimeState = getNativeRuntimeState(runtime, ctx);
4017
- if ((runtimeState.keepAwakeCount ?? 0) > 0) {
4018
- runtimeState.deferSleepCleanupUntilKeepAwakeIdle = true;
4019
- runtimeState.deferredSleepCleanupActorCtx = actorCtx;
4020
- } else {
3888
+ try {
4021
3889
  await cleanupNativeSleepRuntimeState(runtime, ctx);
3890
+ } finally {
4022
3891
  await actorCtx.dispose();
4023
3892
  }
4024
3893
  }
@@ -4366,22 +4235,6 @@ export function buildNativeFactory(
4366
4235
  );
4367
4236
  }
4368
4237
  return await toRuntimeHttpResponse(response);
4369
- } catch (error) {
4370
- const encodingHeader =
4371
- jsRequest.headers.get(HEADER_ENCODING);
4372
- const encoding: Encoding =
4373
- encodingHeader === "cbor" ||
4374
- encodingHeader === "bare"
4375
- ? encodingHeader
4376
- : "json";
4377
- const path = new URL(jsRequest.url).pathname;
4378
- return await toRuntimeHttpResponse(
4379
- buildNativeRequestErrorResponse(
4380
- encoding,
4381
- path,
4382
- error,
4383
- ),
4384
- );
4385
4238
  } finally {
4386
4239
  await requestCtx?.dispose();
4387
4240
  if (conn) {