From 799cad2989dd1a3974f6c3b52add30afdafea125 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Mon, 24 Nov 2025 11:46:09 -0800 Subject: [PATCH] docs: clean up last few missing pieces of content --- CLAUDE.md | 5 + .../packages/rivetkit/src/actor/errors.ts | 2 +- .../rivetkit/src/actor/instance/mod.ts | 2 +- website/src/content/docs/actors/actions.mdx | 34 +- .../content/docs/actors/authentication.mdx | 6 + website/src/content/docs/actors/clients.mdx | 8 + .../actors/communicating-between-actors.mdx | 6 + .../src/content/docs/actors/connections.mdx | 149 +++++++- .../content/docs/actors/design-patterns.mdx | 204 ++++++++++- website/src/content/docs/actors/destroy.mdx | 5 + website/src/content/docs/actors/errors.mdx | 334 +++++++++++++++++- website/src/content/docs/actors/events.mdx | 10 + .../src/content/docs/actors/external-sql.mdx | 45 ++- .../src/content/docs/actors/helper-types.mdx | 13 + website/src/content/docs/actors/input.mdx | 6 + website/src/content/docs/actors/keys.mdx | 7 + website/src/content/docs/actors/lifecycle.mdx | 70 ++-- website/src/content/docs/actors/metadata.mdx | 5 + .../content/docs/actors/request-handler.mdx | 7 +- website/src/content/docs/actors/state.mdx | 5 + website/src/content/docs/actors/testing.mdx | 6 + .../content/docs/actors/websocket-handler.mdx | 7 + .../src/content/docs/clients/javascript.mdx | 9 +- website/src/content/docs/clients/next-js.mdx | 13 +- website/src/content/docs/clients/react.mdx | 13 + website/src/content/docs/cloud/index.mdx | 16 - .../content/docs/drivers/build-your-own.mdx | 17 - .../src/content/docs/drivers/file-system.mdx | 90 ----- website/src/content/docs/drivers/memory.mdx | 49 --- .../docs/examples/autofill-example.mdx | 75 ---- website/src/content/docs/general/cors.mdx | 263 +++----------- .../src/content/docs/general/self-hosting.mdx | 18 - website/src/content/docs/index.mdx | 1 - website/src/sitemap/mod.ts | 113 +++--- website/vercel.json | 3 + 35 files changed, 966 insertions(+), 650 deletions(-) delete mode 100644 website/src/content/docs/cloud/index.mdx delete mode 100644 website/src/content/docs/drivers/build-your-own.mdx delete mode 100644 website/src/content/docs/drivers/file-system.mdx delete mode 100644 website/src/content/docs/drivers/memory.mdx delete mode 100644 website/src/content/docs/examples/autofill-example.mdx delete mode 100644 website/src/content/docs/general/self-hosting.mdx create mode 100644 website/vercel.json diff --git a/CLAUDE.md b/CLAUDE.md index d5b7daf605..19b3a54dc0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 + diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/errors.ts b/rivetkit-typescript/packages/rivetkit/src/actor/errors.ts index 5dc81996aa..5b675ff0a8 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/errors.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/errors.ts @@ -297,7 +297,7 @@ export class DatabaseNotEnabled extends ActorError { } } -export class RequestHandlerNotDfeined extends ActorError { +export class RequestHandlerNotDefined extends ActorError { constructor() { super( "handler", diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts b/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts index 2fd4ab79f7..c40b7ffaa4 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts @@ -674,7 +674,7 @@ export class ActorInstance { this.assertReady(); if (!this.#config.onRequest) { - throw new errors.RequestHandlerNotDfeined(); + throw new errors.RequestHandlerNotDefined(); } try { diff --git a/website/src/content/docs/actors/actions.mdx b/website/src/content/docs/actors/actions.mdx index 882cee87ae..bb519ff5c4 100644 --- a/website/src/content/docs/actors/actions.mdx +++ b/website/src/content/docs/actors/actions.mdx @@ -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 } + } } ``` @@ -267,3 +272,10 @@ function incrementCount(c: ActionContextOf) { 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 diff --git a/website/src/content/docs/actors/authentication.mdx b/website/src/content/docs/actors/authentication.mdx index 2c81647168..303ef3a7c8 100644 --- a/website/src/content/docs/actors/authentication.mdx +++ b/website/src/content/docs/actors/authentication.mdx @@ -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 + diff --git a/website/src/content/docs/actors/clients.mdx b/website/src/content/docs/actors/clients.mdx index 4345e2fbcc..1de3c90655 100644 --- a/website/src/content/docs/actors/clients.mdx +++ b/website/src/content/docs/actors/clients.mdx @@ -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 + diff --git a/website/src/content/docs/actors/communicating-between-actors.mdx b/website/src/content/docs/actors/communicating-between-actors.mdx index 97bb4893e2..fd6a740a5d 100644 --- a/website/src/content/docs/actors/communicating-between-actors.mdx +++ b/website/src/content/docs/actors/communicating-between-actors.mdx @@ -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 + diff --git a/website/src/content/docs/actors/connections.mdx b/website/src/content/docs/actors/connections.mdx index ec1d43ba52..5ec3bdb822 100644 --- a/website/src/content/docs/actors/connections.mdx +++ b/website/src/content/docs/actors/connections.mdx @@ -17,7 +17,7 @@ import { createClient } from "rivetkit/client"; import type { registry } from "./src/index"; const client = createClient("http://localhost:8080"); -const gameRoom = await client.gameRoom.get({ +const gameRoom = client.gameRoom.getOrCreate("room-123", { params: { authToken: "supersekure" } }); ``` @@ -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; @@ -49,7 +49,7 @@ const gameRoom = actor({ // Create connection state return { userId: getUserIdFromToken(authToken), role: "player" }; }, - + actions: { // ... } @@ -133,16 +133,136 @@ There are two ways to define an actor's connection state: -## 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 @@ -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 diff --git a/website/src/content/docs/actors/design-patterns.mdx b/website/src/content/docs/actors/design-patterns.mdx index 59404ba425..5c16f46ac1 100644 --- a/website/src/content/docs/actors/design-patterns.mdx +++ b/website/src/content/docs/actors/design-patterns.mdx @@ -36,7 +36,7 @@ Actors scale by splitting state into isolated entities. However, it's common to **Coordinator actors** track other actors. Think of them as an index of data actors. Examples: a list of chat rooms, a list of active users, a list of game lobbies. -**Example: Chat Rooms** +**Example: Chat Room Coordinator** @@ -74,12 +74,19 @@ const chatRoomList = actor({ listChatRooms: (c) => c.state.chatRoomIds, }, }); + +export const registry = setup({ + use: { chatRoom, chatRoomList }, +}); ``` ```ts +import { createClient } from "rivetkit/client"; +import type { registry } from "./registry"; + const client = createClient("http://localhost:8080"); // Create a new chat room via coordinator @@ -107,14 +114,19 @@ Sharding splits a single actor's workload across multiple actors based on a key. - Requests are routed to shards based on the key - Shards operate independently without coordination -**Example: Sharded Analytics** - -Instead of one analytics actor handling all events, shard by time bucket: +**Example: Sharding by Time** ```ts +import { actor, setup } from "rivetkit"; + +interface Event { + type: string; + url: string; +} + const hourlyAnalytics = actor({ state: { events: [] as Event[] }, actions: { @@ -124,12 +136,21 @@ const hourlyAnalytics = actor({ getEvents: (c) => c.state.events, }, }); + +export const registry = setup({ + use: { hourlyAnalytics }, +}); ``` ```ts +import { createClient } from "rivetkit/client"; +import type { registry } from "./registry"; + +const client = createClient("http://localhost:8080"); + // Shard by hour: hourlyAnalytics:2024-01-15T00, hourlyAnalytics:2024-01-15T01 const shardKey = new Date().toISOString().slice(0, 13); // "2024-01-15T00" const analytics = client.hourlyAnalytics.getOrCreate(shardKey); @@ -139,14 +160,14 @@ await analytics.trackEvent({ type: "page_view", url: "/home" }); -**Example: Sharded Rate Limiter** - -Instead of one rate limiter that becomes a bottleneck, shard randomly: +**Example: Random Sharding** ```ts +import { actor, setup } from "rivetkit"; + const rateLimiter = actor({ state: { requests: {} as Record }, actions: { @@ -158,12 +179,21 @@ const rateLimiter = actor({ }, }, }); + +export const registry = setup({ + use: { rateLimiter }, +}); ``` ```ts +import { createClient } from "rivetkit/client"; +import type { registry } from "./registry"; + +const client = createClient("http://localhost:8080"); + // Shard randomly: rateLimiter:shard-0, rateLimiter:shard-1, rateLimiter:shard-2 const shardKey = `shard-${Math.floor(Math.random() * 3)}`; const limiter = client.rateLimiter.getOrCreate(shardKey); @@ -189,6 +219,18 @@ Fan-in and fan-out are patterns for distributing work and aggregating results. ```ts +import { actor, setup } from "rivetkit"; + +interface Task { + id: string; + data: string; +} + +interface Result { + taskId: string; + output: string; +} + // Coordinator fans out tasks, then fans in results const coordinator = actor({ state: { results: [] as Result[] }, @@ -212,18 +254,27 @@ const worker = actor({ state: {}, actions: { process: async (c, task: Task) => { - const result = await doWork(task); + const result = { taskId: task.id, output: `Processed ${task.data}` }; const client = c.client(); await client.coordinator.getOrCreate("main").reportResult(result); }, }, }); + +export const registry = setup({ + use: { coordinator, worker }, +}); ``` ```ts +import { createClient } from "rivetkit/client"; +import type { registry } from "./registry"; + +const client = createClient("http://localhost:8080"); + const coordinator = client.coordinator.getOrCreate("main"); // Start a job with multiple tasks @@ -240,23 +291,33 @@ const results = await coordinator.getResults(); -## State Loading From External Resources +## Integrating With External Databases & APIs + +Actors can integrate with external resources like databases or external APIs. + +### Loading State Load external data during actor initialization using `createVars`. This keeps your actor's persisted state clean while caching expensive lookups. -**When to use:** +Use this when: + - Fetching user profiles, configs, or permissions from a database - Loading data that changes externally and shouldn't be persisted +- Caching expensive API calls or computations -**Example: Loading User Data** +**Example: Loading User Profile** ```ts +import { actor, setup } from "rivetkit"; + const userSession = actor({ - state: { activityLog: [] as string[] }, + state: { requestCount: 0 }, + // createVars runs on every wake (after restarts, crashes, or sleep), so + // external data stays fresh. createVars: async (c) => { // Load from database on every wake const user = await db.users.findById(c.id); @@ -264,33 +325,127 @@ const userSession = actor({ }, actions: { - getProfile: (c) => c.vars.user, - updatePreference: async (c, key: string, value: string) => { - await db.users.update(c.id, { [key]: value }); + getProfile: (c) => { + c.state.requestCount++; + return c.vars.user; + }, + updateEmail: async (c, email: string) => { + c.state.requestCount++; + await db.users.update(c.id, { email }); // Refresh cached data c.vars.user = await db.users.findById(c.id); }, }, }); + +export const registry = setup({ + use: { userSession }, +}); ``` ```ts +import { createClient } from "rivetkit/client"; +import type { registry } from "./registry"; + +const client = createClient("http://localhost:8080"); + const session = client.userSession.getOrCreate("user-123"); // Get profile (loaded from database on actor wake) const profile = await session.getProfile(); -// Update preference (writes to database and refreshes cache) -await session.updatePreference("theme", "dark"); +// Update email (writes to database and refreshes cache) +await session.updateEmail("alice@example.com"); ``` -`createVars` runs on every wake (after restarts, crashes, or sleep), so external data stays fresh. + +### Syncing State Changes + +Use `onStateChange` to automatically sync actor state changes to external resources. This hook is called whenever the actor's state is modified. + +Use this when: + +- You need to mirror actor state in an external database +- Triggering external side effects when state changes +- Keeping external systems in sync with actor state + +**Example: Syncing to Database** + + + + +```ts +import { actor, setup } from "rivetkit"; + +const userActor = actor({ + state: { + email: "", + lastActive: 0, + }, + + onCreate: async (c, input: { email: string }) => { + // Insert into database on actor creation + await db.users.insert({ + id: c.id, + email: input.email, + createdAt: Date.now(), + }); + }, + + onStateChange: async (c, newState) => { + // Sync any state changes to database + await db.users.update(c.id, { + email: newState.email, + lastActive: newState.lastActive, + }); + }, + + actions: { + updateEmail: (c, email: string) => { + c.state.email = email; + c.state.lastActive = Date.now(); + }, + getUser: (c) => ({ + email: c.state.email, + lastActive: c.state.lastActive, + }), + }, +}); + +export const registry = setup({ + use: { userActor }, +}); +``` + + + + +```ts +import { createClient } from "rivetkit/client"; +import type { registry } from "./registry"; + +const client = createClient("http://localhost:8080"); + +const user = await client.userActor.create("user-123", { + input: { email: "alice@example.com" }, +}); + +// Updates state and triggers onStateChange +await user.updateEmail("alice2@example.com"); + +const userData = await user.getUser(); +``` + + + + +`onStateChange` is called after every state modification, ensuring external resources stay in sync. ## Anti-Patterns @@ -300,6 +455,8 @@ Avoid creating a single actor that handles everything. This defeats the purpose **Problem:** ```ts +import { actor } from "rivetkit"; + // Bad: one actor doing everything const app = actor({ state: { users: {}, orders: {}, inventory: {}, analytics: {} }, @@ -320,6 +477,11 @@ Actors are designed to maintain state across multiple requests. Creating a new a **Problem:** ```ts +import { createClient } from "rivetkit/client"; +import type { registry } from "./registry"; + +const client = createClient("http://localhost:8080"); + // Bad: creating an actor for each API request app.post("/process", async (req) => { const actor = client.processor.getOrCreate(crypto.randomUUID()); @@ -331,3 +493,9 @@ app.post("/process", async (req) => { **Solution:** Use actors for entities that persist (users, sessions, documents), not for one-off operations. For stateless request handling, use regular functions. +## API Reference + +- [`ActorDefinition`](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) - Interface for pattern examples +- [`ActorContext`](/typedoc/interfaces/rivetkit.mod.ActorContext.html) - Context usage patterns +- [`ActionContext`](/typedoc/interfaces/rivetkit.mod.ActionContext.html) - Action patterns + diff --git a/website/src/content/docs/actors/destroy.mdx b/website/src/content/docs/actors/destroy.mdx index c103c1ad19..7a05067820 100644 --- a/website/src/content/docs/actors/destroy.mdx +++ b/website/src/content/docs/actors/destroy.mdx @@ -106,3 +106,8 @@ const userActor = actor({ Once an actor is destroyed, any subsequent requests to it will return an `actor_not_found` error. The actor's state is permanently deleted. +## API Reference + +- [`ActorHandle`](/typedoc/types/rivetkit.client_mod.ActorHandle.html) - Has destroy methods +- [`ActorContext`](/typedoc/interfaces/rivetkit.mod.ActorContext.html) - Context during destruction + diff --git a/website/src/content/docs/actors/errors.mdx b/website/src/content/docs/actors/errors.mdx index fdf83d2a53..e8b9b2d006 100644 --- a/website/src/content/docs/actors/errors.mdx +++ b/website/src/content/docs/actors/errors.mdx @@ -1,4 +1,336 @@ # Errors -TODO +Rivet provides robust error handling with security built in by default. Errors are handled differently based on whether they should be exposed to clients or kept private. + +There are two types of errors: + +- **UserError**: Thrown from actors and safely returned to clients with full details +- **Internal errors**: All other errors that are converted to a generic error message for security + +## Throwing and Catching Errors + +`UserError` lets you throw custom errors that will be safely returned to the client. + +Throw a `UserError` with just a message: + + + + +```typescript +import { actor, UserError } from "rivetkit"; + +const user = actor({ + state: { username: "" }, + actions: { + updateUsername: (c, username: string) => { + // Validate username + if (username.length > 32) { + throw new UserError("Username is too long"); + } + + // Update username + c.state.username = username; + } + } +}); +``` + + + + +```typescript +import { createClient, ActorError } from "rivetkit/client"; +import type { registry } from "./src/index"; + +const client = createClient("http://localhost:8080"); +const conn = client.user.getOrCreate().connect(); + +try { + await conn.updateUsername("extremely_long_username_that_exceeds_the_limit"); +} catch (error) { + if (error instanceof ActorError) { + console.log(error.message); // "Username is too long" + } +} +``` + + + + +```typescript +import { createClient, ActorError } from "rivetkit/client"; +import type { registry } from "./src/index"; + +const client = createClient("http://localhost:8080"); +const userActor = client.user.getOrCreate(); + +try { + await userActor.updateUsername("extremely_long_username_that_exceeds_the_limit"); +} catch (error) { + if (error instanceof ActorError) { + console.log(error.message); // "Username is too long" + } +} +``` + + + + +## Error Codes + +Use error codes for explicit error matching in try-catch blocks: + + + + +```typescript +import { actor, UserError } from "rivetkit"; + +const user = actor({ + state: { username: "" }, + actions: { + updateUsername: (c, username: string) => { + if (username.length < 3) { + throw new UserError("Username is too short", { + code: "username_too_short" + }); + } + + if (username.length > 32) { + throw new UserError("Username is too long", { + code: "username_too_long" + }); + } + + // Update username + c.state.username = username; + } + } +}); +``` + + + + +```typescript +import { createClient, ActorError } from "rivetkit/client"; +import type { registry } from "./src/index"; + +const client = createClient("http://localhost:8080"); +const conn = client.user.getOrCreate().connect(); + +try { + await conn.updateUsername("ab"); +} catch (error) { + if (error instanceof ActorError) { + if (error.code === "username_too_short") { + console.log("Please choose a longer username"); + } else if (error.code === "username_too_long") { + console.log("Please choose a shorter username"); + } + } +} +``` + + + + +```typescript +import { createClient, ActorError } from "rivetkit/client"; +import type { registry } from "./src/index"; + +const client = createClient("http://localhost:8080"); +const userActor = client.user.getOrCreate(); + +try { + await userActor.updateUsername("ab"); +} catch (error) { + if (error instanceof ActorError) { + if (error.code === "username_too_short") { + console.log("Please choose a longer username"); + } else if (error.code === "username_too_long") { + console.log("Please choose a shorter username"); + } + } +} +``` + + + + +## Errors With Metadata + +Include metadata to provide additional context for rich error handling: + + + + +```typescript +import { actor, UserError } from "rivetkit"; + +const api = actor({ + state: { requestCount: 0, lastReset: Date.now() }, + actions: { + makeRequest: (c) => { + c.state.requestCount++; + + const limit = 100; + if (c.state.requestCount > limit) { + const resetAt = c.state.lastReset + 60_000; // Reset after 1 minute + + throw new UserError("Rate limit exceeded", { + code: "rate_limited", + metadata: { + limit: limit, + resetAt: resetAt, + retryAfter: Math.ceil((resetAt - Date.now()) / 1000) + } + }); + } + + // Rest of request logic... + } + } +}); +``` + + + + +```typescript +import { createClient, ActorError } from "rivetkit/client"; +import type { registry } from "./src/index"; + +const client = createClient("http://localhost:8080"); +const conn = client.api.getOrCreate().connect(); + +try { + await conn.makeRequest(); +} catch (error) { + if (error instanceof ActorError) { + console.log(error.message); // "Rate limit exceeded" + console.log(error.code); // "rate_limited" + console.log(error.metadata); // { limit: 100, resetAt: 1234567890, retryAfter: 45 } + + if (error.code === "rate_limited") { + console.log(`Rate limit hit. Try again in ${error.metadata.retryAfter} seconds`); + } + } +} +``` + + + + +```typescript +import { createClient, ActorError } from "rivetkit/client"; +import type { registry } from "./src/index"; + +const client = createClient("http://localhost:8080"); +const apiActor = client.api.getOrCreate(); + +try { + await apiActor.makeRequest(); +} catch (error) { + if (error instanceof ActorError) { + console.log(error.message); // "Rate limit exceeded" + console.log(error.code); // "rate_limited" + console.log(error.metadata); // { limit: 100, resetAt: 1234567890, retryAfter: 45 } + + if (error.code === "rate_limited") { + console.log(`Rate limit hit. Try again in ${error.metadata.retryAfter} seconds`); + } + } +} +``` + + + + +## Internal Errors + +All errors that are not UserError instances are automatically converted to a generic "internal error" response. This prevents accidentally leaking sensitive information like stack traces, database details, or internal system information. + + + + +```typescript +import { actor } from "rivetkit"; + +const payment = actor({ + state: { transactions: [] }, + actions: { + processPayment: async (c, amount: number) => { + // This will throw a regular Error (not UserError) + const result = await fetch("https://payment-api.example.com/charge", { + method: "POST", + body: JSON.stringify({ amount }) + }); + + if (!result.ok) { + // This internal error will be hidden from the client + throw new Error(`Payment API returned ${result.status}: ${await result.text()}`); + } + + // Rest of payment logic... + } + } +}); +``` + + + + +```typescript +import { createClient, ActorError } from "rivetkit/client"; +import type { registry } from "./src/index"; + +const client = createClient("http://localhost:8080"); +const conn = client.payment.getOrCreate().connect(); + +try { + await conn.processPayment(100); +} catch (error) { + if (error instanceof ActorError) { + console.log(error.code); // "internal_error" + console.log(error.message); // "Internal error. Read the server logs for more details." + + // Original error details are NOT exposed to the client + // Check your server logs to see the actual error message + } +} +``` + + + + +```typescript +import { createClient, ActorError } from "rivetkit/client"; +import type { registry } from "./src/index"; + +const client = createClient("http://localhost:8080"); +const paymentActor = client.payment.getOrCreate(); + +try { + await paymentActor.processPayment(100); +} catch (error) { + if (error instanceof ActorError) { + console.log(error.code); // "internal_error" + console.log(error.message); // "Internal error. Read the server logs for more details." + + // Original error details are NOT exposed to the client + // Check your server logs to see the actual error message + } +} +``` + + + + +The original error message and stack trace are logged server-side for debugging. Check your server logs to see the full error details. + +## API Reference + +- [`UserError`](/typedoc/classes/rivetkit.actor_errors.UserError.html) - User-facing error class +- [`ActorError`](/typedoc/classes/rivetkit.client_mod.ActorError.html) - Errors received by the client diff --git a/website/src/content/docs/actors/events.mdx b/website/src/content/docs/actors/events.mdx index 9f1ab8d388..8847ff50b4 100644 --- a/website/src/content/docs/actors/events.mdx +++ b/website/src/content/docs/actors/events.mdx @@ -270,3 +270,13 @@ function ConditionalListener() { For more details on actor connections, including connection lifecycle, authentication, and advanced connection patterns, see the [Connections documentation](/docs/actors/connections). +## API Reference + +- [`RivetEvent`](/typedoc/interfaces/rivetkit.mod.RivetEvent.html) - Base event interface +- [`RivetMessageEvent`](/typedoc/interfaces/rivetkit.mod.RivetMessageEvent.html) - Message event type +- [`RivetCloseEvent`](/typedoc/interfaces/rivetkit.mod.RivetCloseEvent.html) - Close event type +- [`UniversalEvent`](/typedoc/interfaces/rivetkit.mod.UniversalEvent.html) - Universal event type +- [`UniversalMessageEvent`](/typedoc/interfaces/rivetkit.mod.UniversalMessageEvent.html) - Universal message event +- [`UniversalErrorEvent`](/typedoc/interfaces/rivetkit.mod.UniversalErrorEvent.html) - Universal error event +- [`EventUnsubscribe`](/typedoc/types/rivetkit.client_mod.EventUnsubscribe.html) - Unsubscribe function type + diff --git a/website/src/content/docs/actors/external-sql.mdx b/website/src/content/docs/actors/external-sql.mdx index e4a33e6ab2..ac14e1cdd9 100644 --- a/website/src/content/docs/actors/external-sql.mdx +++ b/website/src/content/docs/actors/external-sql.mdx @@ -62,26 +62,29 @@ export const userActor = actor({ lastActive: Date.now() }), - // Insert user into database when actor starts - onWake: async (c, opts) => { + // Insert user into database when actor creates + onCreate: async (c, opts) => { const result = await pool.query( "INSERT INTO users (username, email, created_at) VALUES ($1, $2, $3)", [c.state.username, c.state.email, c.state.lastActive] ); }, + // Sync state changes to database + onStateChange: async (c, newState) => { + await pool.query( + "UPDATE users SET email = $1, last_active = $2 WHERE username = $3", + [newState.email, newState.lastActive, newState.username] + ); + }, + actions: { - // Update user information + // Update user information, this will trigger onStateChange updateUser: async (c, email: string) => { c.state.requestCount++; c.state.email = email; c.state.lastActive = Date.now(); - - await pool.query( - "UPDATE users SET email = $1 WHERE username = $3", - [email, c.state.username] - ); - + return { requestCount: c.state.requestCount }; }, @@ -176,8 +179,8 @@ export const userActor = actor({ lastActive: Date.now() }), - // Insert user into database when actor starts - onWake: async (c, opts) => { + // Insert user into database when actor creates + onCreate: async (c, opts) => { const result = await db.insert(users).values({ username: c.state.username, email: c.state.email, @@ -185,19 +188,23 @@ export const userActor = actor({ }); }, + // Sync state changes to database + onStateChange: async (c, newState) => { + await db.update(users) + .set({ + email: newState.email, + lastActive: new Date(newState.lastActive) + }) + .where(eq(users.username, newState.username)); + }, + actions: { - // Update user information + // Update user information, this will trigger onStateChange updateUser: async (c, email: string) => { c.state.requestCount++; c.state.email = email; c.state.lastActive = Date.now(); - - await db.update(users) - .set({ - email - }) - .where(eq(users.username, c.state.username)); - + return { requestCount: c.state.requestCount }; }, diff --git a/website/src/content/docs/actors/helper-types.mdx b/website/src/content/docs/actors/helper-types.mdx index 68e877128f..e049bf83fd 100644 --- a/website/src/content/docs/actors/helper-types.mdx +++ b/website/src/content/docs/actors/helper-types.mdx @@ -58,3 +58,16 @@ function processCounterAction(context: ActionContextOf) { context.state.count++; } ``` + +## API Reference + +- [`ActorContextOf`](/typedoc/types/rivetkit.mod.ActorContextOf.html) - Extract context from actor +- [`ActionContextOf`](/typedoc/types/rivetkit.mod.ActionContextOf.html) - Extract context from action +- [`ActorConfig`](/typedoc/types/rivetkit.mod.ActorConfig.html) - Actor configuration type +- [`ActorConfigInput`](/typedoc/types/rivetkit.mod.ActorConfigInput.html) - Actor configuration input +- [`RegistryActors`](/typedoc/types/rivetkit.mod.RegistryActors.html) - Extract actors from registry +- [`ExtractActorsFromRegistry`](/typedoc/types/rivetkit.client_mod.ExtractActorsFromRegistry.html) - Utility type +- [`ExtractRegistryFromClient`](/typedoc/types/rivetkit.client_mod.ExtractRegistryFromClient.html) - Utility type +- [`AnyActorDefinition`](/typedoc/types/rivetkit.mod.AnyActorDefinition.html) - Any actor definition type +- [`AnyActorInstance`](/typedoc/types/rivetkit.mod.AnyActorInstance.html) - Any actor instance type +- [`AnyClient`](/typedoc/types/rivetkit.mod.AnyClient.html) - Any client type diff --git a/website/src/content/docs/actors/input.mdx b/website/src/content/docs/actors/input.mdx index 13fa613f89..44bbb16a0a 100644 --- a/website/src/content/docs/actors/input.mdx +++ b/website/src/content/docs/actors/input.mdx @@ -185,3 +185,9 @@ const game = actor({ }, }); ``` + +## API Reference + +- [`CreateOptions`](/typedoc/interfaces/rivetkit.client_mod.CreateOptions.html) - Options for creating actors +- [`CreateRequest`](/typedoc/types/rivetkit.client_mod.CreateRequest.html) - Request type for creation +- [`ActorDefinition`](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) - Interface for defining input types diff --git a/website/src/content/docs/actors/keys.mdx b/website/src/content/docs/actors/keys.mdx index c21fc06091..ceac4c8c3a 100644 --- a/website/src/content/docs/actors/keys.mdx +++ b/website/src/content/docs/actors/keys.mdx @@ -163,3 +163,10 @@ const chatRoom = await client.chatRoom.create(["room", roomName], { }); ``` +## API Reference + +- [`ActorKey`](/typedoc/types/rivetkit.mod.ActorKey.html) - Key type for actors +- [`ActorQuery`](/typedoc/types/rivetkit.mod.ActorQuery.html) - Query type using keys +- [`GetOptions`](/typedoc/interfaces/rivetkit.client_mod.GetOptions.html) - Options for getting by key +- [`QueryOptions`](/typedoc/interfaces/rivetkit.client_mod.QueryOptions.html) - Options for querying + diff --git a/website/src/content/docs/actors/lifecycle.mdx b/website/src/content/docs/actors/lifecycle.mdx index bef518b292..0cbf548918 100644 --- a/website/src/content/docs/actors/lifecycle.mdx +++ b/website/src/content/docs/actors/lifecycle.mdx @@ -1,42 +1,40 @@ # Lifecycle -Understand actor lifecycle hooks and initialization patterns - Actors follow a well-defined lifecycle with hooks at each stage. Understanding these hooks is essential for proper initialization, state management, and cleanup. ## Lifecycle Actors transition through several states during their lifetime. Each transition triggers specific hooks that let you initialize resources, manage connections, and clean up state. -**On Create** (first time only) +**On Create** (runs once per actor) -- `createState` -- `onCreate` -- `createVars` -- `onWake` +1. `createState` +2. `onCreate` +3. `createVars` +4. `onWake` -**On Wake** (after sleep, restart, or crash) +**On Destroy** -- `createVars` -- `onWake` +1. `onDestroy` -**On Sleep** +**On Wake** (after sleep, restart, or crash) -- `onSleep` +1. `createVars` +2. `onWake` -**On Destroy** +**On Sleep** (after idle period) -- `onDestroy` +1. `onSleep` **On Connect** (per client) -- `onBeforeConnect` -- `createConnState` -- `onConnect` +1. `onBeforeConnect` +2. `createConnState` +3. `onConnect` **On Disconnect** (per client) -- `onDisconnect` +1. `onDisconnect` ## Lifecycle Hooks @@ -57,7 +55,7 @@ const counter = actor({ ### `createState` -`createState(c: ActorContext, input: TInput): TState | Promise` +[API Reference](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) The `createState` function dynamically initializes state based on input. Called only once when the actor is first created. Can be async. See [state documentation](/docs/actors/state) for more information. @@ -88,7 +86,7 @@ const counter = actor({ ### `createVars` -`createVars(c: InitContext, driverCtx: any): TVars | Promise` +[API Reference](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) The `createVars` function dynamically initializes ephemeral variables. Can be async. Use this when you need to initialize values at runtime. The `driverCtx` parameter provides driver-specific context. See [ephemeral variables documentation](/docs/actors/state#ephemeral-variables-vars) for more information. @@ -112,7 +110,7 @@ const counter = actor({ ### `onCreate` -`onCreate(c: ActorContext, input: TInput): void | Promise` +[API Reference](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) The `onCreate` hook is called when the actor is first created. Can be async. Use this hook for initialization logic that doesn't affect the initial state. @@ -132,7 +130,7 @@ const counter = actor({ ### `onDestroy` -`onDestroy(c: ActorContext): void | Promise` +[API Reference](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) The `onDestroy` hook is called when the actor is being permanently destroyed. Can be async. Use this for final cleanup operations like closing external connections, releasing resources, or performing any last-minute state persistence. @@ -149,7 +147,7 @@ const gameSession = actor({ ### `onWake` -`onWake(c: ActorContext): void | Promise` +[API Reference](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) This hook is called any time the actor is started (e.g. after restarting, upgrading code, or crashing). Can be async. @@ -191,11 +189,13 @@ const counter = actor({ ### `onSleep` -`onSleep(c: ActorContext): void | Promise` +[API Reference](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) This hook is called when the actor is going to sleep. Can be async. Use this to clean up resources, close connections, or perform any shutdown operations. -Not supported on all platforms. +This hook may not always be called in situations like crashes or forced terminations. Don't rely on it for critical cleanup operations. + +Not supported on Cloudflare Workers. ```typescript import { actor } from "rivetkit"; @@ -231,7 +231,7 @@ const counter = actor({ ### `onStateChange` -`onStateChange(c: ActorContext, newState: TState): void` +[API Reference](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) Called whenever the actor's state changes. Cannot be async. This is often used to broadcast state updates. @@ -259,7 +259,7 @@ const counter = actor({ ### `createConnState` and `connState` -`createConnState(c: CreateConnStateContext, params: TConnParams): TConnState | Promise` +[API Reference](/typedoc/interfaces/rivetkit.mod.ActorDefinition.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 @@ -267,7 +267,7 @@ There are two ways to define the initial state for connections: ### `onBeforeConnect` -`onBeforeConnect(c: OnBeforeConnectContext, params: TConnParams): void | Promise` +[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. @@ -314,7 +314,7 @@ Connections cannot interact with the actor until this method completes successfu ### `onConnect` -`onConnect(c: OnConnectContext, conn: Conn): void | Promise` +[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. @@ -346,7 +346,7 @@ Messages will not be processed for this actor until this hook succeeds. Errors t ### `onDisconnect` -`onDisconnect(c: ActorContext, conn: Conn): void | Promise` +[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. @@ -376,7 +376,7 @@ const chatRoom = actor({ ### `onRequest` -`onRequest(c: RequestContext, request: Request): Response | Promise` +[API Reference](/typedoc/interfaces/rivetkit.mod.RequestContext.html) The `onRequest` hook handles HTTP requests sent to your actor at `/actors/{actorName}/http/*` endpoints. Can be async. It receives the request context and a standard `Request` object, and should return a `Response` object or `void` to continue default routing. @@ -411,7 +411,7 @@ const apiActor = actor({ ### `onWebSocket` -`onWebSocket(c: ActorContext, websocket: WebSocket): void | Promise` +[API Reference](/typedoc/interfaces/rivetkit.mod.WebSocketContext.html) The `onWebSocket` hook handles WebSocket connections to your actor. Can be async. It receives the actor context and a `WebSocket` object. Use this to set up WebSocket event listeners and handle real-time communication. @@ -456,7 +456,7 @@ const realtimeActor = actor({ ### `onBeforeActionResponse` -`onBeforeActionResponse(c: ActorContext, name: string, args: unknown[], output: TOutput): TOutput | Promise` +[API Reference](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) The `onBeforeActionResponse` hook is called before sending an action response to the client. Can be async. Use this hook to modify or transform the output of an action before it's sent to the client. This is useful for formatting responses, adding metadata, or applying transformations to the output. @@ -470,7 +470,7 @@ const loggingActor = actor({ // Log action calls console.log(`Action ${actionName} called with args:`, args); console.log(`Action ${actionName} returned:`, output); - + // Add metadata to all responses return { data: output, @@ -478,7 +478,7 @@ const loggingActor = actor({ actionName, timestamp: Date.now(), requestId: crypto.randomUUID(), - userId: c.conn.auth?.userId + userId: c.conn.state?.userId } }; }, diff --git a/website/src/content/docs/actors/metadata.mdx b/website/src/content/docs/actors/metadata.mdx index 63962742d5..763f709eac 100644 --- a/website/src/content/docs/actors/metadata.mdx +++ b/website/src/content/docs/actors/metadata.mdx @@ -87,3 +87,8 @@ console.log("Actor metadata:", metadata); ``` + +## API Reference + +- [`ActorDefinition`](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) - Interface for defining metadata +- [`CreateOptions`](/typedoc/interfaces/rivetkit.client_mod.CreateOptions.html) - Includes metadata options diff --git a/website/src/content/docs/actors/request-handler.mdx b/website/src/content/docs/actors/request-handler.mdx index 31ea6b1b9b..34f759c487 100644 --- a/website/src/content/docs/actors/request-handler.mdx +++ b/website/src/content/docs/actors/request-handler.mdx @@ -171,6 +171,11 @@ The `onRequest` handler is WinterTC compliant and will work with existing librar ## Limitations -- Does not support streaming responses & server-sent events at the moment. See the [tracking issue](https://github.com/rivet-dev/rivet/issues/3529). +- Does not support streaming responses & server-sent events at the moment. See the [tracking issue](https://github.com/rivet-dev/rivet/issues/3529). - `OPTIONS` requests currently are handled by Rivet and are not passed to `onRequest` +## API Reference + +- [`RequestContext`](/typedoc/interfaces/rivetkit.mod.RequestContext.html) - Context for HTTP request handlers +- [`ActorDefinition`](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) - Interface for defining request handlers + diff --git a/website/src/content/docs/actors/state.mdx b/website/src/content/docs/actors/state.mdx index baa1414342..be1111df56 100644 --- a/website/src/content/docs/actors/state.mdx +++ b/website/src/content/docs/actors/state.mdx @@ -190,3 +190,8 @@ State is currently constrained to the following types: - `Array` - Plain objects +## API Reference + +- [`InitContext`](/typedoc/types/rivetkit.mod.InitContext.html) - Context available during actor initialization +- [`ActorContext`](/typedoc/interfaces/rivetkit.mod.ActorContext.html) - Context available throughout actor lifecycle +- [`ActorDefinition`](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) - Interface for defining actors with state diff --git a/website/src/content/docs/actors/testing.mdx b/website/src/content/docs/actors/testing.mdx index 50fee27160..830f0e7d89 100644 --- a/website/src/content/docs/actors/testing.mdx +++ b/website/src/content/docs/actors/testing.mdx @@ -230,3 +230,9 @@ The `setupTest` function automatically calls `vi.useFakeTimers()`, allowing you 4. **Use realistic data**: Test with data that resembles production scenarios. Rivet's testing framework automatically handles server setup and teardown, so you can focus on writing effective tests for your business logic. + +## API Reference + +- [`test`](/typedoc/functions/rivetkit.mod.test.html) - Test helper function +- [`createMemoryDriver`](/typedoc/functions/rivetkit.mod.createMemoryDriver.html) - In-memory driver for tests +- [`createFileSystemDriver`](/typedoc/functions/rivetkit.mod.createFileSystemDriver.html) - Filesystem driver for tests diff --git a/website/src/content/docs/actors/websocket-handler.mdx b/website/src/content/docs/actors/websocket-handler.mdx index 6989ccc2b8..d3a2815217 100644 --- a/website/src/content/docs/actors/websocket-handler.mdx +++ b/website/src/content/docs/actors/websocket-handler.mdx @@ -215,3 +215,10 @@ onWebSocket: async (c, websocket) => { }); } ``` + +## API Reference + +- [`WebSocketContext`](/typedoc/interfaces/rivetkit.mod.WebSocketContext.html) - Context for WebSocket handlers +- [`UniversalWebSocket`](/typedoc/interfaces/rivetkit.mod.UniversalWebSocket.html) - Universal WebSocket interface +- [`handleRawWebSocketHandler`](/typedoc/functions/rivetkit.mod.handleRawWebSocketHandler.html) - Function to handle raw WebSocket +- [`UpgradeWebSocketArgs`](/typedoc/interfaces/rivetkit.mod.UpgradeWebSocketArgs.html) - Arguments for WebSocket upgrade diff --git a/website/src/content/docs/clients/javascript.mdx b/website/src/content/docs/clients/javascript.mdx index 602a15e7d2..35bd579658 100644 --- a/website/src/content/docs/clients/javascript.mdx +++ b/website/src/content/docs/clients/javascript.mdx @@ -11,5 +11,12 @@ See the [backend quickstart guide](/docs/actors/quickstart/backend) for getting ## API Reference -See the [RivetKit client API](http://rivet.dev/docs/actors/clients/#actor-client). +**Package:** [@rivetkit/client](https://www.npmjs.com/package/@rivetkit/client) + +See the [RivetKit client API](/docs/actors/clients/#actor-client). + +- [`createClient`](/typedoc/functions/rivetkit.client_mod.createClient.html) - Create a client +- [`createEngineDriver`](/typedoc/functions/rivetkit.mod.createEngineDriver.html) - Engine driver +- [`DriverConfig`](/typedoc/types/rivetkit.mod.DriverConfig.html) - Driver configuration +- [`Client`](/typedoc/types/rivetkit.mod.Client.html) - Client type diff --git a/website/src/content/docs/clients/next-js.mdx b/website/src/content/docs/clients/next-js.mdx index 202823da1f..e5f85dabbe 100644 --- a/website/src/content/docs/clients/next-js.mdx +++ b/website/src/content/docs/clients/next-js.mdx @@ -20,4 +20,15 @@ See the [Next.js quickstart guide](/docs/actors/quickstart/next-js) for getting ## API Reference -It's the same as the [React client API reference](https://rivet.dev/docs/clients/react#api-reference), since the RivetKit React package is used in both Next.js and React applications. +**Package:** [@rivetkit/next-js](https://www.npmjs.com/package/@rivetkit/next-js) + +See the full Next.js API documentation at [rivetkit.org/docs/actors/clients](https://rivetkit.org/docs/actors/clients). + +The Next.js client uses the same hooks as the React client: + +- [`RivetKitProvider`](https://rivetkit.org/docs/actors/clients/#react-provider) - React context provider +- [`useActor`](https://rivetkit.org/docs/actors/clients/#useactor) - React hook for actor instances +- [`createClient`](/typedoc/functions/rivetkit.client_mod.createClient.html) - Create a client +- [`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 diff --git a/website/src/content/docs/clients/react.mdx b/website/src/content/docs/clients/react.mdx index 79e5dec336..e874e1f26c 100644 --- a/website/src/content/docs/clients/react.mdx +++ b/website/src/content/docs/clients/react.mdx @@ -238,3 +238,16 @@ function AuthenticatedApp() { Learn more about [authentication](/docs/actors/authentication). +## API Reference + +**Package:** [@rivetkit/react](https://www.npmjs.com/package/@rivetkit/react) + +See the full React API documentation at [rivetkit.org/docs/actors/clients](https://rivetkit.org/docs/actors/clients). + +- [`RivetKitProvider`](https://rivetkit.org/docs/actors/clients/#react-provider) - React context provider +- [`useActor`](https://rivetkit.org/docs/actors/clients/#useactor) - React hook for actor instances +- [`createClient`](/typedoc/functions/rivetkit.client_mod.createClient.html) - Create a client +- [`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 + diff --git a/website/src/content/docs/cloud/index.mdx b/website/src/content/docs/cloud/index.mdx deleted file mode 100644 index 9eba6e7025..0000000000 --- a/website/src/content/docs/cloud/index.mdx +++ /dev/null @@ -1,16 +0,0 @@ -# Rivet Cloud - -Rivet Cloud provides the fastest way to deploy and scale your Rivet Actors. - -## Dashboard - -Rivet Cloud's dashboard can be accessed at [dashboard.rivet.dev](https://dashboard.rivet.dev). - -## Features - -- **Deploy In 60 Seconds**: Rivet streamlines getting your application deployed -- **Edge Network**: Provides a global edge network to deploy your actors -- **Works With Your Cloud**: Keep deploying your backend where you're comfortable, Rivet Cloud works with your stack -- **No Vendor Lock**: Rivet is open-source (Apache 2.0) and does not vendor-lock your application -- **Organizations & Security**: Provides all of the tools you'd expect for managing organizations & security - diff --git a/website/src/content/docs/drivers/build-your-own.mdx b/website/src/content/docs/drivers/build-your-own.mdx deleted file mode 100644 index 420a0ca73f..0000000000 --- a/website/src/content/docs/drivers/build-your-own.mdx +++ /dev/null @@ -1,17 +0,0 @@ -# Build Your Own - -Each driver implements common interfaces defined by RivetKit, including: - -- **ActorDriver**: Manages actor state, lifecycle, and persistence -- **ManagerDriver**: Handles actor discovery, routing, and scaling - -## Source Code Locations - -Get started by looking at source code for the driver interfaces and existing drivers: - -- **Driver Interfaces** - - **ActorDriver*** [Source Code](https://github.com/rivet-dev/rivetkit/blob/main/packages/core/src/actor/driver.ts) - - **ManagerDriver*** [Source Code](https://github.com/rivet-dev/rivetkit/blob/main/packages/core/src/manager/driver.ts) -- **Driver Implementations**: [Source Code](https://github.com/rivet-dev/rivetkit/tree/main/packages/core/src/drivers) - - diff --git a/website/src/content/docs/drivers/file-system.mdx b/website/src/content/docs/drivers/file-system.mdx deleted file mode 100644 index bcccaf5bad..0000000000 --- a/website/src/content/docs/drivers/file-system.mdx +++ /dev/null @@ -1,90 +0,0 @@ -# File System - -The File System driver is the default driver for Rivet Actors, providing local file-based storage for state management and inter-actor communication. If no driver is specified in your configuration, the File System driver will be used automatically. - - -The File System driver is ideal for development and single-node deployments. For production applications that need to scale horizontally across multiple machines, use the [Redis driver](/docs/drivers/redis). - - -## Feature Support - -| Feature | Supported | -| --- | --- | -| Horizontal scaling | No | -| WebSockets | Yes | -| Edge | No | -| Scheduling | Yes | - -## Setup - - - - -The File System driver is included with `rivetkit` - no additional packages needed: - -```bash -npm install rivetkit -``` - - - - - - - - -Use the default configuration with automatic path based on your current working directory: - -```typescript {{"title":"server.ts"}} -import { createFileSystemDriver } from "rivetkit"; -import { registry } from "./registry"; - -const driver = createFileSystemDriver(); -const { client } = registry.start({ driver }); - -// ...rest of your server... -``` - -The default path is stored in your system's data path. The path is unique to the current working directory, so you can safely run multiple projects on the same machine. - - - - - -Specify a custom path for actor storage: - -```typescript {{"title":"server.ts"}} -import { createFileSystemDriver } from "rivetkit"; -import { registry } from "./registry"; - -const driver = createFileSystemDriver({ - path: "/custom/path/to/actors" -}); -const { client } = registry.start({ driver }); - -// ...rest of your server... -``` - -**Configuration Options:** - -- `path` - Custom directory path for storing actor data (optional) - - - - - - - -## Data Management - -The path where your actors are stored is printed when you start your project. To reset your actors, delete the folder that is printed. - -If running on a single node, make sure to back up your actors folder regularly. `rsync` works nicely with this because each actor is stored as its own file. - -## Examples - - - -Basic File System driver setup and configuration example. - - diff --git a/website/src/content/docs/drivers/memory.mdx b/website/src/content/docs/drivers/memory.mdx deleted file mode 100644 index 26307c8fcd..0000000000 --- a/website/src/content/docs/drivers/memory.mdx +++ /dev/null @@ -1,49 +0,0 @@ -# Memory - -The Memory driver stores all actor state and communication in memory, making it ideal for testing, development, and prototyping scenarios where persistence is not required. - - -The Memory driver does not persist data between server restarts. For production applications that need to scale horizontally across multiple machines, see [self-hosting](/docs/self-hosting) - - -## Feature Support - -| Feature | Supported | -| --- | --- | -| Horizontal scaling | No | -| WebSockets | Yes | -| Edge | No | -| Scheduling | Yes | - -## Setup - - - - -The Memory driver is included with `rivetkit` - no additional packages needed: - -```bash -npm install rivetkit -``` - - - - - -Create and use the Memory driver: - -```typescript {{"title":"server.ts"}} -import { createMemoryDriver } from "rivetkit"; -import { registry } from "./registry"; - -const driver = createMemoryDriver(); -const { client } = registry.start({ driver }); - -// ...rest of your server... -``` - -The Memory driver requires no configuration options. - - - - diff --git a/website/src/content/docs/examples/autofill-example.mdx b/website/src/content/docs/examples/autofill-example.mdx deleted file mode 100644 index 2ddafeb4d8..0000000000 --- a/website/src/content/docs/examples/autofill-example.mdx +++ /dev/null @@ -1,75 +0,0 @@ -# Code Autofill Example - -This page demonstrates the code autofill feature with template variables. - -## Basic Example - -The following code snippet uses autofill to populate your project and namespace information. Click on any blue highlighted value to change your selection: - -```ts {{"autofill": true}} -import { createClient } from "rivetkit"; - -const client = createClient({ - token: "{{namespace.token}}", - endpoint: "{{engine.url}}" -}); - -console.log("Connected to project: {{project.slug}}"); -console.log("Using namespace: {{namespace.slug}}"); -``` - -## Using Default Values - -You can specify default values that will be shown before the user selects a project. Format: `{{variable.name:"default-value"}}` - -```ts {{"autofill": true}} -import { createClient } from "rivetkit"; - -const client = createClient({ - // These will show default values until user selects a project - token: "{{namespace.token:'YOUR_TOKEN_HERE'}}", - endpoint: "{{engine.url:'https://engine.rivet.dev'}}" -}); - -const config = { - project: "{{project.slug:'my-project'}}", - namespace: "{{namespace.slug:'production'}}", -}; -``` - -## Multiple Template Variables - -You can use various template variables in your code: - -```ts {{"autofill": true}} -const config = { - // Organization info - organization: "{{organization.slug}}", - - // Project info - project: { - slug: "{{project.slug}}", - name: "{{project.name}}", - }, - - // Namespace info - namespace: { - slug: "{{namespace.slug}}", - name: "{{namespace.name}}", - token: "{{namespace.token}}", - }, - - // Connection - engineUrl: "{{engine.url}}", -}; -``` - -## Regular Code Block (No Autofill) - -This code block doesn't use autofill: - -```ts -// This is a regular code block without autofill -const greeting = "Hello, World!"; -console.log(greeting); -``` diff --git a/website/src/content/docs/general/cors.mdx b/website/src/content/docs/general/cors.mdx index 7fb8779003..88479175db 100644 --- a/website/src/content/docs/general/cors.mdx +++ b/website/src/content/docs/general/cors.mdx @@ -1,233 +1,66 @@ # Cross-Origin Resource Sharing -Cross-Origin Resource Sharing (CORS) is a security mechanism that allows a web application running at one origin to access resources from a different origin. Without CORS, browsers block cross-origin HTTP requests by default as a security measure. +Cross-Origin Resource Sharing (CORS) controls which origins (domains) can access your actors. When actors are exposed to the public internet, proper origin validation is critical to prevent security breaches and denial of service attacks. -You'll need to configure CORS when: +Unlike stateless HTTP APIs that use CORS headers, Rivet Actors are stateful and support persistent WebSocket connections. Since WebSockets don't natively support CORS, we validate origins manually in the `onBeforeConnect` hook before connections may open. -- **Local Development**: You're developing locally and your client runs on a different port than your actor service -- **Different Domain**: Your frontend application is hosted on a different domain than your actor service +## Implementing Origin Restrictions -## Registry-Level CORS - -Configure CORS directly in your registry setup for applications that do not require configuring CORS on their own endpoints. +To implement origin restrictions on Rivet Actors, use the `onBeforeConnect` hook to verify the request. ```typescript {{"title":"server.ts"}} -import { registry } from "./registry"; - -const { client } = registry.start({ - cors: { - origin: "https://yourdomain.com", - } +import { actor, UserError } from "rivetkit"; + +const ALLOWED_ORIGINS = [ + "http://localhost:3000", + "https://myapp.com", + "https://www.myapp.com" +]; + +const myActor = actor({ + state: { count: 0 }, + + onBeforeConnect: (c, params) => { + // Check if origin is allowed + // + // This works for both HTTP & WebSocket requests + const origin = c.request?.headers.get("origin"); + if (!ALLOWED_ORIGINS.includes(origin)) { + throw new UserError("Origin not allowed", { code: "origin_not_allowed" }); + } + }, + + actions: { + increment: (c) => { + c.state.count++; + return c.state.count; + } + } }); ``` -### Configuration Options - -#### `origin` +To catch the error on the client, use the following code: -`string | string[] | (origin: string) => boolean | string` +```typescript {{"title":"client.ts"}} +import { createClient, ActorError } from "rivetkit/client"; +import type { registry } from "./registry"; -Specifies which domains can access your resources: - -```typescript {{"title":"server.ts"}} -// Single domain -origin: "https://example.com" +const client = createClient(); -// Multiple domains -origin: ["https://app.com", "https://admin.com"] +try { + const actor = client.myActor.getOrCreate("my-actor"); + const conn = actor.connect(); -// Dynamic validation -origin: (origin) => { - return origin?.endsWith('.example.com') ? origin : false; + // Connection will be established or error will be thrown + await conn.increment(); +} catch (error) { + if (error instanceof ActorError && error.code === "origin_not_allowed") { + console.error("Connection rejected: Origin not allowed"); + } } - -// All domains (not recommended for production) -origin: "*" -``` - -#### `allowMethods` - -`string[]` - -HTTP methods clients are allowed to use: - -```typescript -allowMethods: ["GET", "POST", "OPTIONS"] // Common for Rivet -``` - -#### `allowHeaders` - -`string[]` - -Headers that clients can send in requests: - -```typescript -allowHeaders: [ - "Authorization", // Your auth headers - "Content-Type", // Standard content type - "X-API-Key", // Custom API key header - ...ALLOWED_PUBLIC_HEADERS // Required Rivet headers -] -``` - -#### `credentials` - -`boolean` - -Whether to allow credentials (cookies, auth headers): - -```typescript -credentials: true // Required for authentication ``` - -When `credentials: true`, you cannot use `origin: "*"`. Specify exact origins instead. - - -#### `maxAge` - -`number` - -How long browsers cache CORS preflight responses (in seconds): - -```typescript -maxAge: 600 // Cache for 10 minutes -``` - -#### `exposeHeaders` - -`string[]` - -Server headers that browsers can access: - -```typescript -exposeHeaders: ["Content-Length", "X-Request-Id"] -``` - -## Router-Level CORS - -For applications that need to expose their own routes, configure CORS at the router level: - - - -```typescript {{"title":"Hono"}} -import { registry } from "./registry"; -import { Hono } from "hono"; -import { cors } from "hono/cors"; -import { serve } from "@hono/node-server"; -import { ALLOWED_PUBLIC_HEADERS } from "rivetkit"; - -registry.start(); - -const app = new Hono(); - -app.use("*", cors({ - origin: ["http://localhost:3000", "https://myapp.com"], - allowHeaders: [ - "Authorization", - "Content-Type", - ...ALLOWED_PUBLIC_HEADERS - ], -})); - -serve(app); -``` - -```typescript {{"title":"Express"}} -import express from "express"; -import cors from "cors"; -import { ALLOWED_PUBLIC_HEADERS } from "rivetkit"; -import { registry } from "./registry"; - -registry.start(); - -const app = express(); - -app.use(cors({ - origin: ["http://localhost:3000", "https://myapp.com"], - allowedHeaders: [ - "Authorization", - "Content-Type", - ...ALLOWED_PUBLIC_HEADERS - ], - credentials: true, -})); - -app.use("/registry", handler); -app.listen(8080); -``` - - - -### Required Headers for Rivet - -Rivet requires specific headers for communication. Always include `ALLOWED_PUBLIC_HEADERS`: - -```typescript {{"title":"server.ts"}} -import { ALLOWED_PUBLIC_HEADERS } from "rivetkit"; - -const corsConfig = { - allowHeaders: [ - "X-Foo-Bar", // Whatever headers you need to configure - ...ALLOWED_PUBLIC_HEADERS // Required Rivet headers - ] -}; -``` - -These are automatically configured if using `registry.start({ cors })`. - - -Without `ALLOWED_PUBLIC_HEADERS`, Rivet clients won't be able to communicate with your actors from the browser. - - -## Development vs Production - -### Development Setup - -For local development, allow localhost origins: - -```typescript -const isDev = process.env.NODE_ENV !== "production"; - -const corsConfig = { - origin: isDev - ? ["http://localhost:3000", "http://localhost:5173"] - : ["https://myapp.com"], - allowHeaders: ["Authorization", ...ALLOWED_PUBLIC_HEADERS], - credentials: true, -}; -``` - -### Production Setup - -For production, be restrictive with origins: - -```typescript -const corsConfig = { - origin: [ - "https://myapp.com", - "https://www.myapp.com", - "https://admin.myapp.com" - ], - allowHeaders: ["Authorization", ...ALLOWED_PUBLIC_HEADERS], - credentials: true, - maxAge: 3600, // Cache for 1 hour -}; -``` - -## Troubleshooting - -### Common CORS Errors - -**"Access to fetch blocked by CORS policy"** -- Add your frontend's origin to the `origin` list -- Ensure `ALLOWED_PUBLIC_HEADERS` are included in `allowHeaders` - -**"Request header not allowed"** -- Add the missing header to `allowHeaders` -- Include `ALLOWED_PUBLIC_HEADERS` in your configuration - -**"Credentials mode mismatch"** -- Set `credentials: true` in CORS config -- Cannot use `origin: "*"` with credentials + + See tracking issue for [configuring CORS per-actor on the gateway](https://github.com/rivet-dev/rivet/issues/3539) that will remove the need to implement origin restrictions in `onBforeRequest`. + diff --git a/website/src/content/docs/general/self-hosting.mdx b/website/src/content/docs/general/self-hosting.mdx deleted file mode 100644 index 14c0dfe710..0000000000 --- a/website/src/content/docs/general/self-hosting.mdx +++ /dev/null @@ -1,18 +0,0 @@ -# Self-Hosting - -Take full control of your Rivet deployment with flexible hosting options and storage drivers. - -## Hosting Providers - - - - Deploy Rivet applications with Railway's simple platform-as-a-service - - - Run Rivet actors on Cloudflare's edge computing platform - - - Managed Rivet hosting with enterprise features and support - - - diff --git a/website/src/content/docs/index.mdx b/website/src/content/docs/index.mdx index 23f9cce89e..fc092d4245 100644 --- a/website/src/content/docs/index.mdx +++ b/website/src/content/docs/index.mdx @@ -1,5 +1,4 @@ import { deployOptions } from "@/data/deploy/shared"; -import { useCases } from "@/data/use-cases"; import { faActorsBorderless, faNodeJs, diff --git a/website/src/sitemap/mod.ts b/website/src/sitemap/mod.ts index 15ee5b045c..898712de61 100644 --- a/website/src/sitemap/mod.ts +++ b/website/src/sitemap/mod.ts @@ -67,7 +67,6 @@ import { } from "@rivet-gg/icons"; import type { DeployOption } from "@/data/deploy/shared"; import { deployGroups, deployOptions } from "@/data/deploy/shared"; -import { useCases } from "@/data/use-cases"; import nextjs from "@/images/vendors/next-js.svg"; import type { SidebarItem, Sitemap } from "@/lib/sitemap"; @@ -123,25 +122,6 @@ export const sitemap = [ }, ], }, - // { - // title: "Use Cases", - // pages: [ - // ...useCases.slice(0, 3).map(({ title, href, icon }) => ({ - // title, - // href, - // icon, - // })), - // { - // title: "More", - // collapsible: true, - // pages: useCases.slice(3).map(({ title, href, icon }) => ({ - // title, - // href, - // icon, - // })), - // }, - // ], - // }, { title: "Concepts", pages: [ @@ -170,38 +150,6 @@ export const sitemap = [ href: "/docs/actors/schedule", icon: faClock, }, - { - title: "Lifecycle & Config", - icon: faSlidersHSquare, - collapsible: true, - pages: [ - { - title: "Lifecycle", - href: "/docs/actors/lifecycle", - //icon: faRotate, - }, - { - title: "Input Parameters", - href: "/docs/actors/input", - //icon: faFileImport, - }, - { - title: "Keys", - href: "/docs/actors/keys", - //icon: faKey, - }, - { - title: "Metadata", - href: "/docs/actors/metadata", - //icon: faTag, - }, - { - title: "Destroying", - href: "/docs/actors/destroy", - //icon: faTag, - }, - ], - }, { title: "Communication", icon: faArrowRightArrowLeft, @@ -236,6 +184,38 @@ export const sitemap = [ }, ], }, + { + title: "Lifecycle & Config", + icon: faSlidersHSquare, + collapsible: true, + pages: [ + { + title: "Lifecycle", + href: "/docs/actors/lifecycle", + //icon: faRotate, + }, + { + title: "Input Parameters", + href: "/docs/actors/input", + //icon: faFileImport, + }, + { + title: "Keys", + href: "/docs/actors/keys", + //icon: faKey, + }, + { + title: "Metadata", + href: "/docs/actors/metadata", + //icon: faTag, + }, + { + title: "Destroying", + href: "/docs/actors/destroy", + //icon: faTag, + }, + ], + }, { title: "Design Patterns", icon: faLayerGroup, @@ -256,6 +236,11 @@ export const sitemap = [ href: "/docs/actors/external-sql", //icon: faDatabase, }, + { + title: "Logging", + href: "/docs/general/logging", + // icon: faListUl, + }, { title: "Errors", href: "/docs/actors/errors" @@ -275,11 +260,6 @@ export const sitemap = [ href: "/docs/general/cors", // icon: faShareNodes, }, - { - title: "Logging", - href: "/docs/general/logging", - // icon: faListUl, - }, ], }, ], @@ -407,17 +387,6 @@ export const sitemap = [ ...deploySidebarSections, ], }, - // { - // title: "Rivet Cloud", - // href: "/docs/cloud", - // sidebar: [ - // { - // title: "Overview", - // href: "/docs/cloud", - // // icon: faSquareInfo, - // }, - // ], - // }, { title: "Self-Hosting", href: "/docs/self-hosting", @@ -457,6 +426,10 @@ export const sitemap = [ title: "Platforms", collapsible: true, pages: [ + { + title: "Railway", + href: "/docs/self-hosting/railway", + }, { title: "Docker Container", href: "/docs/self-hosting/docker-container", @@ -466,8 +439,8 @@ export const sitemap = [ href: "/docs/self-hosting/docker-compose", }, { - title: "Railway", - href: "/docs/self-hosting/railway", + title: "Kubernetes", + href: "/docs/self-hosting/kubernetes", }, ], }, diff --git a/website/vercel.json b/website/vercel.json new file mode 100644 index 0000000000..5afbede5e6 --- /dev/null +++ b/website/vercel.json @@ -0,0 +1,3 @@ +{ + "trailingSlash": true +}