Skip to content

Commit 8c874e4

Browse files
committed
fix(rivetkit): normalize waitUntil/keepAwake promises to null to avoid serde undefined error
1 parent 27c18c7 commit 8c874e4

9 files changed

Lines changed: 151 additions & 4 deletions

File tree

rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/raw-websocket.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,22 @@ export const rawWebSocketBinaryActor = actor({
167167
},
168168
actions: {},
169169
});
170+
171+
export const rawWebSocketAsyncOpenActor = actor({
172+
state: {
173+
openCount: 0,
174+
},
175+
async onWebSocket(ctx, websocket) {
176+
ctx.state.openCount += 1;
177+
await new Promise((resolve) => setTimeout(resolve, 10));
178+
websocket.send(
179+
JSON.stringify({
180+
type: "async-open",
181+
openCount: ctx.state.openCount,
182+
}),
183+
);
184+
},
185+
actions: {
186+
getOpenCount: (ctx) => ctx.state.openCount,
187+
},
188+
});

rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-static.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ import {
7171
rawHttpVoidReturnActor,
7272
} from "./raw-http";
7373
import { rawHttpRequestPropertiesActor } from "./raw-http-request-properties";
74-
import { rawWebSocketActor, rawWebSocketBinaryActor } from "./raw-websocket";
74+
import {
75+
rawWebSocketActor,
76+
rawWebSocketAsyncOpenActor,
77+
rawWebSocketBinaryActor,
78+
} from "./raw-websocket";
7579
import { rejectConnectionActor } from "./reject-connection";
7680
import { requestAccessActor } from "./request-access";
7781
import {
@@ -96,6 +100,7 @@ import {
96100
sleepWithRawHttp,
97101
sleepWithRawWebSocket,
98102
sleepWithWaitUntilMessage,
103+
counterWaitUntilProbe,
99104
sleepRawWsOnClose,
100105
sleepRawWsOnMessage,
101106
sleepRawWsSendOnSleep,
@@ -196,6 +201,7 @@ export const registry = setup({
196201
sleepRawWsSendOnSleep,
197202
sleepRawWsDelayedSendOnSleep,
198203
sleepWithWaitUntilInOnWake,
204+
counterWaitUntilProbe,
199205
// From sleep-db.ts
200206
sleepWithDb,
201207
sleepWithSlowScheduledDb,
@@ -260,6 +266,7 @@ export const registry = setup({
260266
rawHttpRequestPropertiesActor,
261267
// From raw-websocket.ts
262268
rawWebSocketActor,
269+
rawWebSocketAsyncOpenActor,
263270
rawWebSocketBinaryActor,
264271
// From reject-connection.ts
265272
rejectConnectionActor,

rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sleep.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,41 @@ export const sleepWithWaitUntilInOnWake = actor({
464464
},
465465
});
466466

467+
export const counterWaitUntilProbe = actor({
468+
state: { count: 0 },
469+
actions: {
470+
triggerWaitUntilVoid: (c) => {
471+
c.state.count += 1;
472+
c.waitUntil(Promise.resolve());
473+
return c.state.count;
474+
},
475+
triggerWaitUntilWithValue: (c) => {
476+
c.state.count += 1;
477+
c.waitUntil(Promise.resolve({ ok: true }));
478+
return c.state.count;
479+
},
480+
triggerWaitUntilRejectVoid: (c) => {
481+
c.state.count += 1;
482+
c.waitUntil(Promise.reject(new Error("reject-with-error-ok")));
483+
return c.state.count;
484+
},
485+
triggerKeepAwakeVoid: async (c) => {
486+
c.state.count += 1;
487+
await c.keepAwake(Promise.resolve());
488+
return c.state.count;
489+
},
490+
triggerKeepAwakeWithValue: async (c) => {
491+
c.state.count += 1;
492+
await c.keepAwake(Promise.resolve({ ok: true }));
493+
return c.state.count;
494+
},
495+
getCount: (c) => c.state.count,
496+
},
497+
options: {
498+
sleepTimeout: SLEEP_TIMEOUT,
499+
},
500+
});
501+
467502
export const sleepWithNoSleepOption = actor({
468503
state: { startCount: 0, sleepCount: 0 },
469504
onWake: (c) => {
@@ -485,4 +520,3 @@ export const sleepWithNoSleepOption = actor({
485520
noSleep: true,
486521
},
487522
});
488-

rivetkit-typescript/packages/rivetkit/src/registry/native.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,7 +2159,8 @@ class TrackedNativeWebSocketAdapter implements UniversalWebSocket {
21592159
})
21602160
.finally(() => {
21612161
this.#ctx.endWebSocketCallback(callbackRegionId);
2162-
}),
2162+
})
2163+
.then(() => null),
21632164
);
21642165
} catch (error) {
21652166
logger().error({
@@ -2638,8 +2639,9 @@ export class NativeActorContextAdapter {
26382639
}
26392640

26402641
waitUntil(promise: Promise<unknown>): void {
2642+
const trackedPromise = Promise.resolve(promise).then(() => null);
26412643
try {
2642-
callNativeSync(() => this.#ctx.waitUntil(Promise.resolve(promise)));
2644+
callNativeSync(() => this.#ctx.waitUntil(trackedPromise));
26432645
} catch (error) {
26442646
if (!isClosedTaskRegistrationError(error)) {
26452647
throw error;

rivetkit-typescript/packages/rivetkit/tests/driver/actor-sleep.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,62 @@ describeDriverMatrix("Actor Sleep", (driverTestConfig) => {
304304
}
305305
});
306306

307+
test("waitUntil accepts promises that resolve to undefined", async (c) => {
308+
const { client, getRuntimeOutput } = await setupDriverTest(
309+
c,
310+
driverTestConfig,
311+
);
312+
313+
const probe = client.counterWaitUntilProbe.getOrCreate();
314+
315+
expect(await probe.triggerWaitUntilVoid()).toBe(1);
316+
await waitFor(driverTestConfig, 50);
317+
318+
expect(await probe.getCount()).toBe(1);
319+
expect(getRuntimeOutput()).not.toContain(
320+
"undefined cannot be represented as a serde_json::Value",
321+
);
322+
323+
expect(await probe.triggerWaitUntilWithValue()).toBe(2);
324+
await waitFor(driverTestConfig, 50);
325+
326+
expect(await probe.getCount()).toBe(2);
327+
expect(getRuntimeOutput()).not.toContain(
328+
"undefined cannot be represented as a serde_json::Value",
329+
);
330+
331+
expect(await probe.triggerWaitUntilRejectVoid()).toBe(3);
332+
await waitFor(driverTestConfig, 50);
333+
334+
const runtimeOutput = getRuntimeOutput();
335+
expect(runtimeOutput).toContain("actor wait_until promise rejected");
336+
expect(runtimeOutput).toContain("reject-with-error-ok");
337+
expect(runtimeOutput).not.toContain(
338+
"undefined cannot be represented as a serde_json::Value",
339+
);
340+
});
341+
342+
test("keepAwake accepts promises that resolve to undefined", async (c) => {
343+
const { client, getRuntimeOutput } = await setupDriverTest(
344+
c,
345+
driverTestConfig,
346+
);
347+
348+
const probe = client.counterWaitUntilProbe.getOrCreate();
349+
350+
expect(await probe.triggerKeepAwakeVoid()).toBe(1);
351+
expect(await probe.triggerKeepAwakeWithValue()).toBe(2);
352+
expect(await probe.getCount()).toBe(2);
353+
354+
const runtimeOutput = getRuntimeOutput();
355+
expect(runtimeOutput).not.toContain(
356+
"keepAwake bridge to native runtime failed",
357+
);
358+
expect(runtimeOutput).not.toContain(
359+
"undefined cannot be represented as a serde_json::Value",
360+
);
361+
});
362+
307363
test("rpc calls keep actor awake", async (c) => {
308364
const { client } = await setupDriverTest(c, driverTestConfig);
309365
const actorKey = [`rpc-awake-${crypto.randomUUID()}`];

rivetkit-typescript/packages/rivetkit/tests/driver/raw-websocket.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,30 @@ describeDriverMatrix("Raw Websocket", (driverTestConfig) => {
428428
expect(finalStats?.connectionCount).toBe(0);
429429
});
430430

431+
test("should handle async onWebSocket open handler", async (c) => {
432+
const { client, getRuntimeOutput } = await setupDriverTest(
433+
c,
434+
driverTestConfig,
435+
);
436+
const actor = client.rawWebSocketAsyncOpenActor.getOrCreate([
437+
"async-open",
438+
]);
439+
440+
const ws = await actor.webSocket();
441+
const message = await waitForJsonMessage(ws, 5_000);
442+
443+
expect(message).toEqual({
444+
type: "async-open",
445+
openCount: 1,
446+
});
447+
expect(await actor.getOpenCount()).toBe(1);
448+
expect(getRuntimeOutput()).not.toContain(
449+
"undefined cannot be represented as a serde_json::Value",
450+
);
451+
452+
ws.close();
453+
});
454+
431455
test("should properly handle onWebSocket open and close events", async (c) => {
432456
const { client } = await setupDriverTest(c, driverTestConfig);
433457
const actor = client.rawWebSocketActor.getOrCreate([

rivetkit-typescript/packages/rivetkit/tests/driver/shared-harness.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@ export async function startNativeDriverRuntime(
602602
endpoint,
603603
namespace,
604604
runnerName: poolName,
605+
getRuntimeOutput: () => childOutput(logs),
605606
cleanup: async () => {
606607
await stopRuntime(runtime);
607608
},

rivetkit-typescript/packages/rivetkit/tests/driver/shared-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface DriverDeployOutput {
1717
runnerName: string;
1818
hardCrashActor?: (actorId: string) => Promise<void>;
1919
hardCrashPreservesData?: boolean;
20+
getRuntimeOutput?: () => string;
2021
cleanup(): Promise<void>;
2122
}
2223

rivetkit-typescript/packages/rivetkit/tests/driver/shared-utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export async function setupDriverTest(
3131
endpoint: string;
3232
hardCrashActor?: (actorId: string) => Promise<void>;
3333
hardCrashPreservesData: boolean;
34+
getRuntimeOutput: () => string;
3435
}> {
3536
if (!driverTestConfig.useRealTimers) {
3637
vi.useFakeTimers();
@@ -46,6 +47,7 @@ export async function setupDriverTest(
4647
runnerName,
4748
hardCrashActor,
4849
hardCrashPreservesData,
50+
getRuntimeOutput,
4951
cleanup,
5052
} = await driverTestConfig.start();
5153
timing("setup.driver_start", driverStartStartedAt, testName);
@@ -83,6 +85,7 @@ export async function setupDriverTest(
8385
endpoint,
8486
hardCrashActor,
8587
hardCrashPreservesData: hardCrashPreservesData ?? false,
88+
getRuntimeOutput: getRuntimeOutput ?? (() => ""),
8689
};
8790
}
8891

0 commit comments

Comments
 (0)