Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,8 @@ Data structures often include:
## Optimizations

- Never build a new reqwest client from scratch. Use `rivet_pools::reqwest::client().await?` to access an existing reqwest client instance.

## Documentation

- When talking about "Rivet Actors" make sure to capitalize "Rivet Actor" as a proper noun and lowercase "actor" as a generic noun

2 changes: 1 addition & 1 deletion rivetkit-typescript/packages/rivetkit/src/actor/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ export class DatabaseNotEnabled extends ActorError {
}
}

export class RequestHandlerNotDfeined extends ActorError {
export class RequestHandlerNotDefined extends ActorError {
constructor() {
super(
"handler",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
this.assertReady();

if (!this.#config.onRequest) {
throw new errors.RequestHandlerNotDfeined();
throw new errors.RequestHandlerNotDefined();
}

try {
Expand Down
34 changes: 23 additions & 11 deletions website/src/content/docs/actors/actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -163,33 +163,38 @@ For example:
import { actor, UserError } from "rivetkit";

const user = actor({
state: { users: [] },
state: { username: "" },
actions: {
registerUser: (c, username) => {
updateUsername: (c, username: string) => {
// Validate username
if (username.length > 32) {
// Throw a simple error with a message
throw new UserError("Invalid username", {
code: "invalid_username",
meta: {
throw new UserError("Username is too long", {
code: "username_too_long",
metadata: {
maxLength: 32
}
});
}

// Rest of the user registration logic...

// Update username
c.state.username = username;
}
}
});
```

```typescript {{"title":"client.ts"}}
import { ActorError } from "rivetkit/client";

try {
await userActor.registerUser("extremely_long_username_that_exceeds_limit");
await userActor.updateUsername("extremely_long_username_that_exceeds_limit");
} catch (error) {
console.log("Message", error.message); // "Invalid username"
console.log("Code", error.code); // "invalid_username"
console.log("Metadata", error.metadata); // { maxLength; 32 }
if (error instanceof ActorError) {
console.log("Message", error.message); // "Username is too long"
console.log("Code", error.code); // "username_too_long"
console.log("Metadata", error.metadata); // { maxLength: 32 }
}
}
```

Expand Down Expand Up @@ -267,3 +272,10 @@ function incrementCount(c: ActionContextOf<typeof counter>) {

See [helper types](/docs/actors/helper-types) for more details on using `ActionContextOf` and other utility types.

## API Reference

- [`Actions`](/typedoc/interfaces/rivetkit.mod.Actions.html) - Interface for defining actions
- [`ActionContext`](/typedoc/interfaces/rivetkit.mod.ActionContext.html) - Context available in action handlers
- [`ActorDefinition`](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) - Interface for defining actors with actions
- [`ActorHandle`](/typedoc/types/rivetkit.client_mod.ActorHandle.html) - Handle for calling actions from client
- [`ActorActionFunction`](/typedoc/types/rivetkit.client_mod.ActorActionFunction.html) - Type for action functions
6 changes: 6 additions & 0 deletions website/src/content/docs/actors/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -480,3 +480,9 @@ const cachedAuthActor = actor({
});
```

## API Reference

- [`AuthIntent`](/typedoc/types/rivetkit.mod.AuthIntent.html) - Authentication intent type
- [`OnBeforeConnectContext`](/typedoc/interfaces/rivetkit.mod.OnBeforeConnectContext.html) - Context for auth checks
- [`OnConnectContext`](/typedoc/interfaces/rivetkit.mod.OnConnectContext.html) - Context after connection

8 changes: 8 additions & 0 deletions website/src/content/docs/actors/clients.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -464,3 +464,11 @@ const actorId = await handle.resolve();
console.log(actorId); // "lrysjam017rhxofttna2x5nzjml610"
```

## API Reference

- [`createClient`](/typedoc/functions/rivetkit.client_mod.createClient.html) - Function to create clients
- [`Client`](/typedoc/types/rivetkit.mod.Client.html) - Client type
- [`ActorHandle`](/typedoc/types/rivetkit.client_mod.ActorHandle.html) - Handle for interacting with actors
- [`ActorConn`](/typedoc/types/rivetkit.client_mod.ActorConn.html) - Connection to actors
- [`ClientRaw`](/typedoc/interfaces/rivetkit.client_mod.ClientRaw.html) - Raw client interface

Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,9 @@ const results = await Promise.all(
);
```

## API Reference

- [`ActorHandle`](/typedoc/types/rivetkit.client_mod.ActorHandle.html) - Handle for calling other actors
- [`Client`](/typedoc/types/rivetkit.mod.Client.html) - Client type for actor communication
- [`ActorAccessor`](/typedoc/interfaces/rivetkit.client_mod.ActorAccessor.html) - Accessor for getting actor handles

149 changes: 139 additions & 10 deletions website/src/content/docs/actors/connections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { createClient } from "rivetkit/client";
import type { registry } from "./src/index";

const client = createClient<typeof registry>("http://localhost:8080");
const gameRoom = await client.gameRoom.get({
const gameRoom = client.gameRoom.getOrCreate("room-123", {
params: { authToken: "supersekure" }
});
```
Expand All @@ -38,7 +38,7 @@ const gameRoom = actor({
state: {},

// Handle connection setup
createConnState: (c, opts, params: ConnParams): ConnState => {
createConnState: (c, params: ConnParams): ConnState => {
// Validate authentication token
const authToken = params.authToken;

Expand All @@ -49,7 +49,7 @@ const gameRoom = actor({
// Create connection state
return { userId: getUserIdFromToken(authToken), role: "player" };
},

actions: {
// ...
}
Expand Down Expand Up @@ -133,16 +133,136 @@ There are two ways to define an actor's connection state:
</Tab>
</Tabs>

## Connection Lifecycle Hooks
## Connection Lifecycle

Each client connection goes through a series of lifecycle hooks that allow you to validate, initialize, and clean up connection-specific resources.

**On Connect** (per client)

- `onBeforeConnect`
- `createConnState`
- `onConnect`

**On Disconnect** (per client)

- `onDisconnect`

### `createConnState` and `connState`

[API Reference](/typedoc/interfaces/rivetkit.mod.CreateConnStateContext.html)

There are two ways to define the initial state for connections:
1. `connState`: Define a constant object that will be used as the initial state for all connections
2. `createConnState`: A function that dynamically creates initial connection state based on connection parameters. Can be async.

### `onBeforeConnect`

[API Reference](/typedoc/interfaces/rivetkit.mod.OnBeforeConnectContext.html)

The `onBeforeConnect` hook is called whenever a new client connects to the actor. Can be async. Clients can pass parameters when connecting, accessible via `params`. This hook is used for connection validation and can throw errors to reject connections.

The `onBeforeConnect` hook does NOT return connection state - it's used solely for validation.

```typescript
import { actor } from "rivetkit";

const chatRoom = actor({
state: { messages: [] },

// Method 1: Use a static default connection state
connState: {
role: "guest",
joinTime: 0,
},

// Method 2: Dynamically create connection state
createConnState: (c, params: { userId?: string, role?: string }) => {
return {
userId: params.userId || "anonymous",
role: params.role || "guest",
joinTime: Date.now()
};
},

// Validate connections before accepting them
onBeforeConnect: (c, params: { authToken?: string }) => {
// Validate authentication
const authToken = params.authToken;
if (!authToken || !validateToken(authToken)) {
throw new Error("Invalid authentication");
}

// Authentication is valid, connection will proceed
// The actual connection state will come from connState or createConnState
},

actions: { /* ... */ }
});
```

The connection lifecycle has several hooks:
Connections cannot interact with the actor until this method completes successfully. Throwing an error will abort the connection. This can be used for authentication, see [Authentication](/docs/actors/authentication) for details.

- `onBeforeConnect`: Called before a client connects, returns the connection state
- `onConnect`: Called when a client successfully connects
- `createState`: Called when creating a connection state
- `onDisconnect`: Called when a client disconnects
### `onConnect`

See the documentation on [Actor Lifecycle](/docs/actors/lifecycle) for more details.
[API Reference](/typedoc/interfaces/rivetkit.mod.OnConnectContext.html)

Executed after the client has successfully connected. Can be async. Receives the connection object as a second parameter.

```typescript
import { actor } from "rivetkit";

const chatRoom = actor({
state: { users: {}, messages: [] },

onConnect: (c, conn) => {
// Add user to the room's user list using connection state
const userId = conn.state.userId;
c.state.users[userId] = {
online: true,
lastSeen: Date.now()
};

// Broadcast that a user joined
c.broadcast("userJoined", { userId, timestamp: Date.now() });

console.log(`User ${userId} connected`);
},

actions: { /* ... */ }
});
```

Messages will not be processed for this actor until this hook succeeds. Errors thrown from this hook will cause the client to disconnect.

### `onDisconnect`

[API Reference](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html)

Called when a client disconnects from the actor. Can be async. Receives the connection object as a second parameter. Use this to clean up any connection-specific resources.

```typescript
import { actor } from "rivetkit";

const chatRoom = actor({
state: { users: {}, messages: [] },

onDisconnect: (c, conn) => {
// Update user status when they disconnect
const userId = conn.state.userId;
if (c.state.users[userId]) {
c.state.users[userId].online = false;
c.state.users[userId].lastSeen = Date.now();
}

// Broadcast that a user left
c.broadcast("userLeft", { userId, timestamp: Date.now() });

console.log(`User ${userId} disconnected`);
},

actions: { /* ... */ }
});
```

## Connection List

Expand Down Expand Up @@ -210,3 +330,12 @@ await c.conn.disconnect("Too many requests");
```

This ensures the underlying network connections close cleanly before continuing.

## API Reference

- [`Conn`](/typedoc/interfaces/rivetkit.mod.Conn.html) - Connection interface
- [`ConnInitContext`](/typedoc/interfaces/rivetkit.mod.ConnInitContext.html) - Connection initialization context
- [`CreateConnStateContext`](/typedoc/interfaces/rivetkit.mod.CreateConnStateContext.html) - Context for creating connection state
- [`OnBeforeConnectContext`](/typedoc/interfaces/rivetkit.mod.OnBeforeConnectContext.html) - Pre-connection lifecycle hook context
- [`OnConnectContext`](/typedoc/interfaces/rivetkit.mod.OnConnectContext.html) - Post-connection lifecycle hook context
- [`ActorConn`](/typedoc/types/rivetkit.client_mod.ActorConn.html) - Typed connection from client side
Loading
Loading