rivetkit 2.3.5 → 2.3.7

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 (111) hide show
  1. package/dist/browser/client.d.ts +19 -5
  2. package/dist/browser/client.js +32 -9
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +12 -2
  5. package/dist/browser/inspector/client.js.map +1 -1
  6. package/dist/tsup/agent-os/index.cjs +115 -5
  7. package/dist/tsup/agent-os/index.cjs.map +1 -1
  8. package/dist/tsup/agent-os/index.d.cts +19 -5
  9. package/dist/tsup/agent-os/index.d.ts +19 -5
  10. package/dist/tsup/agent-os/index.js +115 -5
  11. package/dist/tsup/agent-os/index.js.map +1 -1
  12. package/dist/tsup/{chunk-ID7DMKXR.cjs → chunk-2SGPZEAJ.cjs} +13 -3
  13. package/dist/tsup/chunk-2SGPZEAJ.cjs.map +1 -0
  14. package/dist/tsup/{chunk-2GZPBFZV.cjs → chunk-46AE7JOI.cjs} +262 -252
  15. package/dist/tsup/chunk-46AE7JOI.cjs.map +1 -0
  16. package/dist/tsup/{chunk-FJQ36P7Q.js → chunk-6D2WRHJV.js} +2 -2
  17. package/dist/tsup/{chunk-WGRSAJSX.js → chunk-7YQJXWBW.js} +3 -3
  18. package/dist/tsup/{chunk-NYEGEML6.js → chunk-CH25U5NO.js} +3 -3
  19. package/dist/tsup/{chunk-7IIAJQTF.cjs → chunk-DOPH7DT2.cjs} +7 -7
  20. package/dist/tsup/{chunk-7IIAJQTF.cjs.map → chunk-DOPH7DT2.cjs.map} +1 -1
  21. package/dist/tsup/{chunk-G3CCRCTK.cjs → chunk-HNHLBM3Y.cjs} +108 -6
  22. package/dist/tsup/chunk-HNHLBM3Y.cjs.map +1 -0
  23. package/dist/tsup/{chunk-TKPQNJQ2.cjs → chunk-MNZFNIVI.cjs} +3 -3
  24. package/dist/tsup/{chunk-TKPQNJQ2.cjs.map → chunk-MNZFNIVI.cjs.map} +1 -1
  25. package/dist/tsup/{chunk-YOP6FRYG.js → chunk-OG4OR2CM.js} +13 -3
  26. package/dist/tsup/chunk-OG4OR2CM.js.map +1 -0
  27. package/dist/tsup/{chunk-SHBE77IT.js → chunk-QWLJCP3X.js} +106 -4
  28. package/dist/tsup/chunk-QWLJCP3X.js.map +1 -0
  29. package/dist/tsup/{chunk-PG5P2IDC.cjs → chunk-TQHMPWL6.cjs} +9 -9
  30. package/dist/tsup/{chunk-PG5P2IDC.cjs.map → chunk-TQHMPWL6.cjs.map} +1 -1
  31. package/dist/tsup/{chunk-7HDG7FY3.cjs → chunk-UIZZCJ5W.cjs} +4 -4
  32. package/dist/tsup/{chunk-7HDG7FY3.cjs.map → chunk-UIZZCJ5W.cjs.map} +1 -1
  33. package/dist/tsup/{chunk-ENPXVDGZ.js → chunk-YHPFCYL3.js} +2 -2
  34. package/dist/tsup/{chunk-PMIEL4PM.js → chunk-YU3C5OCU.js} +130 -120
  35. package/dist/tsup/chunk-YU3C5OCU.js.map +1 -0
  36. package/dist/tsup/client/mod.cjs +5 -5
  37. package/dist/tsup/client/mod.d.cts +2 -2
  38. package/dist/tsup/client/mod.d.ts +2 -2
  39. package/dist/tsup/client/mod.js +4 -4
  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-BzdqbLAP.d.ts → config-BRpVbJCD.d.ts} +20 -6
  45. package/dist/tsup/{config-CPPEwn_e.d.cts → config-BobvQpex.d.cts} +20 -6
  46. package/dist/tsup/{context-wWv-K2K_.d.ts → context-Cj-DFyiy.d.ts} +1 -1
  47. package/dist/tsup/{context-DSA9gFXB.d.cts → context-YEBNJrws.d.cts} +1 -1
  48. package/dist/tsup/db/drizzle.cjs +2 -2
  49. package/dist/tsup/db/drizzle.js +1 -1
  50. package/dist/tsup/db/mod.cjs +2 -2
  51. package/dist/tsup/db/mod.js +1 -1
  52. package/dist/tsup/dynamic/mod.cjs +2 -2
  53. package/dist/tsup/dynamic/mod.d.cts +1 -1
  54. package/dist/tsup/dynamic/mod.d.ts +1 -1
  55. package/dist/tsup/dynamic/mod.js +1 -1
  56. package/dist/tsup/inspector/mod.cjs +4 -4
  57. package/dist/tsup/inspector/mod.js +3 -3
  58. package/dist/tsup/inspector-tab/mod.cjs +2 -2
  59. package/dist/tsup/inspector-tab/mod.d.cts +2 -2
  60. package/dist/tsup/inspector-tab/mod.d.ts +2 -2
  61. package/dist/tsup/inspector-tab/mod.js +1 -1
  62. package/dist/tsup/mod.cjs +175 -85
  63. package/dist/tsup/mod.cjs.map +1 -1
  64. package/dist/tsup/mod.d.cts +3 -3
  65. package/dist/tsup/mod.d.ts +3 -3
  66. package/dist/tsup/mod.js +107 -17
  67. package/dist/tsup/mod.js.map +1 -1
  68. package/dist/tsup/test/mod.cjs +16 -13
  69. package/dist/tsup/test/mod.cjs.map +1 -1
  70. package/dist/tsup/test/mod.d.cts +1 -1
  71. package/dist/tsup/test/mod.d.ts +1 -1
  72. package/dist/tsup/test/mod.js +15 -12
  73. package/dist/tsup/test/mod.js.map +1 -1
  74. package/dist/tsup/unstable/migrations.cjs +101 -0
  75. package/dist/tsup/unstable/migrations.cjs.map +1 -0
  76. package/dist/tsup/unstable/migrations.d.cts +29 -0
  77. package/dist/tsup/unstable/migrations.d.ts +29 -0
  78. package/dist/tsup/unstable/migrations.js +101 -0
  79. package/dist/tsup/unstable/migrations.js.map +1 -0
  80. package/dist/tsup/utils.cjs +2 -2
  81. package/dist/tsup/utils.js +1 -1
  82. package/dist/tsup/workflow/mod.cjs +9 -9
  83. package/dist/tsup/workflow/mod.d.cts +3 -3
  84. package/dist/tsup/workflow/mod.d.ts +3 -3
  85. package/dist/tsup/workflow/mod.js +4 -4
  86. package/package.json +19 -9
  87. package/src/actor/actions.ts +119 -0
  88. package/src/actor/config.ts +61 -18
  89. package/src/actor/definition.ts +3 -0
  90. package/src/client/actor-common.ts +3 -1
  91. package/src/client/client.ts +24 -11
  92. package/src/registry/config/index.ts +2 -4
  93. package/src/registry/index.ts +86 -5
  94. package/src/registry/napi-runtime.ts +4 -0
  95. package/src/registry/native.ts +17 -16
  96. package/src/registry/runtime.ts +1 -0
  97. package/src/registry/wasm-runtime.ts +6 -0
  98. package/src/test/mod.ts +1 -19
  99. package/src/test/ready.ts +22 -0
  100. package/src/unstable/migrations.test.ts +170 -0
  101. package/src/unstable/migrations.ts +141 -0
  102. package/dist/tsup/chunk-2GZPBFZV.cjs.map +0 -1
  103. package/dist/tsup/chunk-G3CCRCTK.cjs.map +0 -1
  104. package/dist/tsup/chunk-ID7DMKXR.cjs.map +0 -1
  105. package/dist/tsup/chunk-PMIEL4PM.js.map +0 -1
  106. package/dist/tsup/chunk-SHBE77IT.js.map +0 -1
  107. package/dist/tsup/chunk-YOP6FRYG.js.map +0 -1
  108. /package/dist/tsup/{chunk-FJQ36P7Q.js.map → chunk-6D2WRHJV.js.map} +0 -0
  109. /package/dist/tsup/{chunk-WGRSAJSX.js.map → chunk-7YQJXWBW.js.map} +0 -0
  110. /package/dist/tsup/{chunk-NYEGEML6.js.map → chunk-CH25U5NO.js.map} +0 -0
  111. /package/dist/tsup/{chunk-ENPXVDGZ.js.map → chunk-YHPFCYL3.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rivetkit",
3
- "version": "2.3.5",
3
+ "version": "2.3.7",
4
4
  "description": "Lightweight libraries for building stateful actors on edge platforms",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -72,6 +72,16 @@
72
72
  "default": "./dist/tsup/db/drizzle.cjs"
73
73
  }
74
74
  },
75
+ "./unstable/migrations": {
76
+ "import": {
77
+ "types": "./dist/tsup/unstable/migrations.d.ts",
78
+ "default": "./dist/tsup/unstable/migrations.js"
79
+ },
80
+ "require": {
81
+ "types": "./dist/tsup/unstable/migrations.d.cts",
82
+ "default": "./dist/tsup/unstable/migrations.cjs"
83
+ }
84
+ },
75
85
  "./dynamic": {
76
86
  "import": {
77
87
  "types": "./dist/tsup/dynamic/mod.d.ts",
@@ -171,7 +181,7 @@
171
181
  "./dist/tsup/chunk-*.cjs"
172
182
  ],
173
183
  "scripts": {
174
- "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/common/websocket.ts src/actor/errors.ts src/utils.ts src/workflow/mod.ts src/test/mod.ts src/inspector/mod.ts src/inspector-tab/mod.ts src/db/mod.ts src/db/drizzle.ts src/dynamic/mod.ts && tsup src/agent-os/index.ts --no-clean --out-dir dist/tsup/agent-os",
184
+ "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/common/websocket.ts src/actor/errors.ts src/utils.ts src/workflow/mod.ts src/test/mod.ts src/inspector/mod.ts src/inspector-tab/mod.ts src/db/mod.ts src/db/drizzle.ts src/dynamic/mod.ts src/unstable/migrations.ts && tsup src/agent-os/index.ts --no-clean --out-dir dist/tsup/agent-os",
175
185
  "build:browser": "tsup --config tsup.browser.config.ts",
176
186
  "check-types": "tsc --noEmit",
177
187
  "lint": "biome check . && pnpm run check:test-skips && pnpm run check:wait-for-comments",
@@ -191,14 +201,14 @@
191
201
  "@hono/zod-openapi": "^1.1.5",
192
202
  "@rivet-dev/agent-os-core": "^0.1.1",
193
203
  "@rivetkit/bare-ts": "^0.6.2",
194
- "@rivetkit/engine-cli": "2.3.5",
195
- "@rivetkit/engine-envoy-protocol": "2.3.5",
204
+ "@rivetkit/engine-cli": "2.3.7",
205
+ "@rivetkit/engine-envoy-protocol": "2.3.7",
196
206
  "@rivetkit/on-change": "6.0.1",
197
- "@rivetkit/rivetkit-napi": "2.3.5",
198
- "@rivetkit/rivetkit-wasm": "2.3.5",
199
- "@rivetkit/traces": "2.3.5",
200
- "@rivetkit/virtual-websocket": "2.3.5",
201
- "@rivetkit/workflow-engine": "2.3.5",
207
+ "@rivetkit/rivetkit-napi": "2.3.7",
208
+ "@rivetkit/rivetkit-wasm": "2.3.7",
209
+ "@rivetkit/traces": "2.3.7",
210
+ "@rivetkit/virtual-websocket": "2.3.7",
211
+ "@rivetkit/workflow-engine": "2.3.7",
202
212
  "cbor-x": "^1.6.0",
203
213
  "drizzle-orm": "^0.44.2",
204
214
  "hono": "^4.7.0",
@@ -0,0 +1,119 @@
1
+ import type { PrimitiveSchema } from "./schema";
2
+
3
+ export type RuntimeActionHandler = (...args: any[]) => any;
4
+
5
+ export function flattenActionHandlers(
6
+ actions: unknown,
7
+ ): Record<string, RuntimeActionHandler> {
8
+ const flattened = Object.create(null) as Record<
9
+ string,
10
+ RuntimeActionHandler
11
+ >;
12
+ for (const { name, handler } of collectActionEntries(actions)) {
13
+ flattened[name] = handler;
14
+ }
15
+ return flattened;
16
+ }
17
+
18
+ export function flattenActionInputSchemas(
19
+ actions: unknown,
20
+ schemas: unknown,
21
+ ): Record<string, PrimitiveSchema> | undefined {
22
+ if (schemas === undefined) return undefined;
23
+ if (!isRecord(schemas)) {
24
+ throw new TypeError("actionInputSchemas must be an object");
25
+ }
26
+
27
+ const flattened = Object.create(null) as Record<string, PrimitiveSchema>;
28
+ for (const { name, path } of collectActionEntries(actions)) {
29
+ const nestedSchema = lookupNestedSchema(schemas, path);
30
+ const flatSchema = schemas[name];
31
+ if (
32
+ nestedSchema !== undefined &&
33
+ flatSchema !== undefined &&
34
+ nestedSchema !== flatSchema
35
+ ) {
36
+ throw new TypeError(
37
+ `Action input schema \`${name}\` is defined by both a nested path and a dotted key`,
38
+ );
39
+ }
40
+
41
+ const schema = nestedSchema ?? flatSchema;
42
+ if (schema !== undefined) {
43
+ flattened[name] = schema as PrimitiveSchema;
44
+ }
45
+ }
46
+ return flattened;
47
+ }
48
+
49
+ interface ActionEntry {
50
+ name: string;
51
+ path: string[];
52
+ handler: RuntimeActionHandler;
53
+ }
54
+
55
+ function collectActionEntries(actions: unknown): ActionEntry[] {
56
+ const entries: ActionEntry[] = [];
57
+ const names = new Set<string>();
58
+ visitActionGroup(actions ?? {}, [], entries, names);
59
+ return entries;
60
+ }
61
+
62
+ function visitActionGroup(
63
+ value: unknown,
64
+ path: string[],
65
+ entries: ActionEntry[],
66
+ names: Set<string>,
67
+ ) {
68
+ if (!isRecord(value)) {
69
+ throw new TypeError(
70
+ `${formatActionPath(path)} must be an action handler or group`,
71
+ );
72
+ }
73
+
74
+ for (const [segment, child] of Object.entries(value)) {
75
+ const childPath = [...path, segment];
76
+ if (typeof child === "function") {
77
+ const name = childPath.join(".");
78
+ if (names.has(name)) {
79
+ throw new TypeError(
80
+ `Multiple action definitions flatten to \`${name}\``,
81
+ );
82
+ }
83
+ names.add(name);
84
+ entries.push({
85
+ name,
86
+ path: childPath,
87
+ handler: child as RuntimeActionHandler,
88
+ });
89
+ } else {
90
+ visitActionGroup(child, childPath, entries, names);
91
+ }
92
+ }
93
+ }
94
+
95
+ function lookupNestedSchema(
96
+ schemas: Record<string, unknown>,
97
+ path: string[],
98
+ ): unknown {
99
+ let value: unknown = schemas;
100
+ for (const segment of path) {
101
+ if (!isRecord(value) || !Object.hasOwn(value, segment)) {
102
+ return undefined;
103
+ }
104
+ value = value[segment];
105
+ }
106
+ return value;
107
+ }
108
+
109
+ function isRecord(value: unknown): value is Record<string, unknown> {
110
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
111
+ return false;
112
+ }
113
+ const prototype = Object.getPrototypeOf(value);
114
+ return prototype === Object.prototype || prototype === null;
115
+ }
116
+
117
+ function formatActionPath(path: string[]): string {
118
+ return path.length === 0 ? "actions" : `Action \`${path.join(".")}\``;
119
+ }
@@ -6,6 +6,7 @@ import type {
6
6
  } from "@/common/database/config";
7
7
  import type { UniversalWebSocket } from "@/common/websocket-interface";
8
8
  import type { Registry } from "@/registry";
9
+ import { flattenActionHandlers } from "./actions";
9
10
  import type { BaseActorDefinition } from "./definition";
10
11
  import type {
11
12
  EventSchemaConfig,
@@ -792,6 +793,32 @@ const zFunction = <
792
793
  T extends (...args: any[]) => any = (...args: unknown[]) => unknown,
793
794
  >() => z.custom<T>((val) => typeof val === "function");
794
795
 
796
+ const zActionTree = z
797
+ .custom<Record<string, unknown>>((value) => {
798
+ if (
799
+ typeof value !== "object" ||
800
+ value === null ||
801
+ Array.isArray(value)
802
+ ) {
803
+ return false;
804
+ }
805
+ const prototype = Object.getPrototypeOf(value);
806
+ return prototype === Object.prototype || prototype === null;
807
+ })
808
+ .superRefine((actions, ctx) => {
809
+ try {
810
+ flattenActionHandlers(actions);
811
+ } catch (error) {
812
+ ctx.addIssue({
813
+ code: "custom",
814
+ message:
815
+ error instanceof Error
816
+ ? error.message
817
+ : "Invalid action definition",
818
+ });
819
+ }
820
+ });
821
+
795
822
  export type InspectorUnsubscribe = () => void;
796
823
 
797
824
  export interface WorkflowInspectorConfig<THistory = unknown> {
@@ -1128,7 +1155,7 @@ export const ActorConfigSchema = z
1128
1155
  onBeforeActionResponse: zFunction().optional(),
1129
1156
  onRequest: zFunction().optional(),
1130
1157
  onWebSocket: zFunction().optional(),
1131
- actions: z.record(z.string(), zFunction()).default(() => ({})),
1158
+ actions: zActionTree.default(() => ({})),
1132
1159
  actionInputSchemas: z.record(z.string(), z.any()).optional(),
1133
1160
  connParamsSchema: z.any().optional(),
1134
1161
  events: z.record(z.string(), z.any()).optional(),
@@ -1275,19 +1302,34 @@ export interface Actions<
1275
1302
  TEvents extends EventSchemaConfig = Record<never, never>,
1276
1303
  TQueues extends QueueSchemaConfig = Record<never, never>,
1277
1304
  > {
1278
- [Action: string]: (
1279
- c: ActionContext<
1280
- TState,
1281
- TConnParams,
1282
- TConnState,
1283
- TVars,
1284
- TInput,
1285
- TDatabase,
1286
- TEvents,
1287
- TQueues
1288
- >,
1289
- ...args: any[]
1290
- ) => any;
1305
+ [Action: string]:
1306
+ | ((
1307
+ c: ActionContext<
1308
+ TState,
1309
+ TConnParams,
1310
+ TConnState,
1311
+ TVars,
1312
+ TInput,
1313
+ TDatabase,
1314
+ TEvents,
1315
+ TQueues
1316
+ >,
1317
+ ...args: any[]
1318
+ ) => any)
1319
+ | Actions<
1320
+ TState,
1321
+ TConnParams,
1322
+ TConnState,
1323
+ TVars,
1324
+ TInput,
1325
+ TDatabase,
1326
+ TEvents,
1327
+ TQueues
1328
+ >;
1329
+ }
1330
+
1331
+ export interface ActionInputSchemas {
1332
+ [Action: string]: PrimitiveSchema | ActionInputSchemas;
1291
1333
  }
1292
1334
 
1293
1335
  //export type ActorConfig<TState, TConnParams, TConnState, TVars, TInput, TAuthData> = BaseActorConfig<TState, TConnParams, TConnState, TVars, TInput, TAuthData> &
@@ -1655,9 +1697,10 @@ interface BaseActorConfig<
1655
1697
  actions?: TActions;
1656
1698
 
1657
1699
  /**
1658
- * Optional schema map for validating action argument tuples in native runtimes.
1700
+ * Optional schemas for validating action argument tuples in native runtimes.
1701
+ * May mirror nested action groups or use dot-separated low-level action names.
1659
1702
  */
1660
- actionInputSchemas?: Record<string, PrimitiveSchema>;
1703
+ actionInputSchemas?: ActionInputSchemas;
1661
1704
 
1662
1705
  /**
1663
1706
  * Optional schema for validating connection params in native runtimes.
@@ -2184,13 +2227,13 @@ export const DocActorConfigSchema = z
2184
2227
  .record(z.string(), z.unknown())
2185
2228
  .optional()
2186
2229
  .describe(
2187
- "Map of action name to handler function. Defaults to an empty object.",
2230
+ "Tree of action names or nested groups to handler functions. Nested paths use dot-separated low-level action names. Defaults to an empty object.",
2188
2231
  ),
2189
2232
  actionInputSchemas: z
2190
2233
  .record(z.string(), z.unknown())
2191
2234
  .optional()
2192
2235
  .describe(
2193
- "Optional schema map for validating action argument tuples in native runtimes.",
2236
+ "Optional schemas for validating action argument tuples in native runtimes. May mirror nested action groups or use dot-separated low-level action names.",
2194
2237
  ),
2195
2238
  connParamsSchema: z
2196
2239
  .unknown()
@@ -1,5 +1,6 @@
1
1
  import type { AnyDatabaseProvider } from "@/common/database/config";
2
2
  import type { RegistryConfig } from "@/registry/config";
3
+ import { flattenActionHandlers, flattenActionInputSchemas } from "./actions";
3
4
  import {
4
5
  type Actions,
5
6
  type ActorConfig,
@@ -206,6 +207,8 @@ export function actor<
206
207
  TQueues,
207
208
  TActions
208
209
  >;
210
+ flattenActionHandlers(config.actions);
211
+ flattenActionInputSchemas(config.actions, config.actionInputSchemas);
209
212
  return new ActorDefinition(config);
210
213
  }
211
214
 
@@ -43,7 +43,9 @@ type ActorActionMap<R> = {
43
43
  ...args: infer Args
44
44
  ) => infer Return
45
45
  ? ActorActionFunction<Args, Awaited<Return>>
46
- : never;
46
+ : NonNullable<R>[K] extends Record<string, unknown>
47
+ ? ActorActionMap<NonNullable<R>[K]>
48
+ : never;
47
49
  };
48
50
 
49
51
  type ActionsOf<AD extends AnyActorDefinition> =
@@ -515,11 +515,30 @@ export function createClientWithDriver<A extends Registry<any>>(
515
515
  /**
516
516
  * Creates a proxy for a actor that enables calling actions without explicitly using `.action`.
517
517
  **/
518
- function createActorProxy<AD extends AnyActorDefinition>(
518
+ export function createActorProxy<AD extends AnyActorDefinition>(
519
519
  handle: ActorHandleRaw | ActorConnRaw,
520
520
  ): ActorHandle<AD> | ActorConn<AD> {
521
- // Stores returned action functions for faster calls
521
+ // Stores returned action path proxies for faster calls.
522
522
  const methodCache = new Map<string, ActorActionFunction>();
523
+ const actionPath = (name: string): ActorActionFunction => {
524
+ let method = methodCache.get(name);
525
+ if (method) return method;
526
+
527
+ method = new Proxy(
528
+ (...args: unknown[]) => handle.action({ name, args }),
529
+ {
530
+ get(target, prop: string | symbol) {
531
+ if (typeof prop === "symbol")
532
+ return Reflect.get(target, prop);
533
+ if (prop === "then") return undefined;
534
+ return actionPath(`${name}.${prop}`);
535
+ },
536
+ },
537
+ ) as ActorActionFunction;
538
+ methodCache.set(name, method);
539
+ return method;
540
+ };
541
+
523
542
  return new Proxy(handle, {
524
543
  get(target: ActorHandleRaw, prop: string | symbol, receiver: unknown) {
525
544
  // Handle built-in Symbol properties
@@ -542,13 +561,7 @@ function createActorProxy<AD extends AnyActorDefinition>(
542
561
  // If JS is attempting to calling this as a promise, ignore it
543
562
  if (prop === "then") return undefined;
544
563
 
545
- let method = methodCache.get(prop);
546
- if (!method) {
547
- method = (...args: unknown[]) =>
548
- target.action({ name: prop, args });
549
- methodCache.set(prop, method);
550
- }
551
- return method;
564
+ return actionPath(prop);
552
565
  }
553
566
  },
554
567
 
@@ -575,6 +588,7 @@ function createActorProxy<AD extends AnyActorDefinition>(
575
588
  target: ActorHandleRaw,
576
589
  prop: string | symbol,
577
590
  ) {
591
+ if (prop === "then") return undefined;
578
592
  const targetDescriptor = Reflect.getOwnPropertyDescriptor(
579
593
  target,
580
594
  prop,
@@ -588,8 +602,7 @@ function createActorProxy<AD extends AnyActorDefinition>(
588
602
  configurable: true,
589
603
  enumerable: false,
590
604
  writable: false,
591
- value: (...args: unknown[]) =>
592
- target.action({ name: prop, args }),
605
+ value: actionPath(prop),
593
606
  };
594
607
  }
595
608
  return undefined;
@@ -353,9 +353,7 @@ export const RegistryConfigSchema = z
353
353
  const endpoint = config.startEngine
354
354
  ? localEngineEndpoint
355
355
  : (parsedEndpoint?.endpoint ??
356
- (isDevEnv
357
- ? buildEngineEndpoint(ENGINE_HOST, ENGINE_PORT)
358
- : undefined));
356
+ (isDevEnv ? localEngineEndpoint : undefined));
359
357
  const validateServerlessEndpoint = Boolean(
360
358
  config.startEngine || parsedEndpoint,
361
359
  );
@@ -388,7 +386,7 @@ export const RegistryConfigSchema = z
388
386
  // In dev mode, clients connect directly to the local Rivet Engine.
389
387
  const publicEndpoint =
390
388
  parsedPublicEndpoint?.endpoint ??
391
- (isDevEnv && config.startEngine ? endpoint : undefined);
389
+ (isDevEnv && !parsedEndpoint ? endpoint : undefined);
392
390
  // We extract publicNamespace to validate that it matches the backend
393
391
  // namespace (see validation above), not for functional use.
394
392
  const publicNamespace = parsedPublicEndpoint?.namespace;
@@ -19,6 +19,8 @@ import type { RuntimeServerlessResponseHead } from "./runtime";
19
19
 
20
20
  type ShutdownSignal = "SIGINT" | "SIGTERM";
21
21
 
22
+ const REGISTRY_READY_TIMEOUT_MS = 30_000;
23
+
22
24
  function signalExitCode(signal: ShutdownSignal): number {
23
25
  switch (signal) {
24
26
  case "SIGINT":
@@ -73,6 +75,8 @@ export class Registry<A extends RegistryActors> {
73
75
  }
74
76
 
75
77
  #runtimeServePromise?: Promise<void>;
78
+ #runtimeServeLifecyclePromise?: Promise<void>;
79
+ #runtimeReadyPromise?: Promise<void>;
76
80
  #runtimeServeConfiguredPromise?: ReturnType<typeof buildConfiguredRegistry>;
77
81
  #runtimeServerlessPromise?: ReturnType<typeof buildConfiguredRegistry>;
78
82
  #configureServerlessPoolPromise?: Promise<void>;
@@ -490,17 +494,20 @@ export class Registry<A extends RegistryActors> {
490
494
  const configuredRegistryPromise =
491
495
  this.#buildConfiguredRegistry(config);
492
496
  this.#runtimeServeConfiguredPromise = configuredRegistryPromise;
493
- this.#runtimeServePromise = configuredRegistryPromise
494
- .then(async ({ runtime, registry, serveConfig }) => {
497
+ this.#runtimeServeLifecyclePromise = configuredRegistryPromise.then(
498
+ async ({ runtime, registry, serveConfig }) => {
495
499
  await runtime.serveRegistry(registry, serveConfig);
496
- })
497
- .catch((error) => {
500
+ },
501
+ );
502
+ this.#runtimeServePromise = this.#runtimeServeLifecyclePromise.catch(
503
+ (error) => {
498
504
  // Always-attached catch so the stored promise never leaves a
499
505
  // rejection unhandled. Downstream awaits (e.g. #runShutdown's
500
506
  // Promise.race) attach their own catches and still observe
501
507
  // resolution via the race.
502
508
  logger().warn({ error }, "runtime registry serve errored");
503
- });
509
+ },
510
+ );
504
511
  // Install signal handlers once an envoy lifecycle has begun. Only
505
512
  // Mode A ever reaches here. Mode B (handler(request)) intentionally
506
513
  // does not install handlers because it runs on Workers/Vercel/Deno
@@ -694,6 +701,80 @@ export class Registry<A extends RegistryActors> {
694
701
  this.#startEnvoy(this.parseConfig(), true);
695
702
  }
696
703
 
704
+ /**
705
+ * Starts the serverful registry if needed and waits until its envoy has
706
+ * registered with the Engine. Repeated and concurrent calls share one
707
+ * startup lifecycle and readiness promise.
708
+ *
709
+ * Unlike {@link start}, this reports startup failures and does not resolve
710
+ * merely because an HTTP health endpoint is listening.
711
+ */
712
+ public startAndWait(): Promise<void> {
713
+ if (this.#shutdownInFlight !== null) {
714
+ return Promise.reject(
715
+ new Error("registry.startAndWait() cannot run after shutdown has begun"),
716
+ );
717
+ }
718
+ if (this.#runtimeReadyPromise) return this.#runtimeReadyPromise;
719
+ if (getRivetkitRuntimeMode() === "serverless") {
720
+ return Promise.reject(
721
+ new Error(
722
+ "registry.startAndWait() requires envoy runtime mode; serverless registries become ready per request",
723
+ ),
724
+ );
725
+ }
726
+
727
+ const config = this.parseConfig();
728
+ if (config.runtime === "wasm") {
729
+ return Promise.reject(
730
+ new Error(
731
+ "registry.startAndWait() requires the native runtime; WebAssembly registries do not host an envoy",
732
+ ),
733
+ );
734
+ }
735
+ this.#startEnvoy(config, true);
736
+ const configuredRegistryPromise = this.#runtimeServeConfiguredPromise;
737
+ const serveLifecyclePromise = this.#runtimeServeLifecyclePromise;
738
+ if (!configuredRegistryPromise || !serveLifecyclePromise) {
739
+ throw new Error("registry envoy startup did not initialize");
740
+ }
741
+
742
+ let timeout: ReturnType<typeof setTimeout> | undefined;
743
+ const timeoutPromise = new Promise<never>((_resolve, reject) => {
744
+ timeout = setTimeout(
745
+ () =>
746
+ reject(
747
+ new Error(
748
+ `RivetKit registry did not register with the Engine within ${REGISTRY_READY_TIMEOUT_MS}ms`,
749
+ ),
750
+ ),
751
+ REGISTRY_READY_TIMEOUT_MS,
752
+ );
753
+ timeout.unref?.();
754
+ });
755
+ const readinessPromise = (async () => {
756
+ const { runtime, registry } = await configuredRegistryPromise;
757
+ const stoppedBeforeReady = serveLifecyclePromise.then(() => {
758
+ throw new Error("RivetKit registry stopped before becoming ready");
759
+ });
760
+ await Promise.race([
761
+ runtime.waitRegistryReady(registry),
762
+ stoppedBeforeReady,
763
+ ]);
764
+ })();
765
+ this.#runtimeReadyPromise = Promise.race([
766
+ readinessPromise,
767
+ timeoutPromise,
768
+ ]).finally(() => {
769
+ if (timeout !== undefined) clearTimeout(timeout);
770
+ });
771
+ // The native registry is consumed once serve begins. Retrying the same
772
+ // instance after failure could duplicate partially-created resources, so
773
+ // retain the rejected promise and return the same failure to later calls.
774
+ this.#runtimeReadyPromise.catch(() => {});
775
+ return this.#runtimeReadyPromise;
776
+ }
777
+
697
778
  /**
698
779
  * Starts the actor envoy for standalone server deployments.
699
780
  *
@@ -226,6 +226,10 @@ export class NapiCoreRuntime implements CoreRuntime {
226
226
  await asNativeRegistry(registry).serve(config);
227
227
  }
228
228
 
229
+ async waitRegistryReady(registry: RegistryHandle): Promise<void> {
230
+ await asNativeRegistry(registry).waitReady();
231
+ }
232
+
229
233
  async shutdownRegistry(registry: RegistryHandle): Promise<void> {
230
234
  await asNativeRegistry(registry).shutdown();
231
235
  }
@@ -1,17 +1,21 @@
1
1
  import { VirtualWebSocket } from "@rivetkit/virtual-websocket";
2
+ import {
3
+ flattenActionHandlers,
4
+ flattenActionInputSchemas,
5
+ } from "@/actor/actions";
2
6
  import {
3
7
  ACTOR_CONTEXT_INTERNAL_SYMBOL,
4
- CONN_STATE_MANAGER_SYMBOL,
5
- disposeRunInspector,
6
- getRunFunction,
7
- getRunInspectorConfig,
8
- RAW_STATE_SYMBOL,
9
8
  type ActorCron,
10
9
  type ActorCronEveryOptions,
11
10
  type ActorCronSetOptions,
12
11
  type ActorSchedule,
12
+ CONN_STATE_MANAGER_SYMBOL,
13
13
  type CronFire,
14
14
  type CronJobInfo,
15
+ disposeRunInspector,
16
+ getRunFunction,
17
+ getRunInspectorConfig,
18
+ RAW_STATE_SYMBOL,
15
19
  type ScheduledEventInfo,
16
20
  type WorkflowInspectorConfig,
17
21
  } from "@/actor/config";
@@ -84,10 +88,10 @@ import type {
84
88
  RegistryHandle,
85
89
  RuntimeActorConfig,
86
90
  RuntimeBytes,
87
- RuntimeHttpResponse,
88
- RuntimeInspectorTabEntry,
89
91
  RuntimeCronFire,
90
92
  RuntimeCronJobInfo,
93
+ RuntimeHttpResponse,
94
+ RuntimeInspectorTabEntry,
91
95
  RuntimeQueueMessage,
92
96
  RuntimeScheduledEventInfo,
93
97
  RuntimeScheduledFireInfo,
@@ -3524,7 +3528,7 @@ function buildActorConfig(
3524
3528
  preloadMaxConnectionsBytes: options.preloadMaxConnectionsBytes as
3525
3529
  | number
3526
3530
  | undefined,
3527
- actions: Object.keys((config.actions ?? {}) as Record<string, unknown>)
3531
+ actions: Object.keys(flattenActionHandlers(config.actions))
3528
3532
  .sort()
3529
3533
  .map((name) => ({ name })),
3530
3534
  inspectorTabs: buildInspectorTabs(config.inspector, runtimeKind),
@@ -3628,19 +3632,16 @@ export function buildNativeFactory(
3628
3632
  ): ActorFactoryHandle {
3629
3633
  const config = definition.config as Record<string, any>;
3630
3634
  const databaseProvider = config.db as AnyDatabaseProvider;
3635
+ const actionHandlers = flattenActionHandlers(config.actions);
3631
3636
  const schemaConfig: NativeValidationConfig = {
3632
- actionInputSchemas: config.actionInputSchemas,
3637
+ actionInputSchemas: flattenActionInputSchemas(
3638
+ config.actions,
3639
+ config.actionInputSchemas,
3640
+ ),
3633
3641
  connParamsSchema: config.connParamsSchema,
3634
3642
  events: config.events,
3635
3643
  queues: config.queues,
3636
3644
  };
3637
- const actionHandlers = Object.fromEntries(
3638
- (
3639
- Object.entries(config.actions ?? {}) as Array<
3640
- [string, (...args: Array<any>) => any]
3641
- >
3642
- ).map(([name, handler]) => [name, handler]),
3643
- );
3644
3645
  const createClient = () =>
3645
3646
  createClientWithDriver(
3646
3647
  new RemoteEngineControlClient(
@@ -412,6 +412,7 @@ export interface CoreRuntime {
412
412
  registry: RegistryHandle,
413
413
  config: RuntimeServeConfig,
414
414
  ): Promise<void>;
415
+ waitRegistryReady(registry: RegistryHandle): Promise<void>;
415
416
  shutdownRegistry(registry: RegistryHandle): Promise<void>;
416
417
  registryActorStopThresholdMs?(
417
418
  registry: RegistryHandle,
@@ -274,6 +274,12 @@ export class WasmCoreRuntime implements CoreRuntime {
274
274
  await callWasm(() => asWasmRegistry(registry).serve(config));
275
275
  }
276
276
 
277
+ async waitRegistryReady(): Promise<void> {
278
+ throw new Error(
279
+ "registry.startAndWait() is not supported by the WASM runtime",
280
+ );
281
+ }
282
+
277
283
  async shutdownRegistry(registry: RegistryHandle): Promise<void> {
278
284
  await callWasm(() => asWasmRegistry(registry).shutdown());
279
285
  }
package/src/test/mod.ts CHANGED
@@ -1,31 +1,13 @@
1
- import pRetry from "p-retry";
2
1
  import type { TestContext } from "vitest";
3
2
  import { convertRegistryConfigToClientConfig } from "@/client/config";
4
3
  import { type Client, createClient } from "@/client/mod";
5
- import { getMetadata } from "@/engine-client/api-endpoints";
6
4
  import type { Registry } from "@/registry";
5
+ import { waitForRegistryReady } from "./ready";
7
6
 
8
7
  export interface SetupTestResult<A extends Registry<any>> {
9
8
  client: Client<A>;
10
9
  }
11
10
 
12
- async function waitForRegistryReady(registry: Registry<any>): Promise<void> {
13
- const clientConfig = convertRegistryConfigToClientConfig(
14
- registry.parseConfig(),
15
- );
16
-
17
- await pRetry(
18
- async () => {
19
- await getMetadata(clientConfig);
20
- },
21
- {
22
- retries: 20,
23
- minTimeout: 50,
24
- maxTimeout: 250,
25
- },
26
- );
27
- }
28
-
29
11
  export async function setupTest<A extends Registry<any>>(
30
12
  c: TestContext,
31
13
  registry: A,