@rivet-dev/vercel-world 0.0.0-http-body-streaming.064e07d

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 (81) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/README.md +31 -0
  3. package/dist/actors/coordinator.d.ts +114 -0
  4. package/dist/actors/coordinator.d.ts.map +1 -0
  5. package/dist/actors/coordinator.js +232 -0
  6. package/dist/actors/coordinator.js.map +1 -0
  7. package/dist/actors/db.d.ts +8 -0
  8. package/dist/actors/db.d.ts.map +1 -0
  9. package/dist/actors/db.js +282 -0
  10. package/dist/actors/db.js.map +1 -0
  11. package/dist/actors/dispatcher.d.ts +84 -0
  12. package/dist/actors/dispatcher.d.ts.map +1 -0
  13. package/dist/actors/dispatcher.js +498 -0
  14. package/dist/actors/dispatcher.js.map +1 -0
  15. package/dist/actors/hook-token.d.ts +26 -0
  16. package/dist/actors/hook-token.d.ts.map +1 -0
  17. package/dist/actors/hook-token.js +151 -0
  18. package/dist/actors/hook-token.js.map +1 -0
  19. package/dist/actors/shared.d.ts +366 -0
  20. package/dist/actors/shared.d.ts.map +1 -0
  21. package/dist/actors/shared.js +112 -0
  22. package/dist/actors/shared.js.map +1 -0
  23. package/dist/actors/streams.d.ts +26 -0
  24. package/dist/actors/streams.d.ts.map +1 -0
  25. package/dist/actors/streams.js +127 -0
  26. package/dist/actors/streams.js.map +1 -0
  27. package/dist/actors/workflow-run.d.ts +891 -0
  28. package/dist/actors/workflow-run.d.ts.map +1 -0
  29. package/dist/actors/workflow-run.js +872 -0
  30. package/dist/actors/workflow-run.js.map +1 -0
  31. package/dist/actors.d.ts +2244 -0
  32. package/dist/actors.d.ts.map +1 -0
  33. package/dist/actors.js +16 -0
  34. package/dist/actors.js.map +1 -0
  35. package/dist/codec.d.ts +4 -0
  36. package/dist/codec.d.ts.map +1 -0
  37. package/dist/codec.js +27 -0
  38. package/dist/codec.js.map +1 -0
  39. package/dist/index.d.ts +843 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +567 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/runtime.d.ts +2 -0
  44. package/dist/runtime.d.ts.map +1 -0
  45. package/dist/runtime.js +24 -0
  46. package/dist/runtime.js.map +1 -0
  47. package/package.json +51 -0
  48. package/scripts/conformance/run.ts +278 -0
  49. package/scripts/integration/run.ts +935 -0
  50. package/src/actors/coordinator.ts +360 -0
  51. package/src/actors/db.ts +291 -0
  52. package/src/actors/dispatcher.ts +787 -0
  53. package/src/actors/hook-token.ts +239 -0
  54. package/src/actors/shared.ts +153 -0
  55. package/src/actors/streams.ts +215 -0
  56. package/src/actors/workflow-run.ts +1477 -0
  57. package/src/actors.ts +18 -0
  58. package/src/codec.ts +28 -0
  59. package/src/index.ts +788 -0
  60. package/src/runtime.ts +29 -0
  61. package/tests/conformance.test.ts +8 -0
  62. package/tests/helpers/db.ts +62 -0
  63. package/tests/helpers/dispatcher-driver.ts +71 -0
  64. package/tests/helpers/harness.ts +161 -0
  65. package/tests/integration/crash-restart.test.ts +145 -0
  66. package/tests/integration/dispatcher-loop.test.ts +144 -0
  67. package/tests/integration/hook-token.test.ts +160 -0
  68. package/tests/integration/hooks.test.ts +123 -0
  69. package/tests/integration/streams.test.ts +178 -0
  70. package/tests/integration/workflow-events.test.ts +326 -0
  71. package/tests/setup.ts +10 -0
  72. package/tests/unit/codec.test.ts +73 -0
  73. package/tests/unit/coordinator-record.test.ts +177 -0
  74. package/tests/unit/db-migrations.test.ts +65 -0
  75. package/tests/unit/dispatcher-queue.test.ts +274 -0
  76. package/tests/unit/logging.test.ts +49 -0
  77. package/tests/unit/readiness.test.ts +102 -0
  78. package/tests/unit/transaction.test.ts +76 -0
  79. package/tsconfig.build.json +13 -0
  80. package/tsconfig.json +12 -0
  81. package/vitest.config.ts +32 -0
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@rivet-dev/vercel-world",
3
+ "version": "0.0.0-http-body-streaming.064e07d",
4
+ "description": "Vercel Workflows backed by Rivet Actors",
5
+ "license": "Apache-2.0",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./registry": {
16
+ "types": "./dist/actors.d.ts",
17
+ "default": "./dist/actors.js"
18
+ }
19
+ },
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.build.json",
24
+ "check-types": "tsc --noEmit",
25
+ "test": "pnpm run test:unit && pnpm run test:integration && pnpm run test:conformance",
26
+ "test:unit": "vitest run tests/unit",
27
+ "test:integration": "tsx scripts/integration/run.ts",
28
+ "test:conformance": "pnpm run build && tsx scripts/conformance/run.ts"
29
+ },
30
+ "dependencies": {
31
+ "@workflow/errors": "5.0.0-beta.11",
32
+ "@workflow/world": "5.0.0-beta.21",
33
+ "cbor-x": "^1.6.0",
34
+ "rivetkit": "0.0.0-http-body-streaming.064e07d",
35
+ "ulid": "~3.0.1"
36
+ },
37
+ "devDependencies": {
38
+ "@rivetkit/engine-cli": "0.0.0-http-body-streaming.064e07d",
39
+ "@rivetkit/engine-cli-linux-x64-musl": "2.3.5",
40
+ "@rivetkit/rivetkit-napi-linux-x64-gnu": "2.3.5",
41
+ "@types/node": "^24.0.0",
42
+ "@workflow/world-testing": "5.0.0-beta.35",
43
+ "tsx": "^4.20.0",
44
+ "typescript": "^5.5.2",
45
+ "vite": "^7.0.0",
46
+ "vitest": "^4.1.9"
47
+ },
48
+ "engines": {
49
+ "node": ">=22.0.0"
50
+ }
51
+ }
@@ -0,0 +1,278 @@
1
+ import { spawn, type ChildProcess } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { createServer } from "node:http";
5
+ import { tmpdir } from "node:os";
6
+ import { dirname, join, resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { getEnginePath } from "@rivetkit/engine-cli";
9
+
10
+ const TOKEN = "dev";
11
+ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
12
+ const PNPM_BIN = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
13
+
14
+ type ManagedProcess = {
15
+ child: ChildProcess;
16
+ output: () => string;
17
+ };
18
+
19
+ type EngineProcess = ManagedProcess & {
20
+ endpoint: string;
21
+ dbRoot: string;
22
+ };
23
+
24
+ function log(message: string) {
25
+ console.log(`[conformance] ${message}`);
26
+ }
27
+
28
+ function sleep(ms: number) {
29
+ return new Promise((resolve) => setTimeout(resolve, ms));
30
+ }
31
+
32
+ async function waitFor(
33
+ label: string,
34
+ predicate: () => Promise<boolean>,
35
+ timeoutMs: number,
36
+ intervalMs = 250,
37
+ ) {
38
+ const deadline = Date.now() + timeoutMs;
39
+ while (Date.now() < deadline) {
40
+ if (await predicate()) return;
41
+ await sleep(intervalMs);
42
+ }
43
+ throw new Error(`Timed out waiting for ${label}`);
44
+ }
45
+
46
+ function freePort(host = "127.0.0.1"): Promise<number> {
47
+ return new Promise((resolvePromise, reject) => {
48
+ const server = createServer();
49
+ server.listen(0, host, () => {
50
+ const address = server.address();
51
+ if (!address || typeof address === "string") {
52
+ server.close(() => reject(new Error("Could not allocate port")));
53
+ return;
54
+ }
55
+ const port = address.port;
56
+ server.close(() => resolvePromise(port));
57
+ });
58
+ server.on("error", reject);
59
+ });
60
+ }
61
+
62
+ function capture(child: ChildProcess): () => string {
63
+ let stdout = "";
64
+ let stderr = "";
65
+ child.stdout?.on("data", (chunk) => {
66
+ stdout += chunk.toString();
67
+ });
68
+ child.stderr?.on("data", (chunk) => {
69
+ stderr += chunk.toString();
70
+ });
71
+ return () => `${stdout}\n${stderr}`;
72
+ }
73
+
74
+ async function stopProcess(child: ChildProcess, name: string) {
75
+ if (child.exitCode !== null || child.signalCode !== null) return;
76
+ child.kill("SIGTERM");
77
+ const stopped = await new Promise<boolean>((resolvePromise) => {
78
+ const timeout = setTimeout(() => resolvePromise(false), 1500);
79
+ child.once("exit", () => {
80
+ clearTimeout(timeout);
81
+ resolvePromise(true);
82
+ });
83
+ });
84
+ if (!stopped) {
85
+ child.kill("SIGKILL");
86
+ await new Promise<void>((resolvePromise) =>
87
+ child.once("exit", () => resolvePromise()),
88
+ );
89
+ }
90
+ log(`stopped ${name}`);
91
+ }
92
+
93
+ async function startEngine(): Promise<EngineProcess> {
94
+ const host = "127.0.0.1";
95
+ const guardPort = await freePort(host);
96
+ const apiPeerPort = await freePort(host);
97
+ const metricsPort = await freePort(host);
98
+ const endpoint = `http://${host}:${guardPort}`;
99
+ const dbRoot = mkdtempSync(join(tmpdir(), "vercel-world-rivet-conf-"));
100
+ const configPath = join(dbRoot, "config.json");
101
+
102
+ mkdirSync(join(dbRoot, "db"), { recursive: true });
103
+ writeFileSync(
104
+ configPath,
105
+ JSON.stringify({
106
+ topology: {
107
+ datacenter_label: 1,
108
+ datacenters: {
109
+ default: {
110
+ datacenter_label: 1,
111
+ is_leader: true,
112
+ public_url: endpoint,
113
+ peer_url: `http://${host}:${apiPeerPort}`,
114
+ },
115
+ },
116
+ },
117
+ }),
118
+ );
119
+
120
+ const child = spawn(getEnginePath(), ["start", "--config", configPath], {
121
+ env: {
122
+ ...process.env,
123
+ RIVET__GUARD__HOST: host,
124
+ RIVET__GUARD__PORT: String(guardPort),
125
+ RIVET__API_PEER__HOST: host,
126
+ RIVET__API_PEER__PORT: String(apiPeerPort),
127
+ RIVET__METRICS__HOST: host,
128
+ RIVET__METRICS__PORT: String(metricsPort),
129
+ RIVET__FILE_SYSTEM__PATH: join(dbRoot, "db"),
130
+ },
131
+ stdio: ["ignore", "pipe", "pipe"],
132
+ });
133
+ const output = capture(child);
134
+
135
+ await waitFor(
136
+ "engine health",
137
+ async () => {
138
+ if (child.exitCode !== null) {
139
+ throw new Error(`Engine exited early:\n${output()}`);
140
+ }
141
+ try {
142
+ return (await fetch(`${endpoint}/health`)).ok;
143
+ } catch {
144
+ return false;
145
+ }
146
+ },
147
+ 90_000,
148
+ 500,
149
+ );
150
+
151
+ log(`engine ready at ${endpoint}`);
152
+ return { child, endpoint, dbRoot, output };
153
+ }
154
+
155
+ async function createNamespace(endpoint: string, namespace: string) {
156
+ const response = await fetch(`${endpoint}/namespaces`, {
157
+ method: "POST",
158
+ headers: {
159
+ Authorization: `Bearer ${TOKEN}`,
160
+ "Content-Type": "application/json",
161
+ },
162
+ body: JSON.stringify({
163
+ name: namespace,
164
+ display_name: `Conformance ${namespace}`,
165
+ }),
166
+ });
167
+ if (!response.ok) {
168
+ throw new Error(
169
+ `Failed to create namespace: ${response.status} ${await response.text()}`,
170
+ );
171
+ }
172
+ }
173
+
174
+ async function upsertRunnerConfig(
175
+ endpoint: string,
176
+ namespace: string,
177
+ poolName: string,
178
+ ) {
179
+ const datacentersResponse = await fetch(
180
+ `${endpoint}/datacenters?namespace=${encodeURIComponent(namespace)}`,
181
+ { headers: { Authorization: `Bearer ${TOKEN}` } },
182
+ );
183
+ if (!datacentersResponse.ok) {
184
+ throw new Error(
185
+ `Failed to list datacenters: ${datacentersResponse.status} ${await datacentersResponse.text()}`,
186
+ );
187
+ }
188
+ const datacenters = (await datacentersResponse.json()) as {
189
+ datacenters: Array<{ name: string }>;
190
+ };
191
+ const datacenter = datacenters.datacenters[0]?.name;
192
+ if (!datacenter) throw new Error("Engine returned no datacenters");
193
+
194
+ const deadline = Date.now() + 30_000;
195
+ while (Date.now() < deadline) {
196
+ const response = await fetch(
197
+ `${endpoint}/runner-configs/${encodeURIComponent(poolName)}?namespace=${encodeURIComponent(namespace)}`,
198
+ {
199
+ method: "PUT",
200
+ headers: {
201
+ Authorization: `Bearer ${TOKEN}`,
202
+ "Content-Type": "application/json",
203
+ },
204
+ body: JSON.stringify({
205
+ datacenters: {
206
+ [datacenter]: { normal: {} },
207
+ },
208
+ }),
209
+ },
210
+ );
211
+ if (response.ok) return;
212
+ const body = await response.text();
213
+ if (body.includes('"code":"not_found"')) {
214
+ await sleep(500);
215
+ continue;
216
+ }
217
+ throw new Error(
218
+ `Failed to upsert runner config: ${response.status} ${body}`,
219
+ );
220
+ }
221
+ throw new Error("Timed out upserting runner config");
222
+ }
223
+
224
+ async function runVitest(
225
+ endpoint: string,
226
+ namespace: string,
227
+ poolName: string,
228
+ args: string[],
229
+ ) {
230
+ const child = spawn(
231
+ PNPM_BIN,
232
+ ["exec", "vitest", "run", "tests/conformance.test.ts", ...args],
233
+ {
234
+ cwd: ROOT,
235
+ env: {
236
+ ...process.env,
237
+ RUN_WORKFLOW_CONFORMANCE: "1",
238
+ RIVET_TOKEN: TOKEN,
239
+ RIVET_ENDPOINT: endpoint,
240
+ RIVET_NAMESPACE: namespace,
241
+ RIVET_POOL: poolName,
242
+ },
243
+ stdio: "inherit",
244
+ },
245
+ );
246
+ const code = await new Promise<number | null>((resolvePromise) =>
247
+ child.once("exit", resolvePromise),
248
+ );
249
+ if (code !== 0) {
250
+ throw new Error(`Vitest exited with code ${code}`);
251
+ }
252
+ }
253
+
254
+ async function run() {
255
+ const engine = await startEngine();
256
+ const namespace = `conf-${randomUUID()}`;
257
+ const poolName = `conf-${randomUUID()}`;
258
+
259
+ try {
260
+ await createNamespace(engine.endpoint, namespace);
261
+ await upsertRunnerConfig(engine.endpoint, namespace, poolName);
262
+ await runVitest(
263
+ engine.endpoint,
264
+ namespace,
265
+ poolName,
266
+ process.argv.slice(2),
267
+ );
268
+ } finally {
269
+ await stopProcess(engine.child, "engine");
270
+ rmSync(engine.dbRoot, { force: true, recursive: true });
271
+ }
272
+ }
273
+
274
+ run().catch((error) => {
275
+ console.error("[conformance] FAILED");
276
+ console.error(error);
277
+ process.exitCode = 1;
278
+ });