Skip to content

Commit 5fbbe77

Browse files
committed
chore(rivetkit): doc skip ready wait
1 parent 6b85d8c commit 5fbbe77

10 files changed

Lines changed: 111 additions & 11 deletions

File tree

rivetkit-typescript/packages/rivetkit/src/client/actor-common.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type ActorActionFunction<
3333

3434
export interface ActorGatewayOptions {
3535
bypassConnectable?: boolean;
36+
skipReadyWait?: boolean;
3637
}
3738

3839
export type ResolvedActorGatewayOptions = Required<ActorGatewayOptions>;
@@ -41,9 +42,16 @@ export function resolveActorGatewayOptions(
4142
defaults: ActorGatewayOptions = {},
4243
overrides?: ActorGatewayOptions,
4344
): ResolvedActorGatewayOptions {
45+
const bypassConnectable =
46+
overrides?.bypassConnectable ??
47+
overrides?.skipReadyWait ??
48+
defaults.bypassConnectable ??
49+
defaults.skipReadyWait ??
50+
false;
51+
4452
return {
45-
bypassConnectable:
46-
overrides?.bypassConnectable ?? defaults.bypassConnectable ?? false,
53+
bypassConnectable,
54+
skipReadyWait: bypassConnectable,
4755
};
4856
}
4957

rivetkit-typescript/packages/rivetkit/src/engine-client/actor-http-client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import {
55
HEADER_RIVET_TARGET,
66
HEADER_RIVET_TOKEN,
77
} from "@/common/actor-router-consts";
8-
import type { GatewayRequestOptions } from "./driver";
8+
import {
9+
shouldBypassConnectable,
10+
type GatewayRequestOptions,
11+
} from "./driver";
912

1013
export interface HttpGatewayRequestOptions extends GatewayRequestOptions {
1114
directActorId?: string;
@@ -79,7 +82,7 @@ function buildGuardHeaders(
7982
headers.set(HEADER_RIVET_TARGET, "actor");
8083
headers.set(HEADER_RIVET_ACTOR, options.directActorId);
8184
}
82-
if (options.bypassConnectable) {
85+
if (shouldBypassConnectable(options)) {
8386
headers.set(HEADER_RIVET_BYPASS_CONNECTABLE, "1");
8487
}
8588
return headers;

rivetkit-typescript/packages/rivetkit/src/engine-client/actor-websocket-client.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ import type { ActorGatewayQuery, CrashPolicy } from "@/client/query";
1818
import type { Encoding, UniversalWebSocket } from "@/mod";
1919
import { encodeCborCompat, uint8ArrayToBase64 } from "@/serde";
2020
import { combineUrlPath } from "@/utils";
21-
import type { GatewayRequestOptions } from "./driver";
21+
import {
22+
shouldBypassConnectable,
23+
type GatewayRequestOptions,
24+
} from "./driver";
2225
import { logger } from "./log";
2326

2427
class BufferedRemoteWebSocket implements UniversalWebSocket {
@@ -269,7 +272,7 @@ export function buildActorQueryGatewayUrl(
269272
if (token !== undefined) {
270273
params.append("rvt-token", token);
271274
}
272-
if (options.bypassConnectable) {
275+
if (shouldBypassConnectable(options)) {
273276
params.append("rvt-bypass_connectable", "true");
274277
}
275278

@@ -392,7 +395,7 @@ export function buildWebSocketProtocols(
392395
protocols.push(`${WS_PROTOCOL_TARGET}${target.target}`);
393396
protocols.push(`${WS_PROTOCOL_ACTOR}${target.actorId}`);
394397
}
395-
if (options.bypassConnectable) {
398+
if (shouldBypassConnectable(options)) {
396399
protocols.push(WS_PROTOCOL_BYPASS_CONNECTABLE);
397400
}
398401
if (params) {

rivetkit-typescript/packages/rivetkit/src/engine-client/driver.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ export type GatewayTarget = { directId: string } | ActorQuery;
88

99
export interface GatewayRequestOptions {
1010
bypassConnectable?: boolean;
11+
skipReadyWait?: boolean;
12+
}
13+
14+
export function shouldBypassConnectable(
15+
options: GatewayRequestOptions = {},
16+
): boolean {
17+
return options.bypassConnectable === true || options.skipReadyWait === true;
1118
}
1219

1320
export interface EngineControlClient {

rivetkit-typescript/packages/rivetkit/src/engine-client/mod.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from "@/common/actor-router-consts";
1010
import { noopNext } from "@/common/utils";
1111
import type { Actor as ApiActor } from "@/engine-api/actors";
12+
import { shouldBypassConnectable } from "@/engine-client/driver";
1213
import type {
1314
ActorOutput,
1415
CreateInput,
@@ -264,7 +265,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
264265
);
265266
const httpOptions = {
266267
...options,
267-
directActorId: options.bypassConnectable
268+
directActorId: shouldBypassConnectable(options)
268269
? directActorIdFromTarget(target)
269270
: undefined,
270271
};
@@ -299,7 +300,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
299300
params,
300301
{
301302
...options,
302-
directActorId: options.bypassConnectable
303+
directActorId: shouldBypassConnectable(options)
303304
? directActorIdFromTarget(target)
304305
: undefined,
305306
},
@@ -424,7 +425,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
424425
const endpoint = getEndpoint(this.#config);
425426

426427
if (
427-
options.bypassConnectable &&
428+
shouldBypassConnectable(options) &&
428429
directActorIdFromTarget(target) &&
429430
canUseDirectBypassPath(path)
430431
) {

rivetkit-typescript/packages/rivetkit/tests/actor-gateway-url.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import {
88
buildActorGatewayUrl,
99
buildActorQueryGatewayUrl,
10+
buildWebSocketProtocols,
1011
} from "@/engine-client/actor-websocket-client";
1112
import { toBase64Url } from "./test-utils";
1213

@@ -56,7 +57,30 @@ describe("gateway URL builders", () => {
5657
expect(url).not.toContain("@");
5758
});
5859

59-
test("serializes gateway bypass for query routing", () => {
60+
test("serializes skipReadyWait for query routing", () => {
61+
const url = buildActorQueryGatewayUrl(
62+
"https://api.rivet.dev/manager",
63+
"prod",
64+
{
65+
getForKey: {
66+
name: "room",
67+
key: ["alpha"],
68+
},
69+
},
70+
undefined,
71+
"/status",
72+
undefined,
73+
undefined,
74+
undefined,
75+
{ skipReadyWait: true },
76+
);
77+
78+
expect(new URL(url).searchParams.get("rvt-bypass_connectable")).toBe(
79+
"true",
80+
);
81+
});
82+
83+
test("serializes bypassConnectable for query routing", () => {
6084
const url = buildActorQueryGatewayUrl(
6185
"https://api.rivet.dev/manager",
6286
"prod",
@@ -79,6 +103,19 @@ describe("gateway URL builders", () => {
79103
);
80104
});
81105

106+
test("serializes bypassConnectable for websocket protocols", () => {
107+
const protocols = buildWebSocketProtocols(
108+
ClientConfigSchema.parse({ endpoint: "https://api.rivet.dev" }),
109+
"json",
110+
undefined,
111+
undefined,
112+
{ target: "actor", actorId: "actor-1" },
113+
{ bypassConnectable: true },
114+
);
115+
116+
expect(protocols).toContain("rivet_bypass_connectable");
117+
});
118+
82119
test("serializes getOrCreate queries with rvt-* params", () => {
83120
const input = { hello: "world" };
84121
const url = buildActorQueryGatewayUrl(

website/src/content/docs/actors/lifecycle.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,12 @@ curl -X POST \
761761

762762
`/sleep` asks the actor to enter the normal sleep shutdown sequence. `/reschedule` asks the platform to allocate the actor again, which is useful after crashes or when you need to force a fresh placement. Both endpoints require the actor ID and namespace.
763763

764+
### Skip Ready Wait
765+
766+
The gateway normally holds requests until the actor is ready. The actor is not ready during startup (before `onWake` finishes) or during the sleep grace period (while `onSleep` and `waitUntil` are running). Probes and readiness checks can opt out with `gateway.skipReadyWait` to reach the actor's `onRequest` or `onWebSocket` handler in either window.
767+
768+
See [Skip Ready Wait](/docs/clients/javascript#skip-ready-wait) on the JavaScript client page for usage.
769+
764770
### Keeping the Actor Awake
765771

766772
RivetKit gives you two primitives for holding the actor awake across background work. Both take a `Promise` and differ in how they interact with idle sleep and the grace period.

website/src/content/docs/actors/request-handler.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,12 @@ The `onRequest` handler is WinterTC compliant and will work with existing librar
249249
- Does not support streaming responses & server-sent events at the moment. See the [tracking issue](https://github.com/rivet-dev/rivet/issues/3529).
250250
- `OPTIONS` requests currently are handled by Rivet and are not passed to `onRequest`
251251

252+
## Advanced
253+
254+
### Skip Ready Wait
255+
256+
Requests are normally held at the gateway until the actor is ready. Pass `gateway.skipReadyWait: true` on `handle.fetch()` to deliver immediately, including while the actor is still starting or in the [sleep grace period](/docs/actors/lifecycle#shutdown-sequence). See [Skip Ready Wait](/docs/clients/javascript#skip-ready-wait) for details.
257+
252258
## API Reference
253259

254260
- [`RequestContext`](/typedoc/interfaces/rivetkit.mod.RequestContext.html) - Context for HTTP request handlers

website/src/content/docs/actors/websocket-handler.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ const myActor = actor({
295295
});
296296
```
297297

298+
### Skip Ready Wait
299+
300+
Connections are normally held at the gateway until the actor is ready. Pass `gateway.skipReadyWait: true` on `handle.webSocket()` to connect immediately, including while the actor is still starting or in the [sleep grace period](/docs/actors/lifecycle#shutdown-sequence). See [Skip Ready Wait](/docs/clients/javascript#skip-ready-wait) for details.
301+
298302
### Async Handlers
299303

300304
The `onWebSocket` handler can be async, allowing you to perform async code before setting up event listeners:

website/src/content/docs/clients/javascript.mdx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,31 @@ https://namespace:token@api.rivet.dev
253253

254254
You can also pass the endpoint without auth and provide `RIVET_NAMESPACE` and `RIVET_TOKEN` separately. For serverless deployments, use your app's `/api/rivet` URL. See [Endpoints](/docs/general/endpoints#url-auth-syntax) for details.
255255

256+
## Advanced
257+
258+
### Skip Ready Wait
259+
260+
Requests are normally held at the gateway until the actor is ready to accept traffic. An actor is not ready while it's still starting (before `onWake` finishes) or while it's in the [sleep grace period](/docs/actors/lifecycle#shutdown-sequence) (running `onSleep`, `waitUntil`, and pending disconnects).
261+
262+
Pass `gateway.skipReadyWait: true` on the [low-level HTTP and WebSocket APIs](#low-level-http--websocket) to deliver immediately and reach the actor's `onRequest` / `onWebSocket` handler in either window:
263+
264+
```ts @nocheck
265+
import { createClient } from "rivetkit/client";
266+
267+
const client = createClient();
268+
const handle = client.chatRoom.getOrCreate(["general"]);
269+
270+
const response = await handle.fetch("/healthz", {
271+
gateway: { skipReadyWait: true },
272+
});
273+
274+
const ws = await handle.webSocket("probe", undefined, {
275+
gateway: { skipReadyWait: true },
276+
});
277+
```
278+
279+
Requests still return a transient `actor.stopping` lifecycle error (`{"group":"actor","code":"stopping","message":"Actor is stopping."}`) if the actor has fully stopped, i.e. the sleep grace period has ended but it has not yet restarted. Retry once the actor is available again.
280+
256281
## API Reference
257282

258283
**Package:** [rivetkit](https://www.npmjs.com/package/rivetkit)

0 commit comments

Comments
 (0)