@rivetkit/engine-runner 25.7.1-rc.1
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/.turbo/turbo-build.log +23 -0
- package/.turbo/turbo-check-types.log +5 -0
- package/.turbo/turbo-test.log +5537 -0
- package/benches/actor-lifecycle.bench.ts +190 -0
- package/benches/utils.ts +143 -0
- package/dist/mod.cjs +2044 -0
- package/dist/mod.cjs.map +1 -0
- package/dist/mod.d.cts +67 -0
- package/dist/mod.d.ts +67 -0
- package/dist/mod.js +2044 -0
- package/dist/mod.js.map +1 -0
- package/package.json +38 -0
- package/src/log.ts +11 -0
- package/src/mod.ts +1354 -0
- package/src/tunnel.ts +841 -0
- package/src/utils.ts +31 -0
- package/src/websocket-tunnel-adapter.ts +486 -0
- package/src/websocket.ts +43 -0
- package/tests/lifecycle.test.ts +596 -0
- package/tsconfig.json +13 -0
- package/tsup.config.ts +4 -0
- package/turbo.json +4 -0
- package/vitest.config.ts +17 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
// import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
// import { Runner } from "@/mod";
|
|
3
|
+
// import type { RunnerConfig, ActorConfig } from "@/mod";
|
|
4
|
+
// import WebSocket, { type CloseEvent } from "ws";
|
|
5
|
+
//
|
|
6
|
+
// const RIVET_ENDPOINT = process.env.RIVET_ENDPOINT ?? "http://localhost:6420";
|
|
7
|
+
// const RIVET_ENDPOINT_WS = RIVET_ENDPOINT.replace("http://", "ws://").replace(
|
|
8
|
+
// "https://",
|
|
9
|
+
// "wss://",
|
|
10
|
+
// );
|
|
11
|
+
//
|
|
12
|
+
// async function createActor(
|
|
13
|
+
// namespaceName: string,
|
|
14
|
+
// runnerNameSelector: string,
|
|
15
|
+
// durable: boolean,
|
|
16
|
+
// name?: string,
|
|
17
|
+
// ): Promise<any> {
|
|
18
|
+
// const response = await fetch(
|
|
19
|
+
// `${RIVET_ENDPOINT}/actors?namespace=${namespaceName}`,
|
|
20
|
+
// {
|
|
21
|
+
// method: "POST",
|
|
22
|
+
// headers: {
|
|
23
|
+
// "Content-Type": "application/json",
|
|
24
|
+
// },
|
|
25
|
+
// body: JSON.stringify({
|
|
26
|
+
// name: name ?? "thingy",
|
|
27
|
+
// input: btoa("hello"),
|
|
28
|
+
// runner_name_selector: runnerNameSelector,
|
|
29
|
+
// durable,
|
|
30
|
+
// }),
|
|
31
|
+
// },
|
|
32
|
+
// );
|
|
33
|
+
//
|
|
34
|
+
// if (!response.ok) {
|
|
35
|
+
// throw new Error(
|
|
36
|
+
// `Failed to create actor: ${response.status} ${response.statusText}\n${await response.text()}`,
|
|
37
|
+
// );
|
|
38
|
+
// }
|
|
39
|
+
//
|
|
40
|
+
// return response.json();
|
|
41
|
+
// }
|
|
42
|
+
//
|
|
43
|
+
// async function destroyActor(
|
|
44
|
+
// namespaceName: string,
|
|
45
|
+
// actorId: string,
|
|
46
|
+
// ): Promise<void> {
|
|
47
|
+
// const response = await fetch(
|
|
48
|
+
// `${RIVET_ENDPOINT}/actors/${actorId}?namespace=${namespaceName}`,
|
|
49
|
+
// {
|
|
50
|
+
// method: "DELETE",
|
|
51
|
+
// },
|
|
52
|
+
// );
|
|
53
|
+
//
|
|
54
|
+
// if (!response.ok) {
|
|
55
|
+
// throw new Error(
|
|
56
|
+
// `Failed to delete actor: ${response.status} ${response.statusText}\n${await response.text()}`,
|
|
57
|
+
// );
|
|
58
|
+
// }
|
|
59
|
+
// }
|
|
60
|
+
//
|
|
61
|
+
// async function createNamespace(
|
|
62
|
+
// name: string,
|
|
63
|
+
// displayName: string,
|
|
64
|
+
// ): Promise<any> {
|
|
65
|
+
// const response = await fetch(`${RIVET_ENDPOINT}/namespaces`, {
|
|
66
|
+
// method: "POST",
|
|
67
|
+
// headers: {
|
|
68
|
+
// "Content-Type": "application/json",
|
|
69
|
+
// },
|
|
70
|
+
// body: JSON.stringify({
|
|
71
|
+
// name,
|
|
72
|
+
// display_name: displayName,
|
|
73
|
+
// }),
|
|
74
|
+
// });
|
|
75
|
+
//
|
|
76
|
+
// if (!response.ok) {
|
|
77
|
+
// console.warn(
|
|
78
|
+
// `Failed to create namespace: ${response.status} ${response.statusText}\n${await response.text()}`,
|
|
79
|
+
// );
|
|
80
|
+
// }
|
|
81
|
+
// }
|
|
82
|
+
//
|
|
83
|
+
// async function getActorNames(namespaceName: string): Promise<any> {
|
|
84
|
+
// const response = await fetch(
|
|
85
|
+
// `${RIVET_ENDPOINT}/actors/names?namespace=${namespaceName}`,
|
|
86
|
+
// {
|
|
87
|
+
// method: "GET",
|
|
88
|
+
// headers: {
|
|
89
|
+
// "Content-Type": "application/json",
|
|
90
|
+
// },
|
|
91
|
+
// },
|
|
92
|
+
// );
|
|
93
|
+
//
|
|
94
|
+
// if (!response.ok) {
|
|
95
|
+
// throw new Error(
|
|
96
|
+
// `Failed to get actor names: ${response.status} ${response.statusText}\n${await response.text()}`,
|
|
97
|
+
// );
|
|
98
|
+
// }
|
|
99
|
+
//
|
|
100
|
+
// return await response.json();
|
|
101
|
+
// }
|
|
102
|
+
//
|
|
103
|
+
// describe("Runner E2E", () => {
|
|
104
|
+
// it("performs end-to-end actor lifecycle", async () => {
|
|
105
|
+
// const namespaceName = `test-${Math.floor(Math.random() * 10000)}`;
|
|
106
|
+
// const runnerName = "test-runner";
|
|
107
|
+
// const prepopulateActorNames: string[] = Array.from(
|
|
108
|
+
// { length: 8 },
|
|
109
|
+
// () =>
|
|
110
|
+
// `actor-${Math.random().toString(36).substring(2, 10)}-${Date.now()}`,
|
|
111
|
+
// );
|
|
112
|
+
// let runnerStarted = Promise.withResolvers();
|
|
113
|
+
// let websocketOpen = Promise.withResolvers();
|
|
114
|
+
// let websocketClosed = Promise.withResolvers();
|
|
115
|
+
// let runner: Runner | null = null;
|
|
116
|
+
// const actorWebSockets = new Map<string, WebSocket>();
|
|
117
|
+
//
|
|
118
|
+
// // Use objects to hold the current promise resolvers so callbacks always get the latest
|
|
119
|
+
// const startedRef = { current: Promise.withResolvers() };
|
|
120
|
+
// const stoppedRef = { current: Promise.withResolvers() };
|
|
121
|
+
//
|
|
122
|
+
// const config: RunnerConfig = {
|
|
123
|
+
// version: 1,
|
|
124
|
+
// endpoint: RIVET_ENDPOINT,
|
|
125
|
+
// namespace: namespaceName,
|
|
126
|
+
// addresses: { main: { host: "127.0.0.1", port: 5051 } },
|
|
127
|
+
// totalSlots: 100,
|
|
128
|
+
// runnerName: runnerName,
|
|
129
|
+
// runnerKey: "default",
|
|
130
|
+
// prepopulateActorNames,
|
|
131
|
+
// onConnected: () => {
|
|
132
|
+
// runnerStarted.resolve(undefined);
|
|
133
|
+
// },
|
|
134
|
+
// onDisconnected: () => { },
|
|
135
|
+
// fetch: async (actorId: string, request: Request) => {
|
|
136
|
+
// const url = new URL(request.url);
|
|
137
|
+
// if (url.pathname === "/ping") {
|
|
138
|
+
// // Return the actor ID in response
|
|
139
|
+
// return new Response(
|
|
140
|
+
// JSON.stringify({
|
|
141
|
+
// actorId,
|
|
142
|
+
// status: "ok",
|
|
143
|
+
// timestamp: Date.now(),
|
|
144
|
+
// }),
|
|
145
|
+
// {
|
|
146
|
+
// status: 200,
|
|
147
|
+
// headers: { "Content-Type": "application/json" },
|
|
148
|
+
// },
|
|
149
|
+
// );
|
|
150
|
+
// }
|
|
151
|
+
// return new Response("ok", { status: 200 });
|
|
152
|
+
// },
|
|
153
|
+
// onActorStart: async (
|
|
154
|
+
// _actorId: string,
|
|
155
|
+
// _generation: number,
|
|
156
|
+
// _config: ActorConfig,
|
|
157
|
+
// ) => {
|
|
158
|
+
// console.log(
|
|
159
|
+
// `Actor ${_actorId} started (generation ${_generation})`,
|
|
160
|
+
// );
|
|
161
|
+
// startedRef.current.resolve(undefined);
|
|
162
|
+
// },
|
|
163
|
+
// onActorStop: async (_actorId: string, _generation: number) => {
|
|
164
|
+
// console.log(
|
|
165
|
+
// `Actor ${_actorId} stopped (generation ${_generation})`,
|
|
166
|
+
// );
|
|
167
|
+
// stoppedRef.current.resolve(undefined);
|
|
168
|
+
// },
|
|
169
|
+
// websocket: async (
|
|
170
|
+
// actorId: string,
|
|
171
|
+
// ws: WebSocket,
|
|
172
|
+
// request: Request,
|
|
173
|
+
// ) => {
|
|
174
|
+
// console.log(`WebSocket connected for actor ${actorId}`);
|
|
175
|
+
// websocketOpen.resolve(undefined);
|
|
176
|
+
// actorWebSockets.set(actorId, ws);
|
|
177
|
+
//
|
|
178
|
+
// // Echo server - send back any messages received
|
|
179
|
+
// ws.on("message", (data) => {
|
|
180
|
+
// console.log(
|
|
181
|
+
// `WebSocket message from actor ${actorId}:`,
|
|
182
|
+
// data.toString(),
|
|
183
|
+
// );
|
|
184
|
+
// ws.send(`Echo: ${data}`);
|
|
185
|
+
// });
|
|
186
|
+
//
|
|
187
|
+
// ws.on("close", () => {
|
|
188
|
+
// console.log(`WebSocket closed for actor ${actorId}`);
|
|
189
|
+
// actorWebSockets.delete(actorId);
|
|
190
|
+
// websocketClosed.resolve(undefined);
|
|
191
|
+
// });
|
|
192
|
+
// },
|
|
193
|
+
// };
|
|
194
|
+
//
|
|
195
|
+
// // Create namespace first
|
|
196
|
+
// await createNamespace(namespaceName, "Test Namespace");
|
|
197
|
+
//
|
|
198
|
+
// runner = new Runner(config);
|
|
199
|
+
//
|
|
200
|
+
// // Check pegboard URL configuration
|
|
201
|
+
// expect(runner.pegboardUrl).toBe(
|
|
202
|
+
// `${RIVET_ENDPOINT_WS}/v1?namespace=${namespaceName}`,
|
|
203
|
+
// );
|
|
204
|
+
//
|
|
205
|
+
// // Start runner
|
|
206
|
+
// runner.start();
|
|
207
|
+
//
|
|
208
|
+
// // Wait for runner to be ready
|
|
209
|
+
// console.log("Waiting runner start...");
|
|
210
|
+
// await runnerStarted.promise;
|
|
211
|
+
//
|
|
212
|
+
// // Check actor names prepopulated
|
|
213
|
+
// console.log("Comparing actor names...");
|
|
214
|
+
// await vi.waitFor(
|
|
215
|
+
// async () => {
|
|
216
|
+
// const { names } = await getActorNames(namespaceName);
|
|
217
|
+
// expect(names.sort()).toStrictEqual(
|
|
218
|
+
// prepopulateActorNames.sort(),
|
|
219
|
+
// );
|
|
220
|
+
// },
|
|
221
|
+
// { interval: 100 },
|
|
222
|
+
// );
|
|
223
|
+
//
|
|
224
|
+
// // Create an actor
|
|
225
|
+
// console.log("Creating actor...");
|
|
226
|
+
// const actorResponse = await createActor(
|
|
227
|
+
// namespaceName,
|
|
228
|
+
// runnerName,
|
|
229
|
+
// false,
|
|
230
|
+
// );
|
|
231
|
+
// console.log("Actor created:", actorResponse.actor);
|
|
232
|
+
// const actorId = actorResponse.actor.actor_id;
|
|
233
|
+
//
|
|
234
|
+
// // Wait for actor to start
|
|
235
|
+
// console.log("Waiting new actor start...");
|
|
236
|
+
// await startedRef.current.promise;
|
|
237
|
+
//
|
|
238
|
+
// // Ping actor to get actor ID in response (via Guard port)
|
|
239
|
+
// console.log("Pinging actor...");
|
|
240
|
+
// const actorPingResponse = await fetch(`${RIVET_ENDPOINT}/ping`, {
|
|
241
|
+
// method: "GET",
|
|
242
|
+
// headers: {
|
|
243
|
+
// "x-rivet-target": "actor",
|
|
244
|
+
// "x-rivet-actor": actorId,
|
|
245
|
+
// },
|
|
246
|
+
// });
|
|
247
|
+
// expect(actorPingResponse.ok).toBe(true);
|
|
248
|
+
// const pingResult = (await actorPingResponse.json()) as any;
|
|
249
|
+
// expect(pingResult.actorId).toBe(actorId);
|
|
250
|
+
//
|
|
251
|
+
// // Test WebSocket connection
|
|
252
|
+
// console.log("Testing WebSocket connection...");
|
|
253
|
+
// const ws = new WebSocket(`${RIVET_ENDPOINT_WS}/ws`, {
|
|
254
|
+
// headers: {
|
|
255
|
+
// "x-rivet-target": "actor",
|
|
256
|
+
// "x-rivet-actor": actorId,
|
|
257
|
+
// },
|
|
258
|
+
// });
|
|
259
|
+
//
|
|
260
|
+
// const testMessage = "Hello, actor!";
|
|
261
|
+
// const messagePromise = new Promise<string>((resolve, reject) => {
|
|
262
|
+
// ws.once("open", () => {
|
|
263
|
+
// console.log("WebSocket connected");
|
|
264
|
+
// ws.send(testMessage);
|
|
265
|
+
// });
|
|
266
|
+
// ws.once("message", (data) => {
|
|
267
|
+
// resolve(data.toString());
|
|
268
|
+
// });
|
|
269
|
+
// ws.once("error", reject);
|
|
270
|
+
// });
|
|
271
|
+
//
|
|
272
|
+
// await websocketOpen.promise;
|
|
273
|
+
//
|
|
274
|
+
// // Test WebSocket messaging
|
|
275
|
+
// console.log("Testing WebSocket messaging...");
|
|
276
|
+
// const response = await messagePromise;
|
|
277
|
+
// expect(response).toBe(`Echo: ${testMessage}`);
|
|
278
|
+
//
|
|
279
|
+
// // Close WebSocket for now
|
|
280
|
+
// ws.close();
|
|
281
|
+
// console.log("Waiting websocket close...");
|
|
282
|
+
// await websocketClosed.promise;
|
|
283
|
+
//
|
|
284
|
+
// await testKv(runner, actorId);
|
|
285
|
+
//
|
|
286
|
+
// // Sleep and wake actor 3 times in a loop
|
|
287
|
+
// for (let i = 1; i <= 3; i++) {
|
|
288
|
+
// console.log(`Sleep/wake cycle ${i}/3`);
|
|
289
|
+
//
|
|
290
|
+
// // Sleep actor
|
|
291
|
+
// console.log(`Sleeping actor (cycle ${i})...`);
|
|
292
|
+
// stoppedRef.current = Promise.withResolvers();
|
|
293
|
+
// runner.sleepActor(actorId);
|
|
294
|
+
//
|
|
295
|
+
// console.log("Waiting actor sleep...");
|
|
296
|
+
// await stoppedRef.current.promise;
|
|
297
|
+
//
|
|
298
|
+
// // Make network request to wake actor (via Guard)
|
|
299
|
+
// console.log(`Waking actor (cycle ${i})...`);
|
|
300
|
+
// startedRef.current = Promise.withResolvers();
|
|
301
|
+
// const wakeResponse = await fetch(`${RIVET_ENDPOINT}/wake`, {
|
|
302
|
+
// method: "GET",
|
|
303
|
+
// headers: {
|
|
304
|
+
// "x-rivet-target": "actor",
|
|
305
|
+
// "x-rivet-actor": actorId,
|
|
306
|
+
// },
|
|
307
|
+
// });
|
|
308
|
+
// console.log(`Wake response status: ${wakeResponse.status}`);
|
|
309
|
+
// console.log(`Wake response body: ${await wakeResponse.text()}`);
|
|
310
|
+
// expect(wakeResponse.status).toBe(200);
|
|
311
|
+
//
|
|
312
|
+
// // TODO: Remove this
|
|
313
|
+
// // Wait for actor to wake
|
|
314
|
+
// console.log("Waiting actor start...");
|
|
315
|
+
// await startedRef.current.promise;
|
|
316
|
+
// console.log(`Actor started successfully for cycle ${i}`);
|
|
317
|
+
//
|
|
318
|
+
// await testKvAfterSleep(runner, actorId);
|
|
319
|
+
// }
|
|
320
|
+
//
|
|
321
|
+
// // Sleep and wake actor 3 times in a loop
|
|
322
|
+
// for (let i = 1; i <= 3; i++) {
|
|
323
|
+
// console.log(`Sleep/wake cycle ${i}/3`);
|
|
324
|
+
//
|
|
325
|
+
// // Sleep actor
|
|
326
|
+
// console.log(`Sleeping actor (cycle ${i})...`);
|
|
327
|
+
// stoppedRef.current = Promise.withResolvers();
|
|
328
|
+
// runner.sleepActor(actorId);
|
|
329
|
+
//
|
|
330
|
+
// console.log("Waiting actor sleep...");
|
|
331
|
+
// await stoppedRef.current.promise;
|
|
332
|
+
//
|
|
333
|
+
// // Open websocket to wake actor (via Guard)
|
|
334
|
+
// console.log(`Waking actor (cycle ${i})...`);
|
|
335
|
+
// startedRef.current = Promise.withResolvers();
|
|
336
|
+
// const ws = new WebSocket(`${RIVET_ENDPOINT_WS}/ws`, {
|
|
337
|
+
// headers: {
|
|
338
|
+
// "x-rivet-target": "actor",
|
|
339
|
+
// "x-rivet-actor": actorId,
|
|
340
|
+
// },
|
|
341
|
+
// });
|
|
342
|
+
//
|
|
343
|
+
// await new Promise<void>((resolve, reject) => {
|
|
344
|
+
// ws.on("open", () => {
|
|
345
|
+
// console.log("WebSocket connected for wake test");
|
|
346
|
+
// resolve();
|
|
347
|
+
// });
|
|
348
|
+
// ws.on("error", reject);
|
|
349
|
+
// });
|
|
350
|
+
//
|
|
351
|
+
// // TODO: Remove this
|
|
352
|
+
// // Wait for actor to wake
|
|
353
|
+
// console.log("Waiting actor start...");
|
|
354
|
+
// await startedRef.current.promise;
|
|
355
|
+
// console.log(`Actor started successfully for cycle ${i}`);
|
|
356
|
+
//
|
|
357
|
+
// await testKvAfterSleep(runner, actorId);
|
|
358
|
+
// }
|
|
359
|
+
//
|
|
360
|
+
// // Create a fresh WebSocket connection for destroy testing
|
|
361
|
+
// console.log("Creating WebSocket for destroy test...");
|
|
362
|
+
// const wsForDestroy = new WebSocket(`${RIVET_ENDPOINT_WS}/ws`, {
|
|
363
|
+
// headers: {
|
|
364
|
+
// "x-rivet-target": "actor",
|
|
365
|
+
// "x-rivet-actor": actorId,
|
|
366
|
+
// },
|
|
367
|
+
// });
|
|
368
|
+
//
|
|
369
|
+
// await new Promise<void>((resolve, reject) => {
|
|
370
|
+
// wsForDestroy.on("open", () => {
|
|
371
|
+
// console.log("WebSocket connected for destroy test");
|
|
372
|
+
// resolve();
|
|
373
|
+
// });
|
|
374
|
+
// wsForDestroy.on("error", reject);
|
|
375
|
+
// });
|
|
376
|
+
//
|
|
377
|
+
// // Test WebSocket closes on actor destroy
|
|
378
|
+
// const wsClosePromise = new Promise<void>((resolve) => {
|
|
379
|
+
// wsForDestroy.on("close", () => {
|
|
380
|
+
// console.log("WebSocket closed after actor destroy");
|
|
381
|
+
// resolve();
|
|
382
|
+
// });
|
|
383
|
+
// });
|
|
384
|
+
//
|
|
385
|
+
// // Destroy actor
|
|
386
|
+
// console.log("Destroying actor...");
|
|
387
|
+
// stoppedRef.current = Promise.withResolvers(); // Create new promise for actor destroy
|
|
388
|
+
//
|
|
389
|
+
// // Start destroy and wait for WebSocket close simultaneously
|
|
390
|
+
// const destroyPromise = destroyActor(namespaceName, actorId);
|
|
391
|
+
//
|
|
392
|
+
// // Wait for WebSocket to close
|
|
393
|
+
// console.log("Waiting WS close...");
|
|
394
|
+
// await wsClosePromise;
|
|
395
|
+
//
|
|
396
|
+
// // Ensure destroy API call completed
|
|
397
|
+
// await destroyPromise;
|
|
398
|
+
// console.log("Destroy API call completed");
|
|
399
|
+
//
|
|
400
|
+
// // Wait for actor to stop with timeout
|
|
401
|
+
// console.log("Waiting actor stopped...");
|
|
402
|
+
// await stoppedRef.current.promise;
|
|
403
|
+
// console.log("Actor stop callback completed");
|
|
404
|
+
//
|
|
405
|
+
// // Validate actor is destroyed
|
|
406
|
+
// console.log("Validating actor is destroyed...");
|
|
407
|
+
// const destroyedPingResponse = await fetch(`${RIVET_ENDPOINT}/ping`, {
|
|
408
|
+
// headers: {
|
|
409
|
+
// "x-rivet-target": "actor",
|
|
410
|
+
// "x-rivet-actor": actorId,
|
|
411
|
+
// },
|
|
412
|
+
// });
|
|
413
|
+
// expect(destroyedPingResponse.status).toBe(404);
|
|
414
|
+
//
|
|
415
|
+
// // Test WebSocket connection to destroyed actor fails
|
|
416
|
+
// console.log("Testing WebSocket to destroyed actor...");
|
|
417
|
+
// const wsToDestroyed = new WebSocket(`${RIVET_ENDPOINT_WS}/ws`, {
|
|
418
|
+
// headers: {
|
|
419
|
+
// "x-rivet-target": "actor",
|
|
420
|
+
// "x-rivet-actor": actorId,
|
|
421
|
+
// },
|
|
422
|
+
// });
|
|
423
|
+
//
|
|
424
|
+
// console.log(
|
|
425
|
+
// "Waiting WS close...",
|
|
426
|
+
// );
|
|
427
|
+
// const closeCode = await new Promise<number>((resolve, reject) => {
|
|
428
|
+
// wsToDestroyed.on("error", (err) => {
|
|
429
|
+
// console.log("WebSocket should not have errored");
|
|
430
|
+
// reject(err);
|
|
431
|
+
// });
|
|
432
|
+
// wsToDestroyed.on("close", (code) => {
|
|
433
|
+
// console.log("WebSocket closed");
|
|
434
|
+
// resolve(code);
|
|
435
|
+
// });
|
|
436
|
+
// });
|
|
437
|
+
// expect(closeCode).toBe(1011);
|
|
438
|
+
//
|
|
439
|
+
// console.log("E2E test completed successfully!");
|
|
440
|
+
//
|
|
441
|
+
// // Clean up - stop the runner
|
|
442
|
+
// if (runner) {
|
|
443
|
+
// await runner.shutdown(false);
|
|
444
|
+
// }
|
|
445
|
+
// }, 30_000);
|
|
446
|
+
// });
|
|
447
|
+
//
|
|
448
|
+
// async function testKv(runner: Runner, actorId: string) {
|
|
449
|
+
// // Test KV operations
|
|
450
|
+
// console.log("Testing KV operations...");
|
|
451
|
+
//
|
|
452
|
+
// // Test kvPut and kvGet
|
|
453
|
+
// const testEntries: [Uint8Array, Uint8Array][] = [
|
|
454
|
+
// [createTestKey(["user", "123"]), createTestValue("alice")],
|
|
455
|
+
// [createTestKey(["user", "456"]), createTestValue("bob")],
|
|
456
|
+
// [createTestKey(["config", "theme"]), createTestValue("dark")],
|
|
457
|
+
// [createTestKey(["config", "lang"]), createTestValue("en")],
|
|
458
|
+
// ];
|
|
459
|
+
//
|
|
460
|
+
// console.log("Testing kvPut...");
|
|
461
|
+
// await runner.kvPut(actorId, testEntries);
|
|
462
|
+
//
|
|
463
|
+
// console.log("Testing kvGet...");
|
|
464
|
+
// const getKeys = testEntries.map(([key, _]) => key);
|
|
465
|
+
// const getResult = await runner.kvGet(actorId, getKeys);
|
|
466
|
+
//
|
|
467
|
+
// expect(getResult.length).toBe(4);
|
|
468
|
+
// expect(decodeValue(getResult[0]!)).toBe("alice");
|
|
469
|
+
// expect(decodeValue(getResult[1]!)).toBe("bob");
|
|
470
|
+
// expect(decodeValue(getResult[2]!)).toBe("dark");
|
|
471
|
+
// expect(decodeValue(getResult[3]!)).toBe("en");
|
|
472
|
+
//
|
|
473
|
+
// // Test getting non-existent key
|
|
474
|
+
// const nonExistentResult = await runner.kvGet(actorId, [
|
|
475
|
+
// createTestKey(["nonexistent"]),
|
|
476
|
+
// ]);
|
|
477
|
+
// expect(nonExistentResult[0]).toBe(null);
|
|
478
|
+
//
|
|
479
|
+
// // Test kvListAll
|
|
480
|
+
// console.log("Testing kvListAll...");
|
|
481
|
+
// const allEntries = await runner.kvListAll(actorId);
|
|
482
|
+
// expect(allEntries.length).toBe(4);
|
|
483
|
+
//
|
|
484
|
+
// // Verify all entries are present (order may vary)
|
|
485
|
+
// const allValues = allEntries.map(([_, value]) => decodeValue(value));
|
|
486
|
+
// expect(allValues.sort()).toEqual(["alice", "bob", "dark", "en"]);
|
|
487
|
+
//
|
|
488
|
+
// // Test kvListAll with limit
|
|
489
|
+
// const limitedEntries = await runner.kvListAll(actorId, { limit: 2 });
|
|
490
|
+
// expect(limitedEntries.length).toBe(2);
|
|
491
|
+
//
|
|
492
|
+
// // Test kvListPrefix
|
|
493
|
+
// console.log("Testing kvListPrefix...");
|
|
494
|
+
// const userEntries = await runner.kvListPrefix(
|
|
495
|
+
// actorId,
|
|
496
|
+
// createTestKey(["user"]),
|
|
497
|
+
// );
|
|
498
|
+
// // Note: Prefix queries may not be working as expected on the server side
|
|
499
|
+
// // For now, we'll test that the method executes without error
|
|
500
|
+
// expect(userEntries.length).toBeGreaterThanOrEqual(0);
|
|
501
|
+
//
|
|
502
|
+
// const configEntries = await runner.kvListPrefix(
|
|
503
|
+
// actorId,
|
|
504
|
+
// createTestKey(["config"]),
|
|
505
|
+
// );
|
|
506
|
+
// expect(configEntries.length).toBeGreaterThanOrEqual(0);
|
|
507
|
+
//
|
|
508
|
+
// // Test kvListRange
|
|
509
|
+
// console.log("Testing kvListRange...");
|
|
510
|
+
// const rangeEntries = await runner.kvListRange(
|
|
511
|
+
// actorId,
|
|
512
|
+
// createTestKey(["config"]),
|
|
513
|
+
// createTestKey(["user"]),
|
|
514
|
+
// false, // inclusive
|
|
515
|
+
// );
|
|
516
|
+
// // Range queries may have varying behavior depending on key ordering
|
|
517
|
+
// expect(rangeEntries.length).toBeGreaterThanOrEqual(0);
|
|
518
|
+
//
|
|
519
|
+
// // Test kvDelete
|
|
520
|
+
// console.log("Testing kvDelete...");
|
|
521
|
+
// const keysToDelete = [createTestKey(["user", "456"])]; // Delete bob
|
|
522
|
+
// await runner.kvDelete(actorId, keysToDelete);
|
|
523
|
+
//
|
|
524
|
+
// // Verify deletion worked
|
|
525
|
+
// const afterDeleteResult = await runner.kvGet(actorId, [
|
|
526
|
+
// createTestKey(["user", "456"]),
|
|
527
|
+
// ]);
|
|
528
|
+
// expect(afterDeleteResult[0]).toBe(null);
|
|
529
|
+
//
|
|
530
|
+
// // Verify other data still exists
|
|
531
|
+
// const remainingUserResult = await runner.kvGet(actorId, [
|
|
532
|
+
// createTestKey(["user", "123"]),
|
|
533
|
+
// ]);
|
|
534
|
+
// expect(decodeValue(remainingUserResult[0]!)).toBe("alice");
|
|
535
|
+
//
|
|
536
|
+
// // Test kvDrop operation before destroy
|
|
537
|
+
// console.log("Testing kvDrop...");
|
|
538
|
+
// await runner.kvDrop(actorId);
|
|
539
|
+
//
|
|
540
|
+
// // Verify all data is cleared
|
|
541
|
+
// const afterDropData = await runner.kvGet(actorId, [
|
|
542
|
+
// createTestKey(["user", "123"]),
|
|
543
|
+
// createTestKey(["config", "theme"]),
|
|
544
|
+
// createTestKey(["config", "lang"]),
|
|
545
|
+
// ]);
|
|
546
|
+
// expect(afterDropData[0]).toBe(null);
|
|
547
|
+
// expect(afterDropData[1]).toBe(null);
|
|
548
|
+
// expect(afterDropData[2]).toBe(null);
|
|
549
|
+
//
|
|
550
|
+
// // Verify list operations return empty after drop
|
|
551
|
+
// const afterDropList = await runner.kvListAll(actorId);
|
|
552
|
+
// expect(afterDropList.length).toBe(0);
|
|
553
|
+
//
|
|
554
|
+
// // Write data to test it exists during a sleep
|
|
555
|
+
// console.log("Writing data to live during sleep...");
|
|
556
|
+
// await runner.kvPut(actorId, [
|
|
557
|
+
// [createTestKey(["user", "789"]), createTestValue("max")],
|
|
558
|
+
// ]);
|
|
559
|
+
// }
|
|
560
|
+
//
|
|
561
|
+
// async function testKvAfterSleep(runner: Runner, actorId: string) {
|
|
562
|
+
// // Verify data still exists after waking again
|
|
563
|
+
// const remainingUserResult = await runner.kvGet(actorId, [
|
|
564
|
+
// createTestKey(["user", "789"]),
|
|
565
|
+
// ]);
|
|
566
|
+
// expect(decodeValue(remainingUserResult[0]!)).toBe("max");
|
|
567
|
+
// }
|
|
568
|
+
//
|
|
569
|
+
// function createTestKey(segments: string[]): Uint8Array {
|
|
570
|
+
// return flattenUint8Arrays(segments.map((s) => new TextEncoder().encode(s)));
|
|
571
|
+
// }
|
|
572
|
+
//
|
|
573
|
+
// function createTestValue(value: string): Uint8Array {
|
|
574
|
+
// return new TextEncoder().encode(value);
|
|
575
|
+
// }
|
|
576
|
+
//
|
|
577
|
+
// function decodeValue(value: Uint8Array): string {
|
|
578
|
+
// return new TextDecoder().decode(value);
|
|
579
|
+
// }
|
|
580
|
+
//
|
|
581
|
+
// function flattenUint8Arrays(arrays: Uint8Array[]): Uint8Array {
|
|
582
|
+
// // Calculate total length
|
|
583
|
+
// const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
584
|
+
//
|
|
585
|
+
// // Create result array
|
|
586
|
+
// const result = new Uint8Array(totalLength);
|
|
587
|
+
//
|
|
588
|
+
// // Copy each array
|
|
589
|
+
// let offset = 0;
|
|
590
|
+
// for (const arr of arrays) {
|
|
591
|
+
// result.set(arr, offset);
|
|
592
|
+
// offset += arr.length;
|
|
593
|
+
// }
|
|
594
|
+
//
|
|
595
|
+
// return result;
|
|
596
|
+
// }
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
package/turbo.json
ADDED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
import { defineConfig } from "vitest/config";
|
|
3
|
+
import defaultConfig from "../../../vitest.base.ts";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
...defaultConfig,
|
|
7
|
+
resolve: {
|
|
8
|
+
alias: {
|
|
9
|
+
"@": resolve(__dirname, "./src"),
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
test: {
|
|
13
|
+
...defaultConfig.test,
|
|
14
|
+
include: ["tests/**/*.test.ts"],
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|