rivetkit 2.3.5-rc.1 → 2.3.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.
- package/dist/browser/client.d.ts +19 -5
- package/dist/browser/client.js +32 -9
- package/dist/browser/client.js.map +1 -1
- package/dist/browser/inspector/client.js +12 -2
- package/dist/browser/inspector/client.js.map +1 -1
- package/dist/tsup/agent-os/index.cjs +115 -5
- package/dist/tsup/agent-os/index.cjs.map +1 -1
- package/dist/tsup/agent-os/index.d.cts +19 -5
- package/dist/tsup/agent-os/index.d.ts +19 -5
- package/dist/tsup/agent-os/index.js +115 -5
- package/dist/tsup/agent-os/index.js.map +1 -1
- package/dist/tsup/{chunk-6HQCLWAW.cjs → chunk-234EFKN2.cjs} +13 -3
- package/dist/tsup/chunk-234EFKN2.cjs.map +1 -0
- package/dist/tsup/{chunk-NIC3QLKL.js → chunk-3ZE5UDOA.js} +3 -3
- package/dist/tsup/{chunk-4XKED5P5.cjs → chunk-AXEGEUYB.cjs} +262 -252
- package/dist/tsup/chunk-AXEGEUYB.cjs.map +1 -0
- package/dist/tsup/{chunk-P4OGQ4JL.js → chunk-BX4G2O6M.js} +3 -3
- package/dist/tsup/{chunk-GAV4PKFT.js → chunk-F6ZW7JZQ.js} +130 -120
- package/dist/tsup/chunk-F6ZW7JZQ.js.map +1 -0
- package/dist/tsup/{chunk-TR5NLR5V.cjs → chunk-FNTTW25Q.cjs} +4 -4
- package/dist/tsup/{chunk-TR5NLR5V.cjs.map → chunk-FNTTW25Q.cjs.map} +1 -1
- package/dist/tsup/{chunk-G3CCRCTK.cjs → chunk-HNHLBM3Y.cjs} +108 -6
- package/dist/tsup/chunk-HNHLBM3Y.cjs.map +1 -0
- package/dist/tsup/{chunk-UJ2FQY63.cjs → chunk-HVOKIYUB.cjs} +7 -7
- package/dist/tsup/{chunk-UJ2FQY63.cjs.map → chunk-HVOKIYUB.cjs.map} +1 -1
- package/dist/tsup/{chunk-PAG6G46L.js → chunk-OH6CBWUD.js} +2 -2
- package/dist/tsup/{chunk-TNIWIUNV.cjs → chunk-PD7SZKHY.cjs} +9 -9
- package/dist/tsup/{chunk-TNIWIUNV.cjs.map → chunk-PD7SZKHY.cjs.map} +1 -1
- package/dist/tsup/{chunk-MGTSVSUC.js → chunk-PLLHHJWR.js} +2 -2
- package/dist/tsup/{chunk-SHBE77IT.js → chunk-QWLJCP3X.js} +106 -4
- package/dist/tsup/chunk-QWLJCP3X.js.map +1 -0
- package/dist/tsup/{chunk-B4IZU537.js → chunk-RDTFPGU2.js} +13 -3
- package/dist/tsup/chunk-RDTFPGU2.js.map +1 -0
- package/dist/tsup/{chunk-M3FCXL3Q.cjs → chunk-WS3PP253.cjs} +3 -3
- package/dist/tsup/{chunk-M3FCXL3Q.cjs.map → chunk-WS3PP253.cjs.map} +1 -1
- package/dist/tsup/client/mod.cjs +5 -5
- package/dist/tsup/client/mod.d.cts +2 -2
- package/dist/tsup/client/mod.d.ts +2 -2
- package/dist/tsup/client/mod.js +4 -4
- 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-BzdqbLAP.d.ts → config-BRpVbJCD.d.ts} +20 -6
- package/dist/tsup/{config-CPPEwn_e.d.cts → config-BobvQpex.d.cts} +20 -6
- package/dist/tsup/{context-wWv-K2K_.d.ts → context-Cj-DFyiy.d.ts} +1 -1
- package/dist/tsup/{context-DSA9gFXB.d.cts → context-YEBNJrws.d.cts} +1 -1
- package/dist/tsup/db/drizzle.cjs +2 -2
- package/dist/tsup/db/drizzle.js +1 -1
- package/dist/tsup/db/mod.cjs +2 -2
- package/dist/tsup/db/mod.js +1 -1
- package/dist/tsup/dynamic/mod.cjs +2 -2
- package/dist/tsup/dynamic/mod.d.cts +1 -1
- package/dist/tsup/dynamic/mod.d.ts +1 -1
- package/dist/tsup/dynamic/mod.js +1 -1
- package/dist/tsup/inspector/mod.cjs +4 -4
- package/dist/tsup/inspector/mod.js +3 -3
- package/dist/tsup/inspector-tab/mod.cjs +2 -2
- package/dist/tsup/inspector-tab/mod.d.cts +2 -2
- package/dist/tsup/inspector-tab/mod.d.ts +2 -2
- package/dist/tsup/inspector-tab/mod.js +1 -1
- package/dist/tsup/mod.cjs +175 -85
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +3 -3
- package/dist/tsup/mod.d.ts +3 -3
- package/dist/tsup/mod.js +107 -17
- package/dist/tsup/mod.js.map +1 -1
- package/dist/tsup/test/mod.cjs +16 -13
- package/dist/tsup/test/mod.cjs.map +1 -1
- package/dist/tsup/test/mod.d.cts +1 -1
- package/dist/tsup/test/mod.d.ts +1 -1
- package/dist/tsup/test/mod.js +15 -12
- package/dist/tsup/test/mod.js.map +1 -1
- package/dist/tsup/unstable/migrations.cjs +101 -0
- package/dist/tsup/unstable/migrations.cjs.map +1 -0
- package/dist/tsup/unstable/migrations.d.cts +29 -0
- package/dist/tsup/unstable/migrations.d.ts +29 -0
- package/dist/tsup/unstable/migrations.js +101 -0
- package/dist/tsup/unstable/migrations.js.map +1 -0
- package/dist/tsup/utils.cjs +2 -2
- package/dist/tsup/utils.js +1 -1
- package/dist/tsup/workflow/mod.cjs +9 -9
- package/dist/tsup/workflow/mod.d.cts +3 -3
- package/dist/tsup/workflow/mod.d.ts +3 -3
- package/dist/tsup/workflow/mod.js +4 -4
- package/package.json +19 -9
- package/src/actor/actions.ts +119 -0
- package/src/actor/config.ts +61 -18
- package/src/actor/definition.ts +3 -0
- package/src/client/actor-common.ts +3 -1
- package/src/client/client.ts +24 -11
- package/src/registry/config/index.ts +2 -4
- package/src/registry/index.ts +86 -5
- package/src/registry/napi-runtime.ts +4 -0
- package/src/registry/native.ts +17 -16
- package/src/registry/runtime.ts +1 -0
- package/src/registry/wasm-runtime.ts +6 -0
- package/src/test/mod.ts +1 -19
- package/src/test/ready.ts +22 -0
- package/src/unstable/migrations.test.ts +170 -0
- package/src/unstable/migrations.ts +141 -0
- package/dist/tsup/chunk-4XKED5P5.cjs.map +0 -1
- package/dist/tsup/chunk-6HQCLWAW.cjs.map +0 -1
- package/dist/tsup/chunk-B4IZU537.js.map +0 -1
- package/dist/tsup/chunk-G3CCRCTK.cjs.map +0 -1
- package/dist/tsup/chunk-GAV4PKFT.js.map +0 -1
- package/dist/tsup/chunk-SHBE77IT.js.map +0 -1
- /package/dist/tsup/{chunk-NIC3QLKL.js.map → chunk-3ZE5UDOA.js.map} +0 -0
- /package/dist/tsup/{chunk-P4OGQ4JL.js.map → chunk-BX4G2O6M.js.map} +0 -0
- /package/dist/tsup/{chunk-PAG6G46L.js.map → chunk-OH6CBWUD.js.map} +0 -0
- /package/dist/tsup/{chunk-MGTSVSUC.js.map → chunk-PLLHHJWR.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rivetkit",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.6",
|
|
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.
|
|
195
|
-
"@rivetkit/engine-envoy-protocol": "2.3.
|
|
204
|
+
"@rivetkit/engine-cli": "2.3.6",
|
|
205
|
+
"@rivetkit/engine-envoy-protocol": "2.3.6",
|
|
196
206
|
"@rivetkit/on-change": "6.0.1",
|
|
197
|
-
"@rivetkit/rivetkit-napi": "2.3.
|
|
198
|
-
"@rivetkit/rivetkit-wasm": "2.3.
|
|
199
|
-
"@rivetkit/traces": "2.3.
|
|
200
|
-
"@rivetkit/virtual-websocket": "2.3.
|
|
201
|
-
"@rivetkit/workflow-engine": "2.3.
|
|
207
|
+
"@rivetkit/rivetkit-napi": "2.3.6",
|
|
208
|
+
"@rivetkit/rivetkit-wasm": "2.3.6",
|
|
209
|
+
"@rivetkit/traces": "2.3.6",
|
|
210
|
+
"@rivetkit/virtual-websocket": "2.3.6",
|
|
211
|
+
"@rivetkit/workflow-engine": "2.3.6",
|
|
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
|
+
}
|
package/src/actor/config.ts
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
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
|
|
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?:
|
|
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
|
-
"
|
|
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
|
|
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()
|
package/src/actor/definition.ts
CHANGED
|
@@ -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
|
-
:
|
|
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> =
|
package/src/client/client.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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: (
|
|
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 &&
|
|
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;
|
package/src/registry/index.ts
CHANGED
|
@@ -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.#
|
|
494
|
-
|
|
497
|
+
this.#runtimeServeLifecyclePromise = configuredRegistryPromise.then(
|
|
498
|
+
async ({ runtime, registry, serveConfig }) => {
|
|
495
499
|
await runtime.serveRegistry(registry, serveConfig);
|
|
496
|
-
}
|
|
497
|
-
|
|
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
|
}
|
package/src/registry/native.ts
CHANGED
|
@@ -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
|
|
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:
|
|
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(
|
package/src/registry/runtime.ts
CHANGED
|
@@ -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,
|