# Rivet Documentation - Complete This file contains the complete documentation for Rivet, an open-source alternative to Durable Objects. ## Actions # Actions Actions are how your backend, frontend, or other actors can communicate with actors. Actions are defined as functions in the actor configuration and can be called from clients. Actions are very lightweight. They can be called thousands of times per second safely. Actions are executed via HTTP requests or via WebSockets if [using `.connect()`](/docs/actors/connections). For advanced use cases that require direct access to HTTP requests or WebSocket connections, see [raw HTTP and WebSocket handling](/docs/actors/fetch-and-websocket-handler). ## Writing Actions Actions are defined in the `actions` object when creating an actor: ```typescript const mathUtils = actor(, actions: } }); ``` Each action receives a context object (commonly named `c`) as its first parameter, which provides access to state, connections, and other utilities. Additional parameters follow after that. ## Calling Actions Actions can be called in different ways depending on your use case: ```typescript } const client = createClient("http://localhost:8080"); const counter = await client.counter.getOrCreate(); const result = await counter.increment(42); console.log(result); // The value returned by the action ``` Learn more about [communicating with actors from the frontend](/docs/actors/communicating-between-actors). ```typescript } const registry = setup( }); const = registry.start(); const app = new Hono(); // Use the client to call actions on a request app.get("/foo", () => ); serve(app); ``` Learn more about [communicating with actors from the backend](/docs/actors/communicating-between-actors). ```typescript } const actorA = actor(, actions: } }); ``` Learn more about [communicating between actors](/docs/actors/communicating-between-actors). Calling actions from the client are async and require an `await`, even if the action itself is not async. ### Type Safety The actor client includes type safety out of the box. When you use `createClient()`, TypeScript automatically infers action parameter and return types: ```typescript } // Create simple counter const counter = actor(, actions: } }); // Create and the app const registry = setup( }); ``` ```typescript } const client = createClient("http://localhost:8080"); // Type-safe client usage const counter = await client.counter.get(); await counter.increment(123); // OK await counter.increment("non-number type"); // TypeScript error await counter.nonexistentMethod(123); // TypeScript error ``` ## Error Handling Actors provide robust error handling out of the box for actions. ### User Errors `UserError` can be used to return rich error data to the client. You can provide: - A human-readable message - A machine-readable code that's useful for matching errors in a try-catch (optional) - A metadata object for providing richer error context (optional) For example: ```typescript } const user = actor(, actions: }); } // Update username c.state.username = username; } } }); ``` ```typescript } try catch (error) } } ``` ### Internal Errors All other errors will return an error with the code `internal_error` to the client. This helps keep your application secure, as errors can sometimes expose sensitive information. ## Schema Validation If passing data to an actor from the frontend, use a library like [Zod](https://zod.dev/) to validate input data. For example, to validate action parameters: ```typescript } // Define schema for action parameters const IncrementSchema = z.object(); const counter = actor(, actions: = IncrementSchema.parse(params); c.state.count += count; return c.state.count; } catch (err) }); } } } }); ``` ## Streaming Data Actions have a single return value. To stream realtime data in response to an action, use [events](/docs/actors/events). ## Canceling Long-Running Actions For operations that should be cancelable on-demand, create your own `AbortController` and chain it with `c.abortSignal` for automatic cleanup on actor shutdown. ```typescript const chatActor = actor(), actions: ), signal: controller.signal }); return await response.json(); }, cancel: (c) => } }); ``` See [Actor Shutdown Abort Signal](/docs/actors/lifecycle#actor-shutdown-abort-signal) for automatically canceling operations when the actor stops. ## Using `ActionContext` Externally When writing complex logic for actions, you may want to extract parts of your implementation into separate helper functions. When doing this, you'll need a way to properly type the context parameter. Rivet provides the `ActionContextOf` utility type for exactly this purpose: ```typescript const counter = actor(, actions: } }); // Simple helper function with typed context function incrementCount(c: ActionContextOf) ``` See [types](/docs/actors/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 ## AI and User-Generated Rivet Actors # AI and User-Generated Rivet Actors This guide shows you how to programmatically create sandboxed Rivet environments and deploy custom actor code to them. Complete example showing how to deploy user-generated Rivet Actor code. ## Use Cases Deploying AI and user-generated Rivet Actors to sandboxed namespaces is useful for: - **AI-generated code deployments**: Deploy code generated by LLMs in sandboxed environments - **User sandbox environments**: Give users their own sandboxed Rivet namespace to experiment - **Preview deployments**: Create ephemeral environments for testing pull requests - **Multi-tenant applications**: Isolate each customer in their own sandboxed namespace ## Rivet Actors For AI-Generated Backends Traditional architectures require AI agents to coordinate across multiple disconnected systems: a database schemas, API logic, and synchronizing schemas & APIs. With Rivet Actors, **state and logic live together in a single actor definition**. This consolidation means: - **Less LLM context required**: No need to understand multiple systems or keep them in sync - **Fewer errors**: State and behavior can't drift apart when they're defined together - **More powerful generation**: AI agents can focus on business logic instead of infrastructure plumbing ## How It Works The deployment process involves four key steps: 1. **Create sandboxed Rivet namespace**: Programmatically create a sandboxed Rivet namespace using the Cloud API or self-hosted Rivet API 2. **Generate tokens**: Create the necessary tokens for authentication: - **Runner token**: Authenticates the serverless runner to execute actors - **Publishable token**: Used by frontend clients to connect to actors - **Access token**: Provides API access for configuring the namespace 3. **Deploy AI or user-generated code**: Deploy the actor code and frontend programmatically to your serverless platform of choice (such as Vercel, Netlify, AWS Lambda, or any other provider). We'll be using [Freestyle](https://freestyle.sh) for this example since it's built for this use case. 4. **Connect Rivet to your deployed code**: Configure Rivet to run actors on your deployment in your sandboxed namespace ## Setup Before you begin, ensure you have: - Node.js 18+ installed - A [Freestyle](https://freestyle.sh) account and API token - A [Rivet Cloud](https://dashboard.rivet.dev/) account 1. Visit your project on [Rivet Cloud](https://dashboard.rivet.dev/) 2. Click on "Tokens" in the sidebar 3. Under "Cloud API Tokens" click "Create Token" 4. Copy the token for use in your deployment script Install the required dependencies: ```bash npm install @rivetkit/engine-api-full@^25.7.2 freestyle-sandboxes@^0.0.95 ``` Write deployment code that handles namespace creation, token generation, Freestyle deployment, and runner configuration. This can be called from your backend to deploy actor and frontend code to an isolated Rivet namespace. ```typescript const CLOUD_API_TOKEN = "your-cloud-api-token"; const FREESTYLE_DOMAIN = "your-app.style.dev"; const FREESTYLE_API_KEY = "your-freestyle-api-key"; async function deploy(projectDir: string) = await cloudRequest("GET", "/tokens/api/inspect"); // Step 2: Create sandboxed namespace with a unique name const namespaceName = `ns-$-$`; const = await cloudRequest( "POST", `/projects/$/namespaces?org=$`, , ); const engineNamespaceName = namespace.access.engineNamespaceName; // NOTE: Intentionally different than namespace.name // Step 3: Generate tokens // - Runner token: authenticates the serverless runner to execute actors // - Publishable token: used by frontend clients to connect to actors // - Access token: provides API access for configuring the namespace const = await cloudRequest( "POST", `/projects/$/namespaces/$/tokens/secret?org=$`, ); const = await cloudRequest( "POST", `/projects/$/namespaces/$/tokens/publishable?org=$`, ); const = await cloudRequest( "POST", `/projects/$/namespaces/$/tokens/access?org=$`, ); // Step 4: Build the frontend with public environment variables. execSync("npm run build", , stdio: "inherit", }); // Step 5: Deploy actor code and frontend to Freestyle with backend // environment variables. const freestyle = new FreestyleSandboxes(); const deploymentSource = prepareDirForDeploymentSync(projectDir); const = await freestyle.deployWeb(deploymentSource, , entrypoint: "src/backend/server.ts", domains: [FREESTYLE_DOMAIN], build: false, }); // Step 6: Configure Rivet to run actors on the Freestyle deployment. const rivet = new RivetClient(); await rivet.runnerConfigsUpsert("default", /api/rivet`, headers: , runnersMargin: 0, minRunners: 0, maxRunners: 1000, slotsPerRunner: 1, requestLifespan: 60 * 5, }, }, }, namespace: engineNamespaceName, }); console.log("Deployment complete!"); console.log("Frontend:", `https://$`); console.log("Rivet Dashboard:", `https://dashboard.rivet.dev/orgs/$/projects/$/ns/$`); console.log("Freestyle Dashboard:", `https://admin.freestyle.sh/dashboard/deployments/$`); } async function cloudRequest(method: string, path: string, body?: any) `, `, ...(body && ), }, ...(body && ), }); return res.json(); } ``` See the [example repository](https://github.com/rivet-dev/rivet/tree/main/examples/ai-and-user-generated-actors-freestyle) for the complete project structure including the template directory and build process. For more information on Freestyle deployment, see the [Freestyle documentation](https://docs.freestyle.sh/web/overview). Before you begin, ensure you have: - Node.js 18+ installed - A [Freestyle](https://freestyle.sh) account and API key - A [self-hosted Rivet instance](/docs/self-hosting) with endpoint and API token Install the required dependencies: ```bash npm install @rivetkit/engine-api-full@^25.7.2 freestyle-sandboxes@^0.0.95 ``` Write deployment code that handles namespace creation, Freestyle deployment, and runner configuration. This can be called from your backend to deploy actor and frontend code to an isolated Rivet namespace. ```typescript // Configuration const RIVET_ENDPOINT = "http://your-rivet-instance:6420"; const RIVET_TOKEN = "your-rivet-token"; const FREESTYLE_DOMAIN = "your-app.style.dev"; const FREESTYLE_API_KEY = "your-freestyle-api-key"; async function deploy(projectDir: string) ); const namespaceName = `ns-$-$`; const = await rivet.namespaces.create(); // Step 2: Build the frontend with public environment variables. execSync("npm run build", , stdio: "inherit", }); // Step 3: Deploy actor and frontend to Freestyle with backend // environment variables. const freestyle = new FreestyleSandboxes(); const deploymentSource = prepareDirForDeploymentSync(projectDir); const = await freestyle.deployWeb(deploymentSource, , entrypoint: "src/backend/server.ts", domains: [FREESTYLE_DOMAIN], build: false, }); // Step 4: Configure your self-hosted Rivet to run actors on the Freestyle // deployment await rivet.runnerConfigsUpsert("default", /api/rivet`, headers: , runnersMargin: 0, minRunners: 0, maxRunners: 1000, slotsPerRunner: 1, requestLifespan: 60 * 5, }, }, }, namespace: namespace.name, }); console.log("Deployment complete!"); console.log("Frontend:", `https://$`); console.log("Freestyle Dashboard:", `https://admin.freestyle.sh/dashboard/deployments/$`); } ``` See the [example repository](https://github.com/rivet-dev/rivet/tree/main/examples/ai-and-user-generated-actors-freestyle) for the complete project structure including the template directory and build process. ## Authentication # Authentication Secure your actors with authentication and authorization. ## Do You Need Authentication? Actors are private by default on Rivet Cloud. Only requests with the publishable token can interact with actors. - **Backend-only actors**: If your publishable token is only included in your backend, then authentication is not necessary. - **Frontend-accessible actors**: If your publishable token is included in your frontend, then implementing authentication is recommended. Actors are public by default on self-hosted Rivet. Anyone can access them without a token. - **Only accessible within private network**: If Rivet is only accessible within your private network, then authentication is not necessary. - **Rivet exposed to the public internet**: If Rivet is configured to accept traffic from the public internet, then implementing authentication is recommended. ## Authentication Connections Authentication is configured through either: - `onBeforeConnect` for simple pass/fail validation - `createConnState` when you need to access user data in your actions via `c.conn.state` ### `onBeforeConnect` The `onBeforeConnect` hook validates credentials before allowing a connection. Throw an error to reject the connection. ```typescript interface ConnParams const chatRoom = actor(, onBeforeConnect: async (c, params: ConnParams) => ); } }, actions: ); }, }, }); ``` ### `createConnState` Use `createConnState` to extract user data from credentials and store it in connection state. This data is accessible in actions via `c.conn.state`. Like `onBeforeConnect`, throwing an error will reject the connection. See [connections](/docs/actors/connections) for more details. ```typescript interface ConnParams interface ConnState const chatRoom = actor(, createConnState: async (c, params: ConnParams): Promise => ); } return ; }, actions: = c.conn.state; if (role !== "member") ); } c.state.messages.push(); c.broadcast("newMessage", ); }, }, }); ``` ## Available Auth Data Authentication hooks have access to several properties: | Property | Description | |----------|-------------| | `params` | Custom data passed by the client when connecting (see [connection params](/docs/actors/connections#extracting-data-from-connection-params)) | | `c.request` | The underlying HTTP request object | | `c.request.headers` | Request headers for tokens, API keys (does not work for `.connect()`) | | `c.state` | Actor state for authorization decisions (see [state](/docs/actors/state)) | | `c.key` | The actor's key (see [keys](/docs/actors/keys)) | It's recommended to use `params` instead of `c.request.headers` whenever possible since it works for both HTTP & WebSocket connections. ## Client Usage ### Passing Credentials Pass authentication data when connecting: ```typescript } const client = createClient(); const chat = client.chatRoom.getOrCreate(["general"], , }); // Authentication will happen on connect by reading connection parameters const connection = chat.connect(); ``` ```typescript } const client = createClient(); const chat = client.chatRoom.getOrCreate(["general"], , }); // Authentication will happen when calling the action by reading input // parameters await chat.sendMessage("Hello, world!"); ``` ```typescript } // This only works for stateless actions, not WebSockets const client = createClient(, }); const chat = client.chatRoom.getOrCreate(["general"]); // Authentication will happen when calling the action by reading headers await chat.sendMessage("Hello, world!"); ``` ### Handling Errors Authentication errors use the same system as regular errors. See [errors](/docs/actors/errors) for more details. ```typescript } const conn = actor.connect(); conn.onError((error: ActorError) => else if (error.code === "insufficient_permissions") }); ``` ```typescript } try catch (error) else if (error instanceof ActorError && error.code === "insufficient_permissions") } ``` ## Examples ### JWT Validate JSON Web Tokens and extract user claims: ```typescript interface ConnParams interface ConnState const jwtActor = actor(, createConnState: (c, params: ConnParams): ConnState => ; } catch ); } }, actions: ); } return ; }, }, }); ``` ### External Auth Provider Validate credentials against an external authentication service: ```typescript interface ConnParams interface ConnState const apiActor = actor(, createConnState: async (c, params: ConnParams): Promise => , }); if (!response.ok) ); } const data = await response.json(); return ; }, actions: ); } return "Premium content"; }, }, }); ``` ### Using `c.state` In Authorization Access actor state via `c.state` and the actor's key via `c.key` to make authorization decisions: ```typescript interface ConnParams const userProfile = actor(, onBeforeConnect: (c, params: ConnParams) => ); } }, actions: ), }, }); ``` ### Role-Based Access Control Create helper functions for common authorization patterns: ```typescript const ROLE_HIERARCHY = ; interface ConnState function requireRole(requiredRole: keyof typeof ROLE_HIERARCHY) }) => ' required`, ); } }; } const forumActor = actor(, createConnState: async (c, params: ): Promise => ; }, actions: , editPost: (c, postId: string, content: string) => , }, }); ``` ### Rate Limiting Use `c.vars` to track connection attempts and rate limit by user: ```typescript interface ConnParams interface RateLimitEntry const rateLimitedActor = actor(, vars: as Record }, onBeforeConnect: async (c, params: ConnParams) => = await validateToken(params.authToken); // Check rate limit const now = Date.now(); const limit = c.vars.rateLimits[userId]; if (limit && limit.resetAt > now && limit.count >= 10) ); } // Update rate limit if (!limit || limit.resetAt (), }, }); ``` The limits in this example are [ephemeral](/docs/actors/state#ephemeral-variables-vars). If you wish to persist rate limits, you can optionally replace `vars` with `state`. ### Caching Tokens Cache validated tokens in `c.vars` to avoid redundant validation on repeated connections. See [ephemeral variables](/docs/actors/state#ephemeral-variables-vars) for more details. ```typescript interface ConnParams interface ConnState interface TokenCache ; } const cachedAuthActor = actor(, vars: as TokenCache }, createConnState: async (c, params: ConnParams): Promise => ; } // Validate token (expensive operation) const payload = await validateToken(token); if (!payload) ); } // Cache the result c.vars.tokenCache[token] = ; return ; }, actions: ), }, }); ``` ## API Reference - [`AuthIntent`](/typedoc/types/rivetkit.mod.AuthIntent.html) - Authentication intent type - [`BeforeConnectContext`](/typedoc/interfaces/rivetkit.mod.BeforeConnectContext.html) - Context for auth checks - [`ConnectContext`](/typedoc/interfaces/rivetkit.mod.ConnectContext.html) - Context after connection ## Clients # Clients Clients are used to get and communicate with actors from your application. Clients can be created from either your frontend or backend. ## Creating a Client For frontend applications or external services: ```typescript } // Must use `type` const client = createClient(); ``` ```tsx } // Must use `type` const = createRivetKit(); ``` Clients include the following options: ```typescript const client = createClient( }); ``` From your backend server that hosts the registry: ```typescript const = registry.start(); ``` From within an actor to communicate with other actors: ```typescript const myActor = actor( } }); ``` Read more about [communicating between actors](/docs/actors/communicating-between-actors). ## Getting an Actor ### `getOrCreate` Returns a handle to an existing actor or creates one if it doesn't exist: ```typescript } const counter = client.counter.getOrCreate("my-counter"); const count = await counter.increment(5); ``` ```tsx } const counter = useActor(); // Call actions through the connection await counter.connection?.increment(5); ``` Pass initialization data when creating: ```typescript } const game = client.game.getOrCreate("game-123", }); ``` ```tsx } const game = useActor(, }); ``` ### `get` Returns a handle to an existing actor or `null` if it doesn't exist: ```typescript } const handle = client.myActor.get("actor-id"); if (handle) ``` ```tsx } // `get` is not currently supported with useActor. // Use createClient with useMemo instead: function MyComponent() ``` ### `create` Creates a new actor, failing if one already exists with that key: ```typescript } const newGame = await client.game.create("game-456", }); ``` ```tsx } // `create` is not currently supported with useActor. // Use createClient with useMemo instead: function MyComponent() }).then(setHandle); }, [client]); // Use handle to call actions } ``` ### `getForId` Connect to an actor using its internal ID: ```typescript } const actor = client.myActor.getForId("lrysjam017rhxofttna2x5nzjml610"); ``` ```tsx } // `getForId` is not currently supported with useActor. // Use createClient with useMemo instead: function MyComponent(: ) ``` Prefer using keys over internal IDs for simplicity. ## Calling Actions ```typescript } const counter = client.counter.getOrCreate("my-counter"); const count = await counter.increment(5); const value = await counter.getCount(); await counter.reset(); ``` ```tsx } const counter = useActor(); // Call actions through the connection const count = await counter.connection?.increment(5); const value = await counter.connection?.getCount(); await counter.connection?.reset(); ``` In JavaScript, actions called without `connect()` are stateless. Each call is independent without a persistent connection. In React, `useActor` automatically manages a persistent connection. ## Connecting to an Actor For real-time use cases, establish a persistent connection to the actor: ```typescript } const counter = client.counter.getOrCreate("live-counter"); const conn = counter.connect(); // Listen for events conn.on("countChanged", (newCount: number) => ); // Call actions through the connection await conn.increment(1); ``` ```tsx } const [count, setCount] = useState(0); const counter = useActor(); // Listen for events counter.useEvent("countChanged", (newCount: number) => ); // Call actions through the connection await counter.connection?.increment(1); ``` ## Subscribing to Events Listen for events from connected actors: ```typescript } const conn = client.chatRoom.getOrCreate("general").connect(); // Listen for events conn.on("messageReceived", (message) => : $`); }); // Listen once conn.once("gameStarted", () => ); ``` ```tsx } const [messages, setMessages] = useState([]); const chatRoom = useActor(); // Listen for events (automatically cleaned up on unmount) chatRoom.useEvent("messageReceived", (message) => ); ``` ## Full-Stack Type Safety Import types from your registry for end-to-end type safety: ```typescript } const client = createClient(); // IDE autocomplete shows available actors and actions const counter = client.counter.getOrCreate("my-counter"); const count = await counter.increment(5); ``` ```tsx } const = createRivetKit(); // IDE autocomplete shows available actors and actions const counter = useActor(); const count = await counter.connection?.increment(5); ``` Use `import type` to avoid accidentally bundling backend code in your frontend. ## Advanced ### Disposing Clients & Connections Dispose clients to close all connections: ```typescript await client.dispose(); ``` Dispose individual connections when finished: ```typescript const conn = actor.connect(); try finally ``` When using `useActor` in React, connections are automatically disposed when the component unmounts. No manual cleanup is required. ### Connection Parameters Pass custom data to the actor when connecting: ```typescript } const chat = client.chatRoom.getOrCreate("general", }); ``` ```tsx } const chat = useActor(, }); ``` ### Authentication Pass authentication tokens when connecting: ```typescript } const chat = client.chatRoom.getOrCreate("general", }); ``` ```tsx } const chat = useActor(, }); ``` See [authentication](/docs/actors/authentication) for more details. ### Error Handling ```typescript } const conn = actor.connect(); conn.onError((error: ActorError) => }); ``` ```typescript } try catch (error) } ``` ```tsx } const actor = useActor(); const handleAction = async () => catch (error) } }; ``` See [errors](/docs/actors/errors) for more details. ### Actor Resolution `get` and `getOrCreate` return immediately without making a network request. The actor is resolved lazily when you call an action or `connect()`. To explicitly resolve an actor and get its ID, use `resolve()`: ```typescript const handle = client.counter.getOrCreate("my-counter"); 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 ## Communicating Between Actors # Communicating Between Actors Learn how actors can call other actors and share data Actors can communicate with each other using the server-side actor client, enabling complex workflows and data sharing between different actor instances. We recommend reading the [clients documentation](/docs/actors/clients) first. This guide focuses specifically on communication between actors. ## Using the Server-Side Actor Client The server-side actor client allows actors to call other actors within the same registry. Access it via `c.client()` in your actor context: ```typescript const orderProcessor = actor(, actions: ); return ; } } }); ``` ## Use Cases and Patterns ### Actor Orchestration Use a coordinator actor to manage complex workflows: ```typescript const workflowActor = actor(, actions: ); return result; } } }); ``` ### Data Aggregation Collect data from multiple actors: ```typescript const analyticsActor = actor(, actions: , generatedAt: Date.now() }; c.state.reports.push(report); return report; } } }); ``` ### Event-Driven Architecture Use connections to listen for events from other actors: ```typescript const auditLogActor = actor(, actions: ); }); // Listen for order events orderActor.on("orderCompleted", (order) => ); }); return ; } } }); ``` ### Batch Operations Process multiple items in parallel: ```typescript // Process items in parallel const results = await Promise.all( items.map(item => client.processor.getOrCreate(item.type).process(item)) ); ``` ## 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 ## Connections # Connections Connections represent client connections to your actor. They provide a way to handle client authentication, manage connection-specific data, and control the connection lifecycle. For documentation on connecting to actors from clients, see the [Clients documentation](/docs/actors/clients). ## Parameters When clients connect to an actor, they can pass connection parameters that are handled during the connection process. For example: ```typescript } const client = createClient("http://localhost:8080"); const gameRoom = client.gameRoom.getOrCreate("room-123", }); ``` ```typescript } interface ConnParams interface ConnState const gameRoom = actor(, // Handle connection setup createConnState: (c, params: ConnParams): ConnState => // Create connection state return ; }, actions: }); ``` ## Connection State There are two ways to define an actor's connection state: Define connection state as a constant value: ```typescript const chatRoom = actor(, // Define default connection state as a constant connState: , onConnect: (c) => , actions: }); ``` This value will be cloned for every new connection using `structuredClone`. Create connection state dynamically with a function called for each connection: ```typescript interface ConnState const chatRoom = actor(, // Create connection state dynamically createConnState: (c): ConnState => ; }, actions: ); c.broadcast("newMessage", ); } } }); ``` ## 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.BeforeConnectContext.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 const chatRoom = actor(, // Method 1: Use a static default connection state connState: , // Method 2: Dynamically create connection state createConnState: (c, params: ) => ; }, // Validate connections before accepting them onBeforeConnect: (c, params: ) => // Authentication is valid, connection will proceed // The actual connection state will come from connState or createConnState }, actions: }); ``` 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. ### `onConnect` [API Reference](/typedoc/interfaces/rivetkit.mod.ConnectContext.html) Executed after the client has successfully connected. Can be async. Receives the connection object as a second parameter. ```typescript const chatRoom = actor(, messages: [] }, onConnect: (c, conn) => ; // Broadcast that a user joined c.broadcast("userJoined", ); console.log(`User $ 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 const chatRoom = actor(, messages: [] }, onDisconnect: (c, conn) => // Broadcast that a user left c.broadcast("userLeft", ); console.log(`User $ disconnected`); }, actions: }); ``` ## Connection List All active connections can be accessed through the context object's `conns` property. This is an array of all current connections. This is frequently used with `conn.send(name, event)` to send messages directly to clients. To send an event to all connections at once, use `c.broadcast()` instead. See [Events](/docs/actors/events) for more details on broadcasting. For example: ```typescript const chatRoom = actor( }, actions: ); } } } }); ``` `conn.send()` has no effect on [low-level WebSocket connections](/docs/actors/websocket-handler). For low-level WebSockets, use the WebSocket API directly (e.g., `websocket.send()`). ## Disconnecting clients Connections can be disconnected from within an action: ```typescript const secureRoom = actor(, actions: } } }); ``` If you need to wait for the disconnection to complete, you can use `await`: ```typescript 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 - [`BeforeConnectContext`](/typedoc/interfaces/rivetkit.mod.BeforeConnectContext.html) - Pre-connection lifecycle hook context - [`ConnectContext`](/typedoc/interfaces/rivetkit.mod.ConnectContext.html) - Post-connection lifecycle hook context - [`ActorConn`](/typedoc/types/rivetkit.client_mod.ActorConn.html) - Typed connection from client side ## Design Patterns # Design Patterns Common patterns and anti-patterns for building scalable actor systems. ## How Actors Scale Actors are inherently scalable because of how they're designed: - **Isolated state:** Each actor manages its own private data. No shared state means no conflicts and no locks, so actors run concurrently without coordination. - **Actor-to-actor communication:** Actors interact through [actions](/docs/actors/actions) and [events](/docs/actors/events), so they don't need to coordinate access to shared data. This makes it easy to distribute them across machines. - **Small, focused units:** Each actor handles a limited scope (a single user, document, or chat room), so load naturally spreads across many actors rather than concentrating in one place. - **Horizontal scaling:** Adding more machines automatically distributes actors across them. These properties form the foundation for the patterns described below. ## Actor Per Entity The core pattern is creating one actor per entity in your system. Each actor represents a single user, document, chat room, or other distinct object. This keeps actors small, independent, and easy to scale. **Good examples** - `User`: Manages user profile, preferences, and authentication - `Document`: Handles document content, metadata, and versioning - `ChatRoom`: Manages participants and message history **Bad examples** - `Application`: Too broad, handles everything - `DocumentWordCount`: Too granular, should be part of Document actor ## Coordinator & Data Actors Actors scale by splitting state into isolated entities. However, it's common to need to track and coordinate actors in a central place. This is where coordinator actors come in. **Data actors** handle the main logic in your application. Examples: chat rooms, user sessions, game lobbies. **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 Room Coordinator** ```ts // Data actor: handles messages and connections const chatRoom = actor([] }, actions: ; c.state.messages.push(message); c.broadcast("newMessage", message); return message; }, getHistory: (c) => c.state.messages, }, }); // Coordinator: indexes chat rooms const chatRoomList = actor(, actions: , listChatRooms: (c) => c.state.chatRoomIds, }, }); const registry = setup(, }); ``` ```ts const client = createClient("http://localhost:8080"); // Create a new chat room via coordinator const coordinator = client.chatRoomList.getOrCreate("main"); const actorId = await coordinator.createChatRoom("general"); // Get list of all chat rooms const chatRoomIds = await coordinator.listChatRooms(); // Connect to a chat room using its ID const chatRoom = client.chatRoom.getForId(actorId); await chatRoom.sendMessage("alice", "Hello!"); const history = await chatRoom.getHistory(); ``` ## Sharding Sharding splits a single actor's workload across multiple actors based on a key. Use this when one actor can't handle all the load or data for an entity. **How it works:** - Partition data using a shard key (user ID, region, time bucket, or random) - Requests are routed to shards based on the key - Shards operate independently without coordination **Example: Sharding by Time** ```ts interface Event const hourlyAnalytics = actor(, actions: , getEvents: (c) => c.state.events, }, }); const registry = setup(, }); ``` ```ts 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); await analytics.trackEvent(); ``` **Example: Random Sharding** ```ts const rateLimiter = actor( as Record }, actions: , }, }); const registry = setup(, }); ``` ```ts const client = createClient("http://localhost:8080"); // Shard randomly: rateLimiter:shard-0, rateLimiter:shard-1, rateLimiter:shard-2 const shardKey = `shard-$`; const limiter = client.rateLimiter.getOrCreate(shardKey); const allowed = await limiter.checkLimit("user-123", 100); ``` Choose shard keys that distribute load evenly. Note that cross-shard queries require coordination. ## Fan-In & Fan-Out Fan-in and fan-out are patterns for distributing work and aggregating results. **Fan-Out**: One actor spawns work across multiple actors. Use for parallel processing or broadcasting updates. **Fan-In**: Multiple actors send results to one aggregator. Use for collecting results or reducing data. **Example: Map-Reduce** ```ts interface Task interface Result // Coordinator fans out tasks, then fans in results const coordinator = actor(, actions: , // Fan-in: collect results reportResult: (c, result: Result) => , getResults: (c) => c.state.results, }, }); const worker = actor(, actions: ` }; const client = c.client(); await client.coordinator.getOrCreate("main").reportResult(result); }, }, }); const registry = setup(, }); ``` ```ts const client = createClient("http://localhost:8080"); const coordinator = client.coordinator.getOrCreate("main"); // Start a job with multiple tasks await coordinator.startJob([ , , , ]); // Results are collected as workers report back const results = await coordinator.getResults(); ``` ## 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. 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 Profile** ```ts const userSession = actor(, // createVars runs on every wake (after restarts, crashes, or sleep), so // external data stays fresh. createVars: async (c) => ; }, actions: , updateEmail: async (c, email: string) => ); // Refresh cached data c.vars.user = await db.users.findById(c.id); }, }, }); const registry = setup(, }); ``` ```ts 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 email (writes to database and refreshes cache) await session.updateEmail("alice@example.com"); ``` ### 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 const userActor = actor(, onCreate: async (c, input: ) => ); }, onStateChange: async (c, newState) => ); }, actions: , getUser: (c) => (), }, }); const registry = setup(, }); ``` ```ts const client = createClient("http://localhost:8080"); const user = await client.userActor.create("user-123", , }); // 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 ### "God" Actor Avoid creating a single actor that handles everything. This defeats the purpose of the actor model and creates a bottleneck. **Problem:** ```ts // Bad: one actor doing everything const app = actor(, orders: , inventory: , analytics: }, actions: , processOrder: (c, order) => , updateInventory: (c, item) => , trackEvent: (c, event) => , }, }); ``` **Solution:** Split into focused actors per entity (User, Order, Inventory, Analytics). ### Actor-Per-Request Actors are designed to maintain state across multiple requests. Creating a new actor for each request wastes resources and loses the benefits of persistent state. **Problem:** ```ts const client = createClient("http://localhost:8080"); // Bad: creating an actor for each API request 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 ## Destroying Actors # Destroying Actors Actors can be permanently destroyed. Common use cases include: - User account deletion - Ending a user session - Closing a room or game - Cleaning up temporary resources - GDPR/compliance data removal Actors sleep when idle, so destruction is only needed to permanently remove data — not to save compute. ## Destroying An Actor ### Destroy via Action To destroy an actor, use `c.destroy()` like this: ```typescript interface UserInput const userActor = actor(), actions: , }, }); ``` ### Destroy via HTTP Send a DELETE request to destroy an actor. This requires an admin token for authentication. ```typescript await fetch(`https://api.rivet.dev/actors/$?namespace=$`, `, }, }); ``` ```bash curl -X DELETE "https://api.rivet.dev/actors/?namespace=" \ -H "Authorization: Bearer " ``` Creating admin tokens is currently not supported on Rivet Cloud. See the [tracking issue](https://github.com/rivet-dev/rivet/issues/3530). ### Destroy via Dashboard To destroy an actor via the dashboard, navigate to the actor and press the red "X" in the top right. ## Lifecycle Hook Once destroyed, the `onDestroy` hook will be called. This can be used to clean up resources related to the actor. For example: ```typescript const resend = new Resend(process.env.RESEND_API_KEY); interface UserInput const userActor = actor(), onDestroy: async (c) => , your account has been deleted.`, }); }, actions: , }, }); ``` ## Accessing Actor After Destroy 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 ## Ephemeral Variables # Ephemeral Variables In addition to persisted state, Rivet provides a way to store ephemeral data that is not saved to permanent storage using `vars`. This is useful for temporary data that only needs to exist while the actor is running or data that cannot be serialized. `vars` is designed to complement `state`, not replace it. Most actors should use both: `state` for critical business data and `vars` for ephemeral or non-serializable data. ## Initializing Variables There are two ways to define an actor's initial vars: Define an actor vars as a constant value: ```typescript // Define vars as a constant const counter = actor(, // Define ephemeral variables vars: , actions: }); ``` This value will be cloned for every new actor using `structuredClone`. Create actor state dynamically on each actors' start: ```typescript // Define vars with initialization logic const counter = actor(, // Define vars using a creation function createVars: (c: CreateVarsContext, driver: any) => ; }, actions: }); ``` If accepting arguments to `createVars`, you **must** define the types: `createVars(c: CreateVarsContext, driver: any)` Otherwise, the return type will not be inferred and `c.vars` will be of type `unknown`. ## Using Variables Vars can be accessed and modified through the context object with `c.vars`: ```typescript const counter = actor(, // Create ephemeral objects that won't be serialized createVars: () => `); }); return ; }, actions: } }); ``` ## When to Use `vars` vs `state` In practice, most actors will use both: `state` for critical business data and `vars` for ephemeral or non-serializable data. Use `vars` when: - You need to store temporary data that doesn't need to survive restarts - You need to maintain runtime-only references that can't be serialized (database connections, event emitters, class instances, etc.) Use `state` when: - The data must be preserved across actor sleeps, restarts, updates, or crashes - The information is essential to the actor's core functionality and business logic ## Advanced ### Accessing Driver Context The `createVars` function receives a second parameter that provides access to driver-specific context. This allows you to access driver-specific functionality. For example, the Redis driver exposes access to the Redis instance: ```typescript const myActor = actor(, // The second parameter provides driver-specific context createVars: (ctx: CreateVarsContext, driver: DriverContext) => (), actions: } }); ``` Consult the documentation for each driver to learn more about their respective `DriverContext` types. ## Errors # Errors 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 const user = actor(, actions: // Update username c.state.username = username; } } }); ``` ```typescript const client = createClient("http://localhost:8080"); const conn = client.user.getOrCreate().connect(); try catch (error) } ``` ```typescript const client = createClient("http://localhost:8080"); const userActor = client.user.getOrCreate(); try catch (error) } ``` ## Error Codes Use error codes for explicit error matching in try-catch blocks: ```typescript const user = actor(, actions: ); } // Update username c.state.username = username; } } }); ``` ```typescript const client = createClient("http://localhost:8080"); const conn = client.user.getOrCreate().connect(); try catch (error) else if (error.code === "username_too_long") } } ``` ```typescript const client = createClient("http://localhost:8080"); const userActor = client.user.getOrCreate(); try catch (error) else if (error.code === "username_too_long") } } ``` ## Errors With Metadata Include metadata to provide additional context for rich error handling: ```typescript const api = actor(, actions: }); } // Rest of request logic... } } }); ``` ```typescript const client = createClient("http://localhost:8080"); const conn = client.api.getOrCreate().connect(); try catch (error) if (error.code === "rate_limited") seconds`); } } } ``` ```typescript const client = createClient("http://localhost:8080"); const apiActor = client.api.getOrCreate(); try catch (error) if (error.code === "rate_limited") 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 const payment = actor(, actions: ) }); if (!result.ok) : $`); } // Rest of payment logic... } } }); ``` ```typescript const client = createClient("http://localhost:8080"); const conn = client.payment.getOrCreate().connect(); try catch (error) } ``` ```typescript const client = createClient("http://localhost:8080"); const paymentActor = client.payment.getOrCreate(); try catch (error) } ``` ### Server-Side Logging **All internal errors are logged server-side with full details.** When an internal error occurs, the complete error message, stack trace, and context are written to your server logs. This is where you should look first when debugging internal errors in production. The client receives only a generic "Internal error" message for security, but you can find the full error details in your server logs including: - Complete error message - Stack trace - Request context (actor ID, action name, connection ID, etc.) - Timestamp **Always check your server logs to see the actual error details when debugging internal errors.** ### Exposing Errors to Clients (Development Only) **Warning:** Only enable error exposure in development environments. In production, this will leak sensitive internal details to clients. For faster debugging during development, you can automatically expose internal error details to clients. This is enabled when: - `NODE_ENV=development` - Automatically enabled in development mode - `RIVET_EXPOSE_ERRORS=1` - Explicitly enable error exposure With error exposure enabled, clients will see the full error message instead of the generic "Internal error" response: ```typescript // With NODE_ENV=development or RIVET_EXPOSE_ERRORS=1 try catch (error) } ``` ## 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 ## Events # Events Events enable real-time communication from actors to clients. While clients use actions to send data to actors, events allow actors to push updates to connected clients instantly. Events can be sent to clients connected using `.connect()`. They have no effect on [low-level WebSocket connections](/docs/actors/websocket-handler). ## Publishing Events from Actors ### Broadcasting to All Clients Use `c.broadcast(eventName, data)` to send events to all connected clients: ```typescript const chatRoom = actor(, actions: ; c.state.messages.push(message); // Broadcast to all connected clients c.broadcast('messageReceived', message); return message; }, } }); ``` ### Sending to Specific Connections Send events to individual connections using `conn.send(eventName, data)`: ```typescript const gameRoom = actor( as Record }, createConnState: (c, opts, params: ) => (), actions: ); } else } } }); ``` Send events to all connections except the sender: ```typescript const gameRoom = actor( as Record }, createConnState: (c, opts, params: ) => (), actions: ) => ); } } } } } }); ``` ## Subscribing to Events from Clients Clients must establish a connection to receive events from actors. Use `.connect()` to create a persistent connection, then listen for events. ### Basic Event Subscription Use `connection.on(eventName, callback)` to listen for events: ```typescript } const client = createClient("http://localhost:8080"); // Get actor handle and establish connection const chatRoom = client.chatRoom.getOrCreate(["general"]); const connection = chatRoom.connect(); // Listen for events connection.on('messageReceived', (message) => : $`); displayMessage(message); }); // Call actions through the connection await connection.sendMessage("user-123", "Hello everyone!"); ``` ```tsx } function ChatRoom() ); // Listen for events chatRoom.useEvent("messageReceived", (message) => ); // ...rest of component... } ``` ### One-time Event Listeners Use `connection.once(eventName, callback)` for events that should only trigger once: ```typescript } const gameRoom = client.gameRoom.getOrCreate(["room-456"]); const connection = gameRoom.connect(); // Listen for game start (only once) connection.once('gameStarted', () => ); ``` ```tsx } function GameLobby() }); // Listen for game start (only once) useEffect(() => ; gameRoom.connection.once('gameStarted', handleGameStart); }, [gameRoom.connection]); // ...rest of component... } ``` ### Removing Event Listeners Use the callback returned from `.on()` to remove event listeners: ```typescript } // Add listener const unsubscribe = connection.on('messageReceived', (message) => ); // Remove listener unsubscribe(); ``` ```tsx } function ConditionalListener() ); useEffect(() => : $`]); }); // Cleanup - remove listener when component unmounts or listening stops return () => ; }, [chatRoom.connection, isListening]); // ...rest of component... } ``` ## More About Connections 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 ## External SQL Database # External SQL Database While actors can serve as a complete database solution, they can also complement your existing databases. For example, you might use actors to handle frequently-changing data that needs real-time access, while keeping less frequently accessed data in your traditional database. Actors can be used with common SQL databases, such as PostgreSQL and MySQL. ## Libraries To facilitate interaction with SQL databases, you can use either ORM libraries or raw SQL drivers. Each has its own use cases and benefits: - **ORM Libraries**: Type-safe and easy way to interact with your database - [Drizzle](https://orm.drizzle.team/) - [Prisma](https://www.prisma.io/) - **Raw SQL Drivers**: Direct access to the database for more flexibility - [PostgreSQL](https://node-postgres.com/) - [MySQL](https://github.com/mysqljs/mysql) ## Hosting Providers There are several options for places to host your SQL database: - [Supabase](https://supabase.com/) - [Neon](https://neon.tech/) - [PlanetScale](https://planetscale.com/) - [AWS RDS](https://aws.amazon.com/rds/) - [Google Cloud SQL](https://cloud.google.com/sql) ## Examples ### Basic PostgreSQL Connection Here's a basic example of a user actor that creates a database record on start and tracks request counts: ```typescript } interface ActorInput // Create a database connection pool const pool = new Pool(); // Create the user actor const userActor = actor(), // Insert user into database when actor creates onCreate: async (c, opts) => , // Sync state changes to database onStateChange: async (c, newState) => , actions: ; }, // Get user data getUser: async (c) => ; } } }); const registry = setup(, }); ``` ```typescript } const client = createClient("http://localhost:8080"); // Create user const alice = await client.userActor.create("alice", }); alice.updateUser("alice2@example.com"); const userData = await alice.getUser(); console.log("User data:", userData); // Create another user const bob = await client.userActor.create("bob", }); const bobData = await bob.getUser(); ``` ### Using Drizzle ORM Here's the same user actor pattern using Drizzle ORM for more type-safe database operations: ```typescript } interface ActorInput // Define your schema const users = pgTable("users", ); // Create a database connection const pool = new Pool(); // Initialize Drizzle with the pool const db = drizzle(pool); // Create the user actor const userActor = actor(), // Insert user into database when actor creates onCreate: async (c, opts) => ); }, // Sync state changes to database onStateChange: async (c, newState) => ) .where(eq(users.username, newState.username)); }, actions: ; }, // Get user data getUser: async (c) => ; } } }); const registry = setup(, }); ``` ```typescript } const client = createClient("http://localhost:8080"); // Create user const alice = await client.userActor.create("alice", }); alice.updateUser("alice2@example.com"); const userData = await alice.getUser(); console.log("User data:", userData); // Create another user const bob = await client.userActor.create("bob", }); const bobData = await bob.getUser(); ``` ## Fetch and WebSocket Handler # Fetch and WebSocket Handler These docs have moved to [Low-Level WebSocket Handler](/docs/actors/websocket-handler) and [Low-Level Request Handler](/docs/actors/request-handler). ## Helper Types # Helper Types This page has moved to [Types](/docs/actors/types). ## Vanilla HTTP API # Vanilla HTTP API TODO ## Overview # Overview Actors for long-lived processes with durable state, realtime, and hibernate when not in use. ## Features - **Long-Lived, Stateful Compute**: Each unit of compute is like a tiny server that remembers things between requests – no need to re-fetch data from a database or worry about timeouts. Like AWS Lambda, but with memory and no timeouts. - **Blazing-Fast Reads & Writes**: State is stored on the same machine as your compute, so reads and writes are ultra-fast. No database round trips, no latency spikes. - **Realtime**: Update state and broadcast changes in realtime with WebSockets. No external pub/sub systems, no polling – just built-in low-latency events. - **Infinitely Scalable**: Automatically scale from zero to millions of concurrent actors. Pay only for what you use with instant scaling and no cold starts. - **Fault Tolerant**: Built-in error handling and recovery. Actors automatically restart on failure while preserving state integrity and continuing operations. ## Core Concepts ### State Management Actors maintain persistent state that survives restarts, crashes, and deployments. State can be defined as a constant or created dynamically: ```typescript const counter = actor(, actions: , getCount: (c) => c.state.count, } }); ``` Learn more about [state management](/docs/actors/state). ### Actions Actions are the primary way to interact with actors. They're type-safe functions that can modify state and communicate with clients: ```typescript const chatRoom = actor(, actions: ; c.state.messages.push(message); c.broadcast("newMessage", message); return message; }, getMessages: (c) => c.state.messages } }); ``` Actions can be called from your backend, your clients, or other actors: ```typescript const room = client.chatRoom.getOrCreate(["general"]); const message = await room.sendMessage("user-123", "Hello everyone!"); ``` Learn more about [actions](/docs/actors/actions) and [communicating with actors](/docs/actors/communicating-between-actors). ### Real-time Communication & Events Actors support real-time bidirectional communication through WebSocket connections. Clients can establish persistent connections to receive live updates. For example, to send events to all connected clients: ```typescript const liveAuction = actor(, actions: ); return amount; } } }); ``` Clients connect and listen for real-time updates: ```typescript const auction = client.liveAuction.getOrCreate(["auction-123"]); const connection = auction.connect(); connection.on("newBid", (data) => `); }); await auction.placeBid(150); ``` Learn more about [events](/docs/actors/events) and [client communication](/docs/actors/communicating-between-actors). ### Scheduling & Lifecycle Actors support scheduled tasks and lifecycle management: ```typescript const reminder = actor(, actions: , sendReminder: (c) => ); } } }); ``` Learn more about [actor lifecycle](/docs/actors/lifecycle). ### Type Safety Rivet provides end-to-end TypeScript safety between clients and actors: ```typescript } const userManager = actor( as Record }, actions: ; return ; }, getUser: (c, userId: string) => c.state.users[userId] } }); ``` ```typescript } const manager = client.userManager.getOrCreate(["default"]); const user = await manager.createUser("Alice"); // Type: const foundUser = await manager.getUser(user.userId); // Type: | undefined ``` ## Input Parameters # Input Parameters Pass initialization data to actors when creating instances Actors can receive input parameters when created, allowing for flexible initialization and configuration. Input is passed during actor creation and is available in lifecycle hooks. ## Passing Input to Actors Input is provided when creating actor instances using the `input` property: ```typescript // Client side - create with input const game = await client.game.create(["game-123"], }); // getOrCreate can also accept input (used only if creating) const gameHandle = client.game.getOrCreate(["game-456"], }); ``` ## Accessing Input in Lifecycle Hooks Input is available in lifecycle hooks via the `opts.input` parameter: ```typescript interface ChatRoomInput const chatRoom = actor(, messages: [], }), onCreate: (c, opts, input: ChatRoomInput) => `); // Setup external services based on input if (input.isPrivate) }, actions: ), }, }); ``` ## Input Validation You can validate input parameters in the `createState` or `onCreate` hooks: ```typescript const GameInputSchema = z.object(); const game = actor(, gameState: "waiting", }; }, actions: ), }, }); ``` ## Input vs Connection Parameters Input parameters are different from connection parameters: - **Input**: - Passed when creating the actor instance - Use for actor-wide configuration - Available in lifecycle hooks - **Connection parameters**: - Passed when connecting to an existing actor - Used for connection-specific configuration - Available in connection hooks ```typescript // Actor creation with input const room = await client.chatRoom.create(["room-123"], , params: }); ``` ## Input Best Practices ### Use Type Safety Define input types to ensure type safety: ```typescript interface GameInput const game = actor(), actions: , }); ``` ### Store Input in State If you need to access input data in actions, store it in the actor's state: ```typescript const game = actor(, // Runtime state players: , gameState: "waiting", }), actions: , }, }); ``` ## 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 ## Actor Keys # Actor Keys Actor keys uniquely identify actor instances within each actor type. Keys are used for addressing which specific actor to communicate with. ## Key Format Actor keys can be either a string or an array of strings: ```typescript // String key const counter = client.counter.getOrCreate("my-counter"); // Array key (compound key) const chatRoom = client.chatRoom.getOrCreate(["room", "general"]); ``` ### Compound Keys & User Data Array keys are useful when you need compound keys with user-provided data. Using arrays makes adding user data safe by preventing key injection attacks: ```typescript // User-specific chat rooms const userRoom = client.chatRoom.getOrCreate(["user", userId, "private"]); // Game rooms by region and difficulty const gameRoom = client.gameRoom.getOrCreate(["us-west", "hard", gameId]); // Multi-tenant resources const workspace = client.workspace.getOrCreate(["tenant", tenantId, workspaceId]); ``` This allows you to create hierarchical addressing schemes and organize actors by multiple dimensions. Don't build keys using string interpolation like `"foo:$:bar"` when `userId` contains user data. If a user provides a value containing the delimiter (`:` in this example), it can break your key structure and cause key injection attacks. ### Omitting Keys You can create actors without specifying a key in situations where there is a singleton actor (i.e. only one actor of a given type). For example: ```typescript // Get the singleton session const globalActor = client.globalActor.getOrCreate(); ``` This pattern should be avoided, since a singleton actor usually means you have a single actor serving all traffic & your application will not scale. See [scaling documentation](/docs/actors/scaling) for more information. ### Key Uniqueness Keys are unique within each actor name. Different actor types can use the same key: ```typescript // These are different actors, same key is fine const userChat = client.chatRoom.getOrCreate(["user-123"]); const userProfile = client.userProfile.getOrCreate(["user-123"]); ``` ## Accessing Keys in Metadata Access the actor's key within the actor using the [metadata](/docs/actors/metadata) API: ```typescript } const chatRoom = actor( } }); const registry = setup( }); ``` ```typescript } const client = createClient("http://localhost:8080"); async function connectToRoom(roomName: string) // Usage example const generalRoom = await connectToRoom("general"); ``` ## Configuration Examples ### Simple Configuration with Keys Use keys to provide basic actor configuration: ```typescript } const userSession = actor( }), actions: }); const registry = setup( }); ``` ```typescript } const client = createClient("http://localhost:8080"); // Pass user ID in the key for user-specific actors const userSession = client.userSession.getOrCreate([userId]); ``` ### Complex Configuration with Input For more complex configuration, use [input parameters](/docs/actors/input): ```typescript } const client = createClient("http://localhost:8080"); // Create with both key and input 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 ## Lifecycle # Lifecycle 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** (runs once per actor) 1. `createState` 2. `onCreate` 3. `createVars` 4. `onWake` **On Destroy** 1. `onDestroy` **On Wake** (after sleep, restart, or crash) 1. `createVars` 2. `onWake` **On Sleep** (after idle period) 1. `onSleep` **On Connect** (per client) 1. `onBeforeConnect` 2. `createConnState` 3. `onConnect` **On Disconnect** (per client) 1. `onDisconnect` ## Lifecycle Hooks Actor lifecycle hooks are defined as functions in the actor configuration. ### `state` The `state` constant defines the initial state of the actor. See [state documentation](/docs/actors/state) for more information. ```typescript const counter = actor(, actions: }); ``` ### `createState` [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. ```typescript const counter = actor() => (), actions: }); ``` ### `vars` The `vars` constant defines ephemeral variables for the actor. These variables are not persisted and are useful for storing runtime-only data. The value for `vars` must be clonable via `structuredClone`. See [ephemeral variables documentation](/docs/actors/state#ephemeral-variables-vars) for more information. ```typescript const counter = actor(, vars: , actions: }); ``` ### `createVars` [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. ```typescript interface CounterVars const counter = actor(, createVars: (c, driverCtx): CounterVars => (), actions: }); ``` ### `onCreate` [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. ```typescript const counter = actor(, onCreate: (c, input: ) => , actions: }); ``` ### `onDestroy` [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. ```typescript const gameSession = actor(, actions: }); ``` ### `onWake` [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. This is called after the actor has been initialized but before any connections are accepted. Use this hook to set up any resources or start any background tasks, such as `setInterval`. ```typescript const counter = actor(, vars: , onWake: (c) => , 10000); // Store interval ID in vars to clean up later if needed c.vars.intervalId = intervalId; }, actions: } } }); ``` ### `onSleep` [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. 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 const counter = actor(, vars: , onWake: (c) => , 10000); }, onSleep: (c) => // Perform any other cleanup console.log('Final count:', c.state.count); }, actions: }); ``` ### `onStateChange` [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. ```typescript const counter = actor(, onStateChange: (c, newState) => ); }, actions: } }); ``` ### `createConnState` and `connState` [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 2. `createConnState`: A function that dynamically creates initial connection state based on connection parameters. Can be async. ### `onBeforeConnect` [API Reference](/typedoc/interfaces/rivetkit.mod.BeforeConnectContext.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 const chatRoom = actor(, // Method 1: Use a static default connection state connState: , // Method 2: Dynamically create connection state createConnState: (c, params: ) => ; }, // Validate connections before accepting them onBeforeConnect: (c, params: ) => // Authentication is valid, connection will proceed // The actual connection state will come from connState or createConnState }, actions: }); ``` 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. ### `onConnect` [API Reference](/typedoc/interfaces/rivetkit.mod.ConnectContext.html) Executed after the client has successfully connected. Can be async. Receives the connection object as a second parameter. ```typescript const chatRoom = actor(, messages: [] }, onConnect: (c, conn) => ; // Broadcast that a user joined c.broadcast("userJoined", ); console.log(`User $ 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 const chatRoom = actor(, messages: [] }, onDisconnect: (c, conn) => // Broadcast that a user left c.broadcast("userLeft", ); console.log(`User $ disconnected`); }, actions: }); ``` ### `onRequest` [API Reference](/typedoc/interfaces/rivetkit.mod.RequestContext.html) The `onRequest` hook handles HTTP requests sent to your actor at `/actors//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. See [Request Handler](/docs/actors/request-handler) for more details. ```typescript const apiActor = actor(, onRequest: (c, request) => ), }); } // Return void to continue to default routing return; }, actions: }); ``` ### `onWebSocket` [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. See [WebSocket Handler](/docs/actors/websocket-handler) for more details. ```typescript const realtimeActor = actor(, onWebSocket: (c, websocket) => )); // Handle incoming messages websocket.addEventListener("message", (event) => )); } }); // Handle connection close websocket.addEventListener("close", () => ); }, actions: }); ``` ### `onBeforeActionResponse` [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. ```typescript const loggingActor = actor(, onBeforeActionResponse: (c, actionName, args, output) => called with args:`, args); console.log(`Action $ returned:`, output); // Add metadata to all responses return }; }, actions: , lastActive: Date.now() }; }, getStats: (c) => ; } } }); ``` ## Options The `options` object allows you to configure various timeouts and behaviors for your actor. ```typescript const myActor = actor(, options: , actions: }); ``` | Option | Default | Description | |--------|---------|-------------| | `createVarsTimeout` | 5000ms | Timeout for `createVars` function | | `createConnStateTimeout` | 5000ms | Timeout for `createConnState` function | | `onConnectTimeout` | 5000ms | Timeout for `onConnect` hook | | `onSleepTimeout` | 5000ms | Timeout for `onSleep` hook | | `onDestroyTimeout` | 5000ms | Timeout for `onDestroy` hook | | `stateSaveInterval` | 10000ms | Interval for persisting state | | `actionTimeout` | 60000ms | Timeout for action execution | | `waitUntilTimeout` | 15000ms | Max time to wait for background promises during shutdown | | `connectionLivenessTimeout` | 2500ms | Timeout for connection liveness check | | `connectionLivenessInterval` | 5000ms | Interval for connection liveness check | | `noSleep` | false | Prevent actor from sleeping | | `sleepTimeout` | 30000ms | Time before actor sleeps due to inactivity | | `canHibernateWebSocket` | false | Whether WebSockets can hibernate (experimental) | ## Advanced ### Running Background Tasks The `c.runInBackground` method allows you to execute promises asynchronously without blocking the actor's main execution flow. The actor is prevented from sleeping while the promise passed to `runInBackground` is still active. This is useful for fire-and-forget operations where you don't need to wait for completion. Common use cases: - **Analytics and logging**: Send events to external services without delaying responses - **State sync**: Populate external databases or APIs with updates to actor state in the background ```typescript const gameRoom = actor(, scores: }, actions: ; // Send analytics event without blocking c.runInBackground( fetch('https://analytics.example.com/events', ) }).then(() => console.log('Analytics sent')) ); return ; }, } }); ``` ### Actor Shutdown Abort Signal The `c.abortSignal` provides an `AbortSignal` that fires when the actor is stopping. Use this to cancel ongoing operations when the actor sleeps or is destroyed. ```typescript const chatActor = actor(), signal: c.abortSignal }); return await response.json(); } } }); ``` See [Canceling Long-Running Actions](/docs/actors/actions#canceling-long-running-actions) for manually canceling operations on-demand. ### Using `ActorContext` Type Externally When extracting logic from lifecycle hooks or actions into external functions, you'll often need to define the type of the context parameter. Rivet provides helper types that make it easy to extract and pass these context types to external functions. ```typescript const myActor = actor(, // Use external function in lifecycle hook onWake: (c) => logActorStarted(c) }); // Simple external function with typed context function logActorStarted(c: ActorContextOf) `); } ``` See [Types](/docs/actors/types) for more details on using `ActorContextOf`. ## Full Example ```typescript interface CounterInput interface CounterState interface ConnParams interface ConnState const counter = actor(), // Initialize actor (run setup that doesn't affect initial state) onCreate: (c, input: CounterInput) => " initialized`); // Set up external resources, logging, etc. }, // Dynamically create connection state from params createConnState: (c, params: ConnParams): ConnState => ; }, // Lifecycle hooks onWake: (c) => " started with count:`, c.state.count); }, onStateChange: (c, newState) => ); }, onBeforeConnect: (c, params: ConnParams) => console.log(`User $ attempting to connect`); }, onConnect: (c, conn) => connected to "$"`); }, onDisconnect: (c, conn) => disconnected from "$"`); }, // Transform all action responses onBeforeActionResponse: (c, actionName, args, output) => }; }, // Define actions actions: , getInfo: (c) => (), } }); default counter; ``` ## Metadata # Metadata Metadata provides information about the currently running actor. ## Actor ID Get the unique instance ID of the actor: ```typescript const actorId = c.actorId; ``` ## Actor Name Get the actor type name: ```typescript const actorName = c.name; ``` This is useful when you need to know which actor type is running, especially if you have generic utility functions that are shared between different actor implementations. ## Actor Key Get the actor key used to identify this actor instance: ```typescript const actorKey = c.key; ``` The key is used to route requests to the correct actor instance and can include parameters passed when creating the actor. Learn more about using keys for actor addressing and configuration in the [keys documentation](/docs/actors/keys). ## Region Region can be accessed from the context object via `c.region`. ```typescript const region = c.region; ``` `c.region` is only supported on Rivet at the moment. ## Example Usage ```typescript } const chatRoom = actor(, actions: ; } } }); const registry = setup( }); ``` ```typescript } const client = createClient("http://localhost:8080"); // Connect to a chat room const chatRoom = await client.chatRoom.get("general"); // Get actor metadata const metadata = await chatRoom.getMetadata(); 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 ## Node.js & Bun Quickstart # Node.js & Bun Quickstart Get started with Rivet Actors in Node.js and Bun ```sh npm install rivetkit ``` Create a simple counter actor: ```ts } const counter = actor(, actions: , }, }); const registry = setup(, }); ``` Choose your preferred web framework: ```ts } // Start Rivet const = registry.start(); ``` ```ts } // Start Rivet const = registry.start(); // Setup Hono app const app = new Hono(); // Example API endpoint app.post("/increment/:name", async (c) => ); }); // Start server serve(app); ``` ```ts } // Start Rivet const = registry.start(); // Setup Express app const app = express(); app.use(express.json()); // Example API endpoints app.post("/increment/:name", async (req, res) => = req.params; const counter = client.counter.getOrCreate(name); const newCount = await counter.increment(1); res.json(); }); app.listen(8080, () => ); ``` ```ts } // Start Rivet const = registry.start(); // Setup Elysia app const app = new Elysia() .mount("/registry", handler) .post("/increment/:name", async () => = params; const counter = client.counter.getOrCreate(name); const newCount = await counter.increment(1); return ; }) .listen(8080); console.log("Server running at http://localhost:8080"); ``` ```sh } npx tsx --watch server.ts ``` ```sh } bun --watch server.ts ``` Your server is now running at `http://localhost:8080` This code can run either in your frontend or within your backend: ```ts } // Create typed client const client = createClient("http://localhost:8080"); // Use the counter actor directly const counter = client.counter.getOrCreate(["my-counter"]); // Call actions const count = await counter.increment(3); console.log("New count:", count); // Get current state const currentCount = await counter.getCount(); console.log("Current count:", currentCount); // Listen to real-time events const connection = counter.connect(); connection.on("countChanged", (newCount) => ); // Increment through connection await connection.increment(1); ``` See the [JavaScript client documentation](/docs/clients/javascript) for more information. ```tsx } const = createRivetKit(); function Counter() ); counter.useEvent("newCount", (x: number) => setCount(x)); const increment = async () => ; return ( Count: Increment ); } ``` See the [React documentation](/docs/clients/react) for more information. ```rust } use rivetkit_client::; use serde_json::json; #[tokio::main] async fn main() -> Result> ", count); }).await; // Call increment action let result = counter.action("increment", vec![json!(1)]).await?; println!("New count: ", result); Ok(()) } ``` See the [Rust client documentation](/docs/clients/rust) for more information. ## Cloudflare Workers Quickstart # Cloudflare Workers Quickstart Get started with Rivet Actors on Cloudflare Workers with Durable Objects ```sh npm install rivetkit @rivetkit/cloudflare-workers ``` Create a simple counter actor: ```ts } const counter = actor(, actions: , }, }); const registry = setup(, }); ``` Choose your preferred web framework: ```ts } // The `/rivet` endpoint is automatically exposed here for external clients const = createHandler(registry); ; ``` ```ts } const app = new Hono } }>(); app.post("/increment/:name", async (c) => ); }); // The `/rivet` endpoint is automatically exposed here for external clients const = createHandler(registry, ); ; ``` ```ts } // The `/rivet` endpoint is automatically mounted on this router for external clients const = createHandler(registry, ), , }); } return new Response("Not Found", ); } }); ; ``` ```ts } const = createInlineClient(registry); // IMPORTANT: Your Durable Object must be exported here ; default ), , }); } // Optional: Mount /rivet path to access actors from external clients if (url.pathname.startsWith("/rivet")) return new Response("Not Found", ); }, } satisfies ExportedHandler; ``` Configure your `wrangler.json` for Cloudflare Workers: ```json } ], "durable_objects": ] }, "kv_namespaces": [ ] } ``` Start the development server: ```sh wrangler dev ``` Your server is now running at `http://localhost:8787` Test your counter actor using HTTP requests: ```ts } // Increment counter const response = await fetch("http://localhost:8787/increment/my-counter", ); const result = await response.json(); console.log("Count:", result.count); // 1 ``` ```sh curl # Increment counter curl -X POST http://localhost:8787/increment/my-counter ``` Deploy to Cloudflare's global edge network: ```bash wrangler deploy ``` Your actors will now run on Cloudflare's edge with persistent state backed by Durable Objects. See the [Cloudflare Workers deployment guide](/docs/connect/cloudflare-workers) for detailed deployment instructions and configuration options. ## Configuration Options ### Connect Frontend To The Rivet Actor Create a type-safe client to connect from your frontend: ```ts } // Create typed client (use your deployed URL) const client = createClient("https://your-app.workers.dev/rivet"); // Use the counter actor directly const counter = client.counter.getOrCreate(["my-counter"]); // Call actions const count = await counter.increment(3); console.log("New count:", count); // Get current state const currentCount = await counter.getCount(); console.log("Current count:", currentCount); // Listen to real-time events const connection = counter.connect(); connection.on("countChanged", (newCount) => ); // Increment through connection await connection.increment(1); ``` See the [JavaScript client documentation](/docs/clients/javascript) for more information. ```tsx } const = createRivetKit("https://your-app.workers.dev/rivet"); function Counter() ); counter.useEvent("newCount", (x: number) => setCount(x)); const increment = async () => ; return ( Count: Increment ); } ``` See the [React documentation](/docs/clients/react) for more information. ```rust } use rivetkit_client::; use serde_json::json; #[tokio::main] async fn main() -> Result> ", count); }).await; // Call increment action let result = counter.action("increment", vec![json!(1)]).await?; println!("New count: ", result); Ok(()) } ``` See the [Rust client documentation](/docs/clients/rust) for more information. Cloudflare Workers mounts the Rivet endpoint on `/rivet` by default. ## Quickstart # Quickstart Set up actors with Node.js, Bun, and web frameworks Build real-time React applications with actors Build server-rendered Next.js experiences backed by actors Deploy actors on Cloudflare Workers with zero infrastructure ## Next.js Quickstart # Next.js Quickstart Get started with Rivet Actors in Next.js ```sh npx create-next-app@latest my-app cd my-app ``` Create a file at `src/rivet/registry.ts` with a simple counter actor: ```ts } const counter = actor(, actions: , }, }); const registry = setup(, }); ``` Create a file at `src/app/api/rivet/[...all]/route.ts` to setup the API routes: ```ts } const maxDuration = 300; const = toNextHandler(registry); ``` Create a file at `src/components/Counter.tsx` to use the actor in a component: ```tsx } "use client"; const = createRivetKit( process.env.NEXT_RIVET_ENDPOINT ?? "http://localhost:3000/api/rivet", ); function Counter() ); counter.useEvent("newCount", (x: number) => setCount(x)); const increment = async () => ; return ( Count: Increment ); } ``` Import the `Counter` component in your page or layout to use it. For more examples on connecting to your actors using React, check the [React documentation for RivetKit](/docs/clients/react). See the [Vercel deployment guide](/docs/connect/vercel) for detailed instructions on deploying your RivetKit app to Vercel. ## API Reference For detailed information about the Next.js client API, see the [React Client API Reference](/docs/clients/react). ## React Quickstart # React Quickstart Build real-time React applications with Rivet Actors ```sh npm install rivetkit @rivetkit/react ``` Create your actor registry on the backend: ```ts } const counter = actor(, actions: , }, }); const registry = setup(, }); ``` Start a server to run your actors: ```ts } // Run server with default configuration registry.start(); ``` Set up your React application: ```tsx } const = createRivetKit(); function App() ); counter.useEvent("newCount", (x: number) => setCount(x)); const increment = async () => ; return ( Rivet Counter Count: Counter Name: setCounterName(e.target.value)} style=} /> Increment Connection Status: Try opening multiple tabs to see real-time sync. ); } default App; ``` Configure Vite for development: ```ts } default defineConfig(, }) ``` Start both the backend and frontend: **Terminal 1**: Start the backend ```sh Backend npx tsx --watch backend/server.ts ``` **Terminal 2**: Start the frontend ```sh Frontend npx vite ``` Open `http://localhost:5173` in your browser. Try opening multiple tabs to see real-time sync in action. ## Configuration Options ### Add Your Own Backend Endpoints Add custom HTTP endpoints alongside your actors to handle additional business logic, authentication, and integrations with external services. See [backend quickstart](/docs/actors/quickstart/backend) for more information. ## API Reference For detailed information about the React client API, see the [React Client API Reference](/docs/clients/react). ## Low-Level HTTP Request Handler # Low-Level HTTP Request Handler Actors can handle HTTP requests through the `onRequest` handler. For most use cases, [actions](/docs/actors/actions) provide high-level API powered by HTTP that's easier to work with than low-level HTTP. However, low-level handlers are required when implementing custom use cases or integrating external libraries that need direct access to the underlying HTTP `Request`/`Response` objects or WebSocket connections. ## Handling HTTP Requests The `onRequest` handler processes HTTP requests sent to your actor. It receives the actor context and a standard `Request` object and returns a `Response` object. ```typescript const counterActor = actor(, // WinterTC compliant - accepts standard Request and returns standard Response onRequest: (c, request) ); } if (request.method === "POST" && url.pathname === "/increment") ); } return new Response("Not Found", ); }, }); ``` ```typescript function buildRouter(c: ActorContext) ); }); app.post("/increment", (c) => ); }); return app; } const counterActor = actor(, createVars: (c) => ; }, async onRequest: (c, request) , }); ``` See also the [raw fetch handler example](https://github.com/rivet-dev/rivet/tree/main/examples/raw-fetch-handler). ## Sending Requests To Actors ### Via RivetKit Client Use the `.fetch()` method on an actor handle to send HTTP requests to the actor's `onRequest` handler. This can be executed from either your frontend or backend. ```typescript const client = createClient(); const actor = client.counter.getOrCreate("my-counter"); // .fetch() is WinterTC compliant, it accepts standard Request and returns standard Response const response = await actor.fetch("/increment", ); const data = await response.json(); console.log(data); // ``` ### Via HTTP API This handler can be accessed with raw HTTP using `https://api.rivet.dev/gateway//request/`. For example, to call `POST /increment` on the counter actor above: ```typescript const response = await fetch( `https://api.rivet.dev/gateway/$/request/increment`, `, }, } ); const data = await response.json(); console.log(data); // ``` ```bash curl -X POST "https://api.rivet.dev/gateway//request/increment" \ -H "Authorization: Bearer " ``` The request is routed to the actor's `onRequest` handler where: - `request.method` is `"POST"` - `request.url` ends with `/increment` (the path after `/request/`) - Headers, body, and other request properties are passed through unchanged See the [HTTP API reference](/docs/actors/http-api) for more information on HTTP routing and authentication. ### Via Proxying Requests You can proxy HTTP requests from your own server to actor handlers using the RivetKit client. This is useful when you need to add custom authentication, rate limiting, or request transformation before forwarding to actors. ```typescript const client = createClient(); const app = new Hono(); // Proxy requests to actor's onRequest handler app.all("/actors/:id/:path", async (c) => ); serve(app); ``` ## Connection & Lifecycle Hooks `onRequest` will trigger the `onBeforeConnect`, `onConnect`, and `onDisconnect` hooks. Read more about [lifecycle hooks](/docs/actors/lifecycle). Requests in flight will be listed in `c.conns`. Read more about [connections](/docs/actors/connections). ## WinterTC Compliance The `onRequest` handler is WinterTC compliant and will work with existing libraries using the standard `Request` and `Response` types. ## Limitations - 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 ## Scaling & Concurrency # Scaling & Concurrency This page has moved to [design patterns](/docs/actors/design-patterns). ## Schedule # Schedule Scheduling is used to trigger events in the future. The actor scheduler is like `setTimeout`, except the timeout will persist even if the actor restarts, upgrades, or crashes. Scheduling is supported on the Rivet Cloud, Cloudflare Workers, file system, and memory drivers. Follow [this issue](https://github.com/rivet-dev/rivetkit/issues/1095) for Redis support. ## Use Cases Scheduling is helpful for long-running timeouts like month-long billing periods or account trials. ## Scheduling ### `c.schedule.after(duration, actionName, ...args)` Schedules a function to be executed after a specified duration. This function persists across actor restarts, upgrades, or crashes. Parameters: - `duration` (number): The delay in milliseconds. - `actionName` (string): The name of the action to be executed. - `...args` (unknown[]): Additional arguments to pass to the function. ### `c.schedule.at(timestamp, actionName, ...args)` Schedules a function to be executed at a specific timestamp. This function persists across actor restarts, upgrades, or crashes. Parameters: - `timestamp` (number): The exact time in milliseconds since the Unix epoch when the function should be executed. - `actionName` (string): The name of the action to be executed. - `...args` (unknown[]): Additional arguments to pass to the function. ## Full Example ```typescript const reminderService = actor( }, actions: ; // Schedule the sendReminder action to run after the delay c.schedule.after(delayMs, "sendReminder", reminderId); return ; }, sendReminder: (c, reminderId) => ); }); } else // Clean up the processed reminder delete c.state.reminders[reminderId]; } } }); ``` ## Sharing and Joining State # Sharing and Joining State This page has moved to [design patterns](/docs/actors/design-patterns). ## State # State Actor state provides the best of both worlds: it's stored in-memory and persisted automatically. This lets you work with the data without added latency while still being able to survive crashes & upgrades. Actors can also be used with external SQL databases. This can be useful to integrate actors with existing applications or for storing relational data. Read more [here](/docs/actors/external-sql). ## Initializing State There are two ways to define an actor's initial state: Define an actor state as a constant value: ```typescript // Simple state with a constant const counter = actor(, actions: }); ``` This value will be cloned for every new actor using `structuredClone`. Create actor state dynamically on each actors' creation: ```typescript // State with initialization logic const counter = actor(; }, actions: }); ``` To accept a custom input parameters for the initial state, use: ```typescript interface CounterInput // State with initialization logic const counter = actor(; }, actions: }); ``` Read more about [input parameters](/docs/actors/input) here. If accepting arguments to `createState`, you **must** define the types: `createState(c: CreateContext, input: MyType)` Otherwise, the return type will not be inferred and `c.state` will be of type `unknown`. The `createState` function is called once when the actor is first created. See [Lifecycle](/docs/actors/lifecycle) for more details. ## Modifying State To update state, modify the `state` property on the context object (`c.state`) in your actions: ```typescript const counter = actor(, actions: , add: (c, value) => } }); ``` Only state stored in the `state` object will be persisted. Any other variables or properties outside of this are not persisted. ## State Saves Actors automatically handle persisting state transparently. This happens at the end of every action if the state has changed. State is also automatically saved after `onFetch` and `onWebSocket` handlers finish executing. For `onWebSocket` handlers specifically, you'll need to manually save state using `c.saveState()` while the WebSocket connection is open if you want state changes to be persisted immediately. This is because WebSocket connections can remain open for extended periods, and state changes made during event handlers (like `message` events) won't be automatically saved until the connection closes. In other cases where you need to force a state change mid-action, you can use `c.saveState()`. This should only be used if your action makes an important state change that needs to be persisted before the action completes. ```typescript const criticalProcess = actor(, actions: `); // Force save state before the async operation c.saveState(); // Long-running operation that might fail await someRiskyOperation(); // Update state again c.state.steps.push(`Completed step $`); return c.state.currentStep; } } }); ``` ## State Isolation Each actor's state is completely isolated, meaning it cannot be accessed directly by other actors or clients. To interact with an actor's state, you must use [Actions](/docs/actors/actions). Actions provide a controlled way to read from and write to the state. If you need a shared state between multiple actors, see [sharing and joining state](/docs/actors/sharing-and-joining-state). ## Ephemeral Variables In addition to persisted state, actors can store ephemeral data that is not saved to permanent storage using `vars`. This is useful for temporary data or non-serializable objects like database connections or event emitters. For complete documentation on ephemeral variables, see [Ephemeral Variables](/docs/actors/ephemeral-variables). ## Type Limitations State is currently constrained to the following types: - `null` - `undefined` - `boolean` - `string` - `number` - `BigInt` - `Date` - `RegExp` - `Error` - Typed arrays (`Uint8Array`, `Int8Array`, `Float32Array`, etc.) - `Map` - `Set` - `Array` - Plain objects ## API Reference - [`CreateContext`](/typedoc/types/rivetkit.mod.CreateContext.html) - Context available during actor state creation - [`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 ## Testing # Testing Rivet provides a straightforward testing framework to build reliable and maintainable applications. This guide covers how to write effective tests for your actor-based services. ## Setup To set up testing with Rivet: ```bash # Install Vitest npm install -D vitest # Run tests npm test ``` ## Basic Testing Setup Rivet includes a test helper called `setupTest` that configures a test environment with in-memory drivers for your actors. This allows for fast, isolated tests without external dependencies. ```ts tests/my-actor.test.ts test("my actor test", async (test) => = await setupTest(test, app); // Now you can interact with your actor through the client const myActor = await client.myActor.get(); // Test your actor's functionality await myActor.someAction(); // Make assertions const result = await myActor.getState(); expect(result).toEqual("updated"); }); ``` ```ts src/index.ts const myActor = actor(, actions: , getState: (c) => } }); const registry = setup( }); ``` ## Testing Actor State The test framework uses in-memory drivers that persist state within each test, allowing you to verify that your actor correctly maintains state between operations. ```ts tests/counter.test.ts test("actor should persist state", async (test) => = await setupTest(test, app); const counter = await client.counter.get(); // Initial state expect(await counter.getCount()).toBe(0); // Modify state await counter.increment(); // Verify state was updated expect(await counter.getCount()).toBe(1); }); ``` ```ts src/index.ts const counter = actor(, actions: , getCount: (c) => } }); const registry = setup( }); ``` ## Testing Events For actors that emit events, you can verify events are correctly triggered by subscribing to them: ```ts tests/chat-room.test.ts test("actor should emit events", async (test) => = await setupTest(test, app); const chatRoom = await client.chatRoom.get(); // Set up event handler with a mock function const mockHandler = vi.fn(); chatRoom.on("newMessage", mockHandler); // Trigger the event await chatRoom.sendMessage("testUser", "Hello world"); // Wait for the event to be emitted await vi.waitFor(() => ); }); ``` ```ts src/index.ts const chatRoom = actor(, actions: ); c.broadcast("newMessage", username, message); }, getHistory: (c) => , }, }); // Create and the app const registry = setup( }); ``` ## Testing Schedules Rivet's schedule functionality can be tested using Vitest's time manipulation utilities: ```ts tests/scheduler.test.ts test("scheduled tasks should execute", async (test) => = await setupTest(test, app); const scheduler = await client.scheduler.get(); // Set up a scheduled task await scheduler.scheduleTask("reminder", 60000); // 1 minute in the future // Fast-forward time by 1 minute await vi.advanceTimersByTimeAsync(60000); // Verify the scheduled task executed expect(await scheduler.getCompletedTasks()).toContain("reminder"); }); ``` ```ts src/index.ts const scheduler = actor(, actions: ; }, completeTask: (c, taskName: string) => ; }, getCompletedTasks: (c) => } }); const registry = setup( }); ``` The `setupTest` function automatically calls `vi.useFakeTimers()`, allowing you to control time in your tests with functions like `vi.advanceTimersByTimeAsync()`. This makes it possible to test scheduled operations without waiting for real time to pass. ## Best Practices 1. **Isolate tests**: Each test should run independently, avoiding shared state. 2. **Test edge cases**: Verify how your actor handles invalid inputs, concurrent operations, and error conditions. 3. **Mock time**: Use Vitest's timer mocks for testing scheduled operations. 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 ## Types # Types TypeScript types for working with Rivet Actors. This page covers context types used in lifecycle hooks and actions, as well as helper types for extracting types from actor definitions. ## Context Types Context types define what properties and methods are available in different parts of the actor lifecycle. ```typescript const counter = actor(, // CreateContext in createState hook createState: (c, input: ) => ; }, // ActionContext in actions actions: } }); ``` ### Extracting Context Types When writing helper functions that work with actor contexts, use context extractor types like `CreateContextOf` or `ActionContextOf` to extract the appropriate context type from your actor definition. ```typescript const gameRoom = actor(, createState: (c, input: ) => ; }, actions: } }); // Extract CreateContext type for createState hook function initializeRoom( context: CreateContextOf, roomId: string ) `); // context.state is not available here (being created) // context.vars is not available here (not created yet) } // Extract ActionContext type for actions function validatePlayer( context: ActionContextOf, playerId: string ) } ``` ## Low-Level WebSocket Handler # Low-Level WebSocket Handler Actors can handle WebSocket connections through the `onWebSocket` handler. For most use cases, [actions](/docs/actors/actions) and [events](/docs/actors/events) provide high-level connection handling powered by WebSockets that's easier to work with than low-level WebSockets. However, low-level handlers are required when implementing custom use cases. ## Handling WebSocket Connections The `onWebSocket` handler manages low-level WebSocket connections. It receives the actor context and a `WebSocket` object. ```typescript const chatActor = actor(, onWebSocket: (c, websocket) )); }); websocket.addEventListener("message", (event) => ); }, }); ``` See also the [raw WebSocket handler example](https://github.com/rivet-dev/rivet/tree/main/examples/raw-websocket-handler). ## Connecting To Actors ### Via RivetKit Client Use the `.websocket()` method on an actor handle to open a WebSocket connection to the actor's `onWebSocket` handler. This can be executed from either your frontend or backend. ```typescript const client = createClient(); const actor = client.chat.getOrCreate("my-chat"); // Open WebSocket connection const ws = await actor.websocket("/"); // Listen for messages ws.addEventListener("message", (event) => ); // Send messages ws.send(JSON.stringify()); ``` The `.websocket()` method returns a standard WebSocket. ### Via HTTP API This handler can be accessed with raw WebSockets using `wss://api.rivet.dev/gateway/@/websocket/`. For example, to connect to the chat actor above: ```typescript const ws = new WebSocket( `wss://api.rivet.dev/gateway/$@$/websocket/` ); ws.addEventListener("message", (event) => ); ws.addEventListener("open", () => )); }); ``` ```bash wscat -c "wss://api.rivet.dev/gateway/@/websocket/" ``` The path after `/websocket/` is passed to your `onWebSocket` handler and can be used to route to different functionality within your actor. For example, to connect with a custom path `/admin`: ```typescript const ws = new WebSocket( `wss://api.rivet.dev/gateway/$@$/websocket/admin` ); ``` ```bash wscat -c "wss://api.rivet.dev/gateway/@/websocket/admin" ``` See the [HTTP API reference](/docs/actors/http-api) for more information on WebSocket routing and authentication. ### Via Proxying Connections You can proxy WebSocket connections from your own server to actor handlers using the RivetKit client. This is useful when you need to add custom authentication or connection management before forwarding to actors. ```typescript const client = createClient(); const app = new Hono(); // Proxy WebSocket connections to actor's onWebSocket handler app.get("/ws/:id", upgradeWebSocket(async (c) => ); actorWs.addEventListener("close", () => ); }, onMessage: (evt, ws) => , onClose: () => , }; })); default app; ``` See also the [raw WebSocket handler with proxy example](https://github.com/rivet-dev/rivet/tree/main/examples/raw-websocket-handler-proxy). ## Connection & Lifecycle Hooks `onWebSocket` will trigger the `onBeforeConnect`, `onConnect`, and `onDisconnect` hooks. Read more about [lifecycle hooks](/docs/actors/lifecycle). Open WebSockets will be listed in `c.conns`. `conn.send` and `c.broadcast` have no effect on low-level WebSocket connections. Read more about [connections](/docs/actors/connections). ## WinterTC Compliance The `onWebSocket` handler uses standard WebSocket APIs and will work with existing libraries expecting WinterTC-compliant WebSocket objects. ## Advanced ## WebSocket Hibernation WebSocket hibernation allows actors to go to sleep while keeping WebSocket connections alive. Actors automatically wake up when a message is received or the connection closes. Enable hibernation by setting `canHibernateWebSocket: true`. You can also pass a function `(request) => boolean` for conditional control. ```typescript const myActor = actor(, }); ``` Since `open` only fires once when the client first connects, use `c.conn.state` to store per-connection data that persists across sleep cycles. See [connections](/docs/actors/connections) for more details. ### Accessing the Request The underlying HTTP request is available via `c.request`. This is useful for accessing the path or query parameters. ```typescript onWebSocket: (c, websocket) ``` ### Async Handlers The `onWebSocket` handler can be async, allowing you to perform async code before setting up event listeners: ```typescript onWebSocket: async (c, websocket) => )); }); websocket.addEventListener("message", (event) => ); } ``` ## 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 ## Node.js & Bun # Node.js & Bun The Rivet JavaScript client allows you to connect to and interact with actors from browser and Node.js applications. ## Getting Started See the [backend quickstart guide](/docs/actors/quickstart/backend) for getting started. ## API Reference **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 ## Next.js # Next.js The Rivet Next.js client allows you to connect to and interact with actors in Next.js applications. Check out the complete example Use Next.js API routes to run RivetKit Registry ## Getting Started See the [Next.js quickstart guide](/docs/actors/quickstart/next-js) for getting started. ## API Reference **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 ## React # React Learn how to create real-time, stateful React applications with Rivet's actor model. The React integration provides intuitive hooks for managing actor connections and real-time updates. ## Getting Started See the [React quickstart guide](/docs/actors/quickstart/react) for getting started. ## API Reference ### `createRivetKit(endpoint?, options?)` Creates the Rivet hooks for React integration. ```tsx const = createRivetKit(); ``` #### Parameters - `endpoint`: Optional endpoint URL (defaults to `http://localhost:6420` or `process.env.RIVET_ENDPOINT`) - `options`: Optional configuration object #### Returns An object containing: - `useActor`: Hook for connecting to actors ### `useActor(options)` Hook that connects to an actor and manages the connection lifecycle. ```tsx const actor = useActor(, enabled: true }); ``` #### Parameters - `options`: Object containing: - `name`: The name of the actor type (string) - `key`: Array of strings identifying the specific actor instance - `params`: Optional parameters passed to the actor connection - `createWithInput`: Optional input to pass to the actor on creation - `createInRegion`: Optional region to create the actor in if does not exist - `enabled`: Optional boolean to conditionally enable/disable the connection (default: true) #### Returns Actor object with the following properties: - `connection`: The actor connection for calling actions, or `null` if not connected - `connStatus`: The connection status (`"idle"`, `"connecting"`, `"connected"`, or `"disconnected"`) - `error`: Error object if the connection failed, or `null` - `useEvent(eventName, handler)`: Method to subscribe to actor events ### `actor.useEvent(eventName, handler)` Subscribe to events emitted by the actor. ```tsx const actor = useActor(); actor.useEvent("newCount", (count: number) => ); ``` #### Parameters - `eventName`: The name of the event to listen for (string) - `handler`: Function called when the event is emitted #### Lifecycle The event subscription is automatically managed: - Subscribes when the actor connects - Cleans up when the component unmounts or actor disconnects - Re-subscribes on reconnection ## Advanced Patterns ### Multiple Actors Connect to multiple actors in a single component: ```tsx function Dashboard() ); const notifications = useActor(); userProfile.useEvent("profileUpdated", (profile) => ); notifications.useEvent("newNotification", (notification) => ); return ( ); } ``` ### Conditional Connections Control when actors connect using the `enabled` option: ```tsx function ConditionalActor() ); return ( setEnabled(!enabled)}> ); } ``` ### Real-time Collaboration Build collaborative features with multiple event listeners: ```tsx function CollaborativeEditor() ); const document = useActor( }); // Listen for content changes document.useEvent("contentChanged", (newContent) => ); // Listen for cursor movements document.useEvent("cursorMoved", () => )); }); // Listen for user join/leave document.useEvent("userJoined", () => joined the document`); }); document.useEvent("userLeft", () => = prev; return rest; }); }); const updateContent = async (newContent: string) => ; return ( ); } ``` ### Authentication Connect authenticated actors in React: ```tsx function AuthenticatedApp() , enabled: !!authToken // Only connect when authenticated }); const login = async () => ; if (!authToken) return ( Authenticated Counter ); } ``` 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 ## Rust # Rust The Rivet Rust client provides a way to connect to and interact with actors from Rust applications. ## Quickstart Add RivetKit client to your `Cargo.toml`: ```toml [dependencies] rivetkit-client = "0.1.0" ``` Make sure you have a running Rivet actor server to connect to. You can follow the [Node.js & Bun Quickstart](https://rivet.dev/docs/actors/quickstart/backend/) to set up a simple actor server. ```rust src/main.rs use rivetkit_client::; use serde_json::json; #[tokio::main] async fn main() -> anyhow::Result : ", username, message); }).await; // Send message to room chat_room.action("sendMessage", vec![ json!("william"), json!("All the world's a stage.") ]).await?; // When finished client.disconnect(); Ok(()) } ``` In a separate terminal, run your client code: ```sh cargo run ``` Run it again to see the state update. ## API Reference For detailed API documentation, please refer to the [RivetKit Rust client implementation](https://github.com/rivet-dev/rivet/tree/main/rivetkit-rust/packages/client). ## Deploying to AWS ECS # Deploying to AWS ECS Run your backend on Amazon ECS with Fargate. See the [AWS ECS template](https://github.com/rivet-dev/template-aws-ecs) for deployment instructions: - [Deploy with Terraform](https://github.com/rivet-dev/template-aws-ecs#option-a-deploy-with-terraform) - [Deploy with AWS CLI](https://github.com/rivet-dev/template-aws-ecs#option-b-deploy-with-aws-cli) ## Deploy To Amazon Web Services Lambda # Deploy To Amazon Web Services Lambda _AWS Lambda is coming soon_ ## Deploying to Cloudflare Workers # Deploying to Cloudflare Workers Deploy your Cloudflare Workers + RivetKit app to [Cloudflare Workers](https://workers.cloudflare.com/). Minimal Cloudflare Workers + RivetKit example. Cloudflare Workers with Hono router. Advanced setup using createInlineClient. ## Guide - [Cloudflare account](https://dash.cloudflare.com/) with Durable Objects enabled - [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/) v3 - A Cloudflare Worker app integrated with RivetKit - See the [Cloudflare Workers quickstart](/docs/actors/quickstart/cloudflare-workers/) or [Cloudflare Workers example](https://github.com/rivet-dev/rivet/tree/main/examples/cloudflare-workers) to get started Your project should have the following files: - `src/index.ts` (or similar entry point with `createHandler`) - `src/registry.ts` (or similar actor registry file) - `wrangler.json` with proper Durable Objects and KV namespace configuration If your project is not integrated with RivetKit yet, follow the [Cloudflare Workers quickstart guide](/docs/actors/quickstart/cloudflare-workers/) or see the [Cloudflare Workers example](https://github.com/rivet-dev/rivet/tree/main/examples/cloudflare-workers). Deploy to Cloudflare's global network: ```sh wrangler deploy ``` Your worker will be deployed and you'll receive a URL like `https://my-rivetkit-worker.workers.dev`. More information on deployments is available in [Cloudflare's docs](https://developers.cloudflare.com/workers/wrangler/commands/#deploy). After running `wrangler deploy`, note the URL printed in the output (e.g., `https://my-rivetkit-worker.workers.dev`). Your RivetKit endpoint will be available at this URL with `/rivet` appended: - Example: `https://my-rivetkit-worker.workers.dev/rivet` Use this endpoint URL when configuring your RivetKit client or connecting from the Rivet dashboard. ## Advanced ### Accessing Environment Bindings You can access Cloudflare Workers environment bindings directly using the importable `env`: ```typescript // Access environment variables and secrets in top-level scope const API_KEY = env.API_KEY; const LOG_LEVEL = env.LOG_LEVEL || "info"; // Use bindings in your actor const myActor = actor(, actions: } } }); ``` ### Driver Context The Cloudflare Workers driver provides access to the Durable Object state and environment through the driver context in `createVars`. ```typescript const myActor = actor(, // Save the Cloudflare driver context createVars: (ctx: CreateVarsContext, driver: DriverContext) => (), actions: , } }); ``` The Cloudflare Workers driver context type is exported as `DriverContext` from `@rivetkit/cloudflare-workers`: ```typescript interface DriverContext ``` While you have access to the Durable Object state, be cautious when directly modifying KV storage or alarms, as this may interfere with RivetKit's internal operations and potentially break actor functionality. ## Deploying to Freestyle # Deploying to Freestyle Deploy RivetKit app to [Freestyle.sh](https://freestyle.sh/), a cloud platform for running AI-generated code with built-in security and scalability. Freestyle provides built-in security for running untrusted AI-generated code, making it ideal for AI agent applications. Using Rivet, it is easy to deploy your vibe-coded or user-provided RivetKit backends straight to Freestyle. Complete example of deploying RivetKit app to Freestyle.sh. ## Setup Install RivetKit and Hono and create your registry: ```bash npm install rivetkit hono ``` Update your server code to run the registry serverless with Deno. ```typescript } const = registry.start(/api`, }); // Freestyle uses Deno under the hood for web deployments // @ts-ignore Deno.serve(fetch); ``` Deploy your application to Freestyle with the correct configuration. Create a deployment script or add this to your existing deployment process: ```typescript const FREESTYLE_DOMAIN = "my-domain.style.dev"; // Change to your desired Freestyle domain const res = await freestyle.deployWeb(buildDir, `, RIVET_RUNNER_KIND: "serverless", // For self-hosted instances: // RIVET_ENDPOINT: "http://127.0.0.1:6420", RIVET_ENDPOINT: "api.rivet.dev", }, timeout: 60 * 5, // Increases max request lifetime on the runner entrypoint: "server.ts", // File which starts serverless runner domains: [FREESTYLE_DOMAIN], build: false, }); ``` Details on `buildDir` and other settings are available on [Freestyle docs](https://docs.freestyle.sh/web/web). Run this deployment script to push your application to Freestyle. **Deployment Configuration:** - `timeout: 60 * 5` - Set timeout to 5 minutes for actor operations - it's important to keep this high - `entrypoint: "server.ts"` - Entry point file with your serverless setup - `domains` - Your Freestyle domain(s) - `build: false` - Disable build if you're pre-building your assets Update the runner configuration on the Rivet side to connect with your Freestyle deployment. Create a configuration script and run it after your Freestyle deployment is live: ```typescript const rivet = new RivetClient(); const FREESTYLE_DOMAIN = "my-domain.style.dev"; // Change to your desired Freestyle domain const RIVET_NAMESPACE = "my-rivet-namespace"; // Change to your Rivet namespace await rivet.runnerConfigs.upsert("freestyle-runner", /start`, runnersMargin: 1, minRunners: 1, maxRunners: 1, slotsPerRunner: 1, // Must be shorter than Freestyle request `timeout` config requestLifespan: 60 * 5 - 5, }, namespace: RIVET_NAMESPACE, }); ``` Execute this configuration script to register your Freestyle deployment with Rivet. **Runner Configuration:** - `url` - Freestyle deployment URL with `/start` endpoint - `runnersMargin` - Buffer of runners to maintain - `minRunners/maxRunners` - Scaling limits - `slotsPerRunner` - Concurrent actors per runner - `requestLifespan` - Request timeout (slightly less than Freestyle timeout) Once executed, Rivet will be connected to your Freestyle serverless instance. ## Deploying to Google Cloud Run # Deploying to Google Cloud Run Run your backend on Cloud Run with a lightweight container image and one command deploy. ## Guide - Google Cloud project with Cloud Run and Artifact Registry enabled - `gcloud` CLI authenticated (`gcloud auth login`) and project set (`gcloud config set project YOUR_PROJECT`) - Artifact Registry repository or Container Registry enabled - Your backend application repository Navigate to Rivet and click _Connect > Manual_. Copy the environment variables provided, they will be used when deploying. They should look something like this: ```bash RIVET_ENDPOINT=https://api-us-west-1.rivet.dev RIVET_NAMESPACE=your-namespace-id RIVET_TOKEN=your-token ``` Create a `Dockerfile` in your project root: ```dockerfile FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY . . ENV PORT=8080 CMD ["node", "server.js"] ``` Use Cloud Build to build and push the image. Replace the region and repository with your own. ```bash gcloud builds submit --tag us-central1-docker.pkg.dev/YOUR_PROJECT/backend/backend:latest ``` Deploy the service to Cloud Run, passing the Rivet environment variables. Adjust the region, image, and VPC connector settings as needed. ```bash gcloud run deploy backend \ --image us-central1-docker.pkg.dev/YOUR_PROJECT/backend/backend:latest \ --region us-central1 \ --allow-unauthenticated \ --min-instances 1 \ --set-env-vars RIVET_ENDPOINT=https://api-us-west-1.rivet.dev,RIVET_NAMESPACE=your-namespace-id,RIVET_TOKEN=your-token ``` You do not need to expose a container port. Rivet tunnels traffic directly to your backend. Confirm the service is running: ```bash gcloud run services describe backend --region us-central1 --format 'value(status.conditions[?type="Ready"].status)' ``` Your runner should appear as connected on the Rivet dashboard once the service reports ready. ## Deploying to Hetzner # Deploying to Hetzner Please see the [VM & Bare Metal](/docs/connect/vm-and-bare-metal) guide. ## Deploy # Deploy Rivet supports deployment to a wide range of platforms, from serverless functions to self-hosted infrastructure. ))} ))} ## Deploying to Kubernetes # Deploying to Kubernetes # Run your backend on any Kubernetes cluster with a simple container image and deployment manifest. ## Guide - A Kubernetes cluster with `kubectl` access (AKS, EKS, GKE, k3s, etc.) - Container registry credentials (Docker Hub, GHCR, GCR, etc.) - Your backend application repository Navigate to Rivet and click _Connect > Manual_. Copy the environment variables provided, they will be used in later manifests. They should look something like this: ```bash RIVET_ENDPOINT=https://api-us-west-1.rivet.dev RIVET_NAMESPACE=your-namespace-id RIVET_TOKEN=your-token ``` Create a `Dockerfile` in your project root: ```dockerfile FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY . . ENV PORT=8080 CMD ["node", "server.js"] ``` ```bash docker build -t registry.example.com/your-team/backend:latest . docker push registry.example.com/your-team/backend:latest ``` Replace `registry.example.com/your-team` with your registry path. Auth with `docker login` first if needed. Create a `backend-secrets.yaml` for your environment variables: ```yaml apiVersion: v1 kind: Secret metadata: name: backend-secrets type: Opaque stringData: RIVET_ENDPOINT: https://api-us-west-1.rivet.dev RIVET_NAMESPACE: your-namespace-id RIVET_TOKEN: your-token ``` Then create a `deployment.yaml`: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: backend spec: replicas: 1 selector: matchLabels: app: backend template: metadata: labels: app: backend spec: containers: - name: backend image: registry.example.com/your-team/backend:latest envFrom: - secretRef: name: backend-secrets ``` You do not need to expose a container port. Rivet tunnels traffic directly to your backend. Apply both manifests: ```bash kubectl apply -f backend-secrets.yaml kubectl apply -f deployment.yaml ``` Add a `Service` or Ingress if you need external access. Check that the pod is running: ```bash kubectl get pods -l app=backend ``` Your runner should appear as connected on the Rivet dashboard once the pod is ready. ## Deploying to Railway # Deploying to Railway # Deploy your RivetKit app to [Railway](https://railway.app). ## Option A: Deploy from Template If you're starting from scratch, go to the Connect tab on the Rivet dashboard and follow the Railway deployment steps. This give you a 1-click deploy of the [RivetKit Railway template](). ## Option B: Deploy Manual - [Railway account](https://railway.app) - Your RivetKit app - If you don't have one, see the [Quickstart](/docs/actors/quickstart) page or our [Examples](https://github.com/rivet-dev/rivet/tree/main/examples) - Access to the [Rivet Cloud](https://dashboard.rivet.dev/) or a [self-hosted Rivet Engine](/docs/general/self-hosting) Ensure your `package.json` has a start script: ```json // ... Rest of package.json } ``` 1. Connect your GitHub account to Railway 2. Select your repository containing your RivetKit app 3. Railway will automatically detect and deploy your app Railway will automatically deploy your RivetKit app on every git push. Detailed steps are available on [Railway's docs](https://docs.railway.com/quick-start). You do not need to expose a container port. Rivet tunnels traffic directly to your backend. After selecting your project on the Rivet dashboard, go to Connect > Railway to get the environment variables needed. To add your environment variables: 1. Go to your Railway project dashboard 2. Select your service 3. Navigate to the **Variables** tab 4. Add the required environment variables and deploy your changes Detailed steps are available on [Railway's docs](https://docs.railway.com/guides/variables#service-variables). You should see your runner connected on the Connect tab of your Rivet Dashboard. Now you can create and connect to your Rivet Actors. ## Supabase # Supabase _Supabase is coming soon_ ## Deploying to Vercel # Deploying to Vercel Deploy your Next.js + RivetKit app to [Vercel](https://vercel.com/). Complete example Next.js + RivetKit app. ## Guide - [Vercel account](https://vercel.com/) - Access to the [Rivet Cloud](https://dashboard.rivet.dev/) or a self-hosted [Rivet Engine](/docs/general/self-hosting) - A Next.js app integrated with RivetKit - See the [Next.js quickstart](/docs/actors/quickstart/next-js/) or [Next.js example](https://github.com/rivet-dev/rivet/tree/main/examples/next-js) to get started - Your project should have the following files: - `src/app/api/rivet/[...all]/route.ts` - `src/rivet/registry.ts` 1. Connect your GitHub project to Vercel 2. Select your repository containing your Next.js + RivetKit app 3. Vercel will deploy your app More information on deployments are available in [Vercel's docs](https://vercel.com/docs/deployments). 1. Visit the [Rivet dashboard](https://dashboard.rivet.dev) 2. Navigate to _Connect > Vercel_ 3. Skip to the _Deploy to Vercel_ step 4. Input your deployed Vercel site URL - e.g. `https://my-app.vercel.app/api/rivet` 5. Once it shows as successfully connected, click _Done_ Your Vercel Functions deployment is now connected to Rivet. ## Deploying to VMs & Bare Metal # Deploying to VMs & Bare Metal Run your backend on any Linux VM or bare metal host with a basic systemd unit. ## Guide - Build your backend locally. - Copy the build output to your server (example): ```bash scp -r ./dist user@server:/opt/backend ``` Place the files somewhere readable by the service user, such as `/opt/backend`. Navigate to Rivet and click _Connect > Manual_. Copy the environment variables provided, they will be used in the next step. They should look something like this: ```bash RIVET_ENDPOINT=https://api-us-west-1.rivet.dev RIVET_NAMESPACE=your-namespace-id RIVET_TOKEN=your-token ``` Create `/etc/systemd/system/backend.service`: ```ini [Unit] Description=Backend Service After=network.target [Service] Type=simple WorkingDirectory=/opt/backend ExecStart=/usr/bin/node server.js Restart=on-failure Environment=RIVET_ENDPOINT=https://api.rivet.dev Environment=RIVET_NAMESPACE=your-namespace-id Environment=RIVET_TOKEN=your-token [Install] WantedBy=multi-user.target ``` Replace the environment values with those from the Connect tab in the Rivet dashboard (or your self-hosted engine) and adjust paths to match your deployment. Reload systemd units and start the service: ```bash sudo systemctl daemon-reload sudo systemctl enable --now backend.service ``` ## Operating ### Restart Restart the service after deploying new builds or environment changes: ```bash sudo systemctl restart backend.service ``` ### Logs Follow realtime logs when debugging: ```bash sudo journalctl -u backend.service -f ``` ## Architecture # Architecture ## 3 ways of running ### rivetkit - rivetkit is the typescript library used for both local development & to connect your application to rivet - a rivetkit instance is called a "runner." you can run multiple runners to scale rivetkit horiziotnally. read omre about runners below. #### local development - in local development, rivetkit provides a full actor environment for single-node deployments #### drivers - rivetkit supports multiple drivers. currently supports: file system (default in local dev), memory, rivet engine (used for rivet cloud & self-hosting), cloudflare durable objects (does not rely on rivet engine) - drivers are very flexible to enable you to write your actors once and plug in to any system that fits your architecture adequately - see the driver interface - actordriver https://github.com/rivet-dev/rivet/blob/eeb01fc4d9ca0e06f2e740d267bd53280ca7330e/rivetkit-typescript/packages/rivetkit/src/actor/driver.ts - managerdriver https://github.com/rivet-dev/rivet/blob/eeb01fc4d9ca0e06f2e740d267bd53280ca7330e/rivetkit-typescript/packages/rivetkit/src/manager/driver.ts ### rivet cloud - provides multi-region and highest performance out of the box - accessible at dashboard.rivet.dev and the api is avialble at api.rivet.dev ### rivet self-hosted - available as a standalone rust binary or a docker contianer - can be configured ot persist to postgres or rocksdb - can scale horiziontally across multipe nodes and can scale across multiple regions - see self-hosting docs (link to docs) ## actors - Actors for long-lived processes with durable state, realtime, and hibernate when not in use. read more about actors at a high level at (link to actors/index) ### actor-per-entity - actors are designed to have an actor-per-entity - you can think about actors a bit like objects in object-oriented programming where ach is responsible for their own state and expose methods (ie actions in our case) - examples incldue - actor per user - actor per user session - actor per document - actor per game room - actor per tenant - actor per rate limit topic ### architecting for scale - actors scale by: - having isolated state to each acotr that combines compute and storage for in-memory reads and writes - communication is stndardized based on actions & events - scale horizontally - read more about scalign at (link to scaling doc) ### horizontal scaling - actors can run across multiple rivetkit runners. this is orchestrated by rivetkit itself. ### lifecycle actors have create, destroy, wake, and sleep lifecycle hooks that you can implement to modify behavior. see the lifecycle docs for reference on actor lifecycel hook sequences ### actor sleeping - actors sleep when not in use - an actor is considered not in use when there are no active network connections to the actor (or the network connections are hibernatable websockets, see below) and there are no actions in flight - actors have a sleep timeout (configured in options.onSleepTimeout) that decides how long to keep the actor in memory with no recent actions - sleep can be disabled with `options.noSleep` ### wake events - actors can wake to any of the follwoing events: - network requests - websocket messages - alarms (see scheduling docs) ### live actor migration - live actor migrations lets your application ugprade, crash, or hot reload cahnges without interruption to your user or application (including websockets) - this is powered by hibernating websockets for live websocket migraiton & our fault tolerance mechanism (read more below) ### coldstart performance - actors have negligible coldstart performance. the code to run the actor is already started (ie the runner), so creating/starting an actor is incredibly cheap. - creating new actors with a key requires some overhead to communicate with other regions in order to reserve the actor's key (see below). actors can be created without keys with near-0 latency. ### multi-region, globally unique actor keys - acotrs can optionally have a globally unique "key" - when creating an actor with a key - this system is highly optimized to reduce wan round trips using a process call EPaxos with a custom database called Epoxy (link to https://github.com/rivet-dev/rivet/tree/main/engine/packages/epoxy) - limitation: when creating an actor with a given key, that key will always be pinned to that region even if the actor is destroyed. creating a new actor with the same key will always live in the same region. - see the acotr keys document ### input - actors have input data that can be passed to them when constructed - this is similar to apssing data to a constructor in an object ### generic parameters actor definitions include the following generic parameters that you'll see frequently in the code: - state - conn state - conn params - ephemeral variables - input data - (experimental) database connector ### persistence - state automatically flushes to storage intelligently - to force a state flush and wait for it to finish, call (TODO: look this up in state document) - read more about state persistence in the state document (link to document) - state is stored in the same place as where the actor lives. loading an actor in to memory has comparable performance to network attached storage, and once in memory, has performance of any standard in-memory read/write like a variable. ### scheduling & alarms - actors have a scheduling api to be able to wake up at any time in the indefinite future - think of this like setTimeout but without a max timeout - rivet is responsible for waking the actor when this timeout wakes ### ephemeral variables - actors have the ability to create ephemrla variables for things that you do not want to be persisted with the actor's state - this is useful for non-serializable data like a utility class like a pubsubs erver or something (TODO extra info) - link to ephemeral variables docs ### actions - for stateless clients, actions are sent as http requests via `POST /gateway//actions/` - for stateful clients, actions are sent as websocket messages ### events & subscriptions - events are sent as websocket messages ### error handling - this is different than fault tolerance: - error handling is a user error - fault tolerance is something goes wrong that your applciation was not built to handle (ie hard crash, oom, network error) - rivet provdies a special UserError class to throw custom errors that will be returned to the client - all other errors are returned as a generic "internal error" - this is becuase leaking error deatils is a common security hole, so we default to expose-nothing errors ### logging - rivet uses pino for logging - we expose a scoped child logger for each actor at `c.log` that automatically logs the actor id + key - this allows you to search lgos easily by actor id without having to log the actor id frequently - logs can be configured via the `RIVETKIT_LOG_LEVEL` env var ### fault tolerance - actors are fault tolerant, meaning that the host machine can crash and the actors will proceed to operate as if nothing happened - runners maintain a socket with rivet engine. when this socket closes or takes to long to ping, actors will reschedule - hibernating websockets (enabled by default) will live-migrate to the new actor as if nothing happened ### crash policy - there are 3 crash policies: sleep, restart, and destroyed - sleep (default, usually the option you want): - when to use: actors that need high-performance in-memory logic. - when not to use: you need this actor running at all times no matter what, even if idle - examples: (list commone xamples) - destroy: - when to use: actors that need to run once until completion. on crash, do not try to reschedule. - when not to use: if you want your actor to have fault tolerance and be able to run transaprenlty to the underlying runner - examples: batch jobs, image conversions, ephemeral jobs, (TODO come up with better eaxmples) - restart: - when to use: actors that should be running at all times - when not to use: if you don't absolutely need something running at all times, since this consumes needless compute resources. considure using the scheduling api instead. - examples: maintain outbound sockets, daemons, always-running jobs, (TODO come up with better examples) the behavior for each is described below: | Event | Restart | Sleep | Destroy | |------------------------------|--------------|--------------|--------------| | Graceful exit (StopCode::Ok) | Destroy | Destroy | Destroy | | Crash (non-Ok exit) | Reschedule | Sleep | Destroy | | Lost (runner disappeared) | Reschedule | Sleep | Destroy | | Lost + force_reschedule | Reschedule | Reschedule | Reschedule | | GoingAway (runner draining) | Reschedule | Sleep | Destroy | | No capacity (allocation) | Queue (wait) | Sleep | Queue (wait) | | No capacity + serverless | Queue (wait) | Queue (wait) | Queue (wait) | | Wake signal (while sleeping) | Reschedule | Reschedule | Reschedule | ### inspector - actors provide an inspector api to implement the: - repl - state read/write - network inspector - event log - this is impelmented over a websocket over bare ### http api - see the http api document on actors ### multi-region - actors can be scheduled across multiple regions - each actor has an actor id which embeds which region it lives in - networking is automatically routed to the region that an actor lives in - limitation: actors curretnly cannot migrate across regions ### backpressure #### no runner capacity - this is how actors with different crash policies behave when when there's backpressure: - sleep = sleeps (sheds load by not rescheduling) - restart = queues - destroy = queues - see the above matrix for more details on actor crash policy on how it handles no capacity. - the actor queue is built to withstand high amounts of backpressure on rivet, so queueing actors is fine here - a large queue means it'll take more time for your application to process the queue to catch up with demand when it comes online. #### per-actor cpu & networking exhaustion - actors are isolated, so they each have their own individual bottleneck. you can think of this like a process thread where each thread can only do so much. - there is no durable message queue/"mailbox" for actors. if the actor cannot respond in time, then the request is dropped. - if an actor exhauses its cpu or networking, then the runner - returns service unavailble (503) if the actor fails to respond to a request in time - there is no hard cap on the networking or cpu usage for each actor at the moment - if your actor is resource intensive, it's common to use a separate mailbox actor to act as a queue ## runners ### regular vs serverless runners there are 2 types of runners: - regular: these are standard nodejs processes connected to rivet that rivet can orchestrate actors to and send network requests to at any time - serverless: rivet works with serverless platforms. when an actor is created, it has a request-per-actor model where it opens a long-running request on the serverless platofrm to run a given actor. ### runner pool - runners are pooled together by sharing a common name (ie "default") - when an acotr is created, it chooses the pool by selecting the runner name to run on - rivet will automatically load balance actors across these runners ### runner key - not relevnat for serverless runners - each runner has a unique key that it provides when connecting. keys are unique to the instace the runner is running on and should be the same if the runner is restarted. - this can be the: machine's ip, k8s pod name, etc - if there is an existing runner connected with a given key, the runner will disconnect the old runner and replace it - rivet is designed to handle network partitions by waiting for runners to miss a ping, indicating it's no longer alive. however, often times runners restart immediately after a hard crash and reconnect. in this case, the runner will reconnect on restart and terminate the old runner in order to prevent further actors from scheduling to the crashed runner. ### capacity - not relevnat for serverless runners - each runner can be assigned a capacity of how many actors it can run - rivet will schedule with spread (not binpacking) in order to spread load over actors #### usefulness of capacity = 1 - setting a capacity of 1 is helpful for situations where you have cpu-intensive apps that should not run with any other actors - examples include game servers, ffmpeg jobs, etc ### versions & upgrading code - each runner has a version index - actors are always scheduled to the highest verison index (see runner priority below) - this means that when a new runner is deployed: 1. runners with higher index come online 2. actors schedule to the highest index, stop scheduling to the older index 3. old index runners start draining and migrating actors to new index 4. all old runners are now shut down - websocekts are live migrated to the new version when upgrading using hibernating websockets to users see no hiccup in their applications - this is important because actors should never downgrade their runner. they should always move to a newer version of code in order to prevent corruption. ### runner scheduling prioroty - actors are scheduled to runners sorted by priority of (version DESC, remaining capacity ASC) ### multi-region TODO ### shutdown sequence - runner shutdown is important to ensure that actors do not get unexpectedly terminated when either: - upgrading your applciation and taking down old pods - scaling down your runners horizontally (ie from an hpa) - pressing ctrl-c when in development - on shutdown: 1. tell rivet the runner is stopping 2. rivet tells all the actors on this runner to migrate 3. runner waits for all actors to finish migrating 4. runner exits process ### reconnection - runners can handle temporary network partitions - they'll automatically reconnect and replay missed commands/events between rivet and the runner - this happens transparenlty to the user - if disconnected for too long (indicating a network partition), the runner will shut itself down and exit ### autoscaling - not relevant to serverless - runners currently autoscale on cpu. more intelligent scaling is coming soon. - tune your runner total slots capacity accordingly - it's up to you to configure your hpa/etc to work like this. see the Connect guides (link to index page) for reference on hwo to configure this. ### serverless timeouts - serverless runners take in to account the maximum run duration of the serverless platform - the runners will mgirate actors to a new request before the request times out - this is completely transparent to you and the user because of the fault tolerance and websocket migraiton characteristics - it's common for actors to go sleep before hitting the serverless timeout ## networking ### web standards - everything in rivet is built on webstandards by default - nothing in rivet requires you to use our sdk, our sdks are meant to be a convenience. it's built to be as easy to use raw http/websocket endpoints from scratch. - actions, events, etc are all built on simple, well-documented http/websocket under the hood (link to openapi & asyncapi docs). - you can use low-level request handlers (lnk to dock) and low-level weboscket handlers (link to doc) to handle low-level primtivies yourself ### encoding - rivetkit's action/events api supports communicating via [VBARE](link to github repo, see the blog post for the link), CBOR, or JSON - VBARE: high-perofrmance & compact, optimal use case - CBOR: descent encoding/decoding perf + portable libraries, good for implemnting high-ish performance on other platforms - JSON: good for fast implementations & debugging (easy to read) ### tunneling - when a runner connects it opens a tunnel to rivet to allow incoming traffic - this is simila to systems like tailscale, ngrok, or other vpns - we do this for security & configuraiton simplicity since it means that you don't have to manage exposing your rivetkit applications' networkig to rivet. instead, anything that can open a socket to rivet can accept inbound traffic to actors. ### gateway - incoming traffic to actors come to the Rivet gateway and are routed to the appropriate runner - the rivet gateway automatically handles: - multi-region routing to route traffic to the correct reigon for an actor - automatically waking the actor if needed - sending traffic over the runner ### hibernating websockets - hibernating web sockets are a core component of live actor migration & fault tolerance. it allows us to maintain an open websocket while the actor crashes, upgrades, or moves to another runner. TODO: copy the rest of this from low-level webosckets document and rephrase ### actor health endpoin - actors provide a simple, utility health endpoint at `/health` that lets you check if your actor is reachable (e.g. `curl https://api.rivet.dev/gateway//health`) ## multi-region ### networking - actors may live in different regions than inbound requests - rive tuses the Epoxy (link again) system to handle global routing to route traffic to the correct region with high performance - this is completely transparent to you. your app sends traffic to https://api.rivet.dev/gatewa/* and it automatically routes to the correct actor in the appropirate region ### globally unique actor keys - acotr keys are globally unique to be able to benefit from multi-region capabilities without any extra work - see more about globally uniuqe actor keys above - see the actor keys document ### regional endpoints - each reigon has a regional endpoint - this endpoint is used specifically for connecting runners (for example https://api-us-east-1.rivet.dev), opt to use api.rivet.dev for all other traffic - runners are required to connect to the regional endpoints - this is because runners are sensitive to latency to the rivet regional datacenter - we add datacenters regularly so each runner needs to be pinned to a single datacenter in order to ensure your availble datacneter list doesn't change sporadically without your consent ### persistence - data is always persisted in the same region that is written - this is important for minimal coldstarts & data locality laws ## namespaces - rivet provides namespaces to run multiple actor systems in isolation - this makes it really easy to have prod/staging environments or completely different applications running on the same rivet instance - when you connect to rivet, you can specify which namespace you're connecting to - self-hotsed rivet defaults to namespace "default" - rivet cloud provdies isolated tokens for each namespace ## manager api - rivet provides a standard rest api for managing actors - useful endoints include: - get /actors - delete /actors/ - get /actors/names -> get all actor types available ## comparison to prior art for actors ### runtime - there are very few serious actor implementation targeted at the javascirpt eocsystem. rivet is arguably the most serious open-source actor implementation for typescript out there. ### library vs orchestrator - some actor systems opt to be purely a library while rivet opts to have an orchestrator (i.e. the single rust binary) - this lets us to a lot of things other actor systems can't: - separating orchestration, persistence, and proxy lets us isoalte the core to be incredibly reliable while the fast-changing applications that ocnnect to rivet can be more error-prone safely. with a library, the blast radius of your application also affects the entire actor system. - support for serverless platforms to benefit from cost, multi-region, blitz scaling, and relibaiblity benefits - optimize fault tolerance since we can make more assumtions about application state when the rivet core does not crash and your app does ### scheduling actors is a loose term, but there are generally 2 types of schedulign in practice: - ephemeral actors - examples: erlang/otp, akka, swift - provides no persistence or sleeping mechanism by defualt - relies on supervisors for managing persistence - [virtual actors](https://www.microsoft.com/en-us/research/project/orleans-virtual-actors/) - an extension of the actor pattern that provides actors that can hibernate ("sleep") when not in use - examples: orleans, dapr, durable objects rivet has similarities with both to provide more flexibility: - crash policies provdie for 3 types of actors: - sleep -> most similar to virutal actors - restart -> most similar to ephemeral actors but with a supervisor to auto-restart, however still has a durable queue ot handle backpressure - crash -> most similar to traditional actors but with no supervisor to restart, however still has a durable queue to handle backpressure ### communication - many actor frameworks use inbox patterns (think: queue-per-actor) to handle sending messages between actors - there is no callback mechanims, instead you need to send messages back to the actual actor - rivet opts to behave like web standards instead of using the message pattern - actors can impelment the inbox pattern optionally - but we provide lower-level networking to be able to be compatible with more techniologies - rivet assumes the same serial concurerntly that other actors do (by the nature of javascript being single-threaded) but we allow you to run promises in parallel or handl eyour own concurrency control (which some other actor frameworks might require a spawning new actor to do) ## Cross-Origin Resource Sharing # Cross-Origin Resource Sharing 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. 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. ## Implementing Origin Restrictions To implement origin restrictions on Rivet Actors, use the `onBeforeConnect` hook to verify the request. ```typescript } const ALLOWED_ORIGINS = [ "http://localhost:3000", "https://myapp.com", "https://www.myapp.com" ]; const myActor = actor(, onBeforeConnect: (c, params) => ); } }, actions: } }); ``` To catch the error on the client, use the following code: ```typescript } const client = createClient(); try catch (error) } ``` 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`. ## Documentation for LLMs & AI # Documentation for LLMs & AI Rivet provides optimized documentation formats specifically designed for Large Language Models (LLMs) and AI integration tools. ## Available Formats ### `llms.txt` (Condensed) A condensed version of the documentation perfect for quick reference and context-aware AI assistance. **Access:** /llms.txt This format includes: - Key concepts and features - Essential getting started information - Summaries of main functionality - Optimized for token efficiency ### `llms-full.txt` (Complete) The complete documentation in a single file, ideal for comprehensive AI assistance and in-depth analysis. **Access:** /llms-full.txt This format includes: - Complete documentation content - All examples and detailed explanations - Full API references and guides - Suitable for complex queries and comprehensive understanding ## Access Pages As Markdown Each documentation page is also available as clean markdown by appending `.md` to any documentation URL path. For example: - Original URL: `https://rivet.dev/docs/actors` - Markdown URL: `https://rivet.dev/docs/actors.md` ## Edge Networking # Edge Networking Actors automatically run near your users on your provider's global network. At the moment, edge networking is only supported on Rivet Cloud & Cloudflare Workers. More self-hosted platforms are on the roadmap. ## Region selection ### Automatic region selection By default, actors will choose the nearest region based on the client's location. Under the hood, Rivet and Cloudflare use [Anycast routing](https://en.wikipedia.org/wiki/Anycast) to automatically find the best location for the client to connect to without relying on a slow manual pinging process. ### Manual region selection The region an actor is created in can be overridden using region options: ```typescript client.ts const client = createClient(/* endpoint */); // Create actor in a specific region const actor = await client.example.get( }); ``` See [Create Manage Actors](/docs/actors/communicating-between-actors) for more information. ## Logging # Logging Actors provide a built-in way to log complex data to the console. Using the context's log object (`c.log`) allows you to log complex data using structured logging. Using the actor logging API is completely optional. ## Log levels There are 7 log levels: | Level | Call | Description | | ------ | ------------------------------- | ---------------------------------------------------------------- | | Fatal | `c.log.fatal(message, ...args);` | Critical errors that prevent core functionality | | Error | `c.log.error(message, ...args);` | Errors that affect functionality but allow continued operation | | Warn | `c.log.warn(message, ...args);` | Potentially harmful situations that should be addressed | | Info | `c.log.info(message, ...args);` | General information about significant events & state changes | | Debug | `c.log.debug(message, ...args);` | Detailed debugging information, usually used in development | | Trace | `c.log.trace(message, ...args);` | Very detailed debugging information, usually for tracing flow | | Silent | N/A | Disables all logging output | ## Structured logging The built-in logging API (using `c.log`) provides structured logging to let you log key-value pairs instead of raw strings. Structured logs are readable by both machines & humans to make them easier to parse & search. When using `c.log`, the actor's name, key, and actor ID are automatically included in every log output. This makes it easy to filter and trace logs by specific actors in production environments. ### Examples ```typescript // Just a message c.log.info('server started'); // Prints: level=INFO actor=myActor key=foo actorId=44096d46632fd087 msg="server started" // Message with an object c.log.info('user connected', ); // Prints: level=INFO actor=myActor key=foo actorId=44096d46632fd087 msg="user connected" userId=123 ip="192.168.1.1" // Just an object (no message) c.log.info(); // Prints: level=INFO actor=myActor key=foo actorId=44096d46632fd087 action="purchase" amount=99.99 currency="USD" ``` The logging system is built on [Pino](https://getpino.io/#/docs/api?id=logger), a high-performance structured logger for Node.js. ## Configuration ### Environment Variables You can configure logging behavior using environment variables: | Variable | Description | Values | Default | | -------- | ----------- | ------ | ------- | | `RIVETKIT_LOG_LEVEL` | Sets the minimum log level to display | `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `silent` | `warn` | | `RIVETKIT_LOG_TARGET` | Include the module name that logged the message | `1` to enable, `0` to disable | `0` | | `RIVETKIT_LOG_TIMESTAMP` | Include timestamp in log output | `1` to enable, `0` to disable | `0` | | `RIVETKIT_LOG_MESSAGE` | Enable detailed message logging for debugging | `1` to enable, `0` to disable | `0` | | `RIVETKIT_LOG_ERROR_STACK` | Include stack traces in error output | `1` to enable, `0` to disable | `0` | | `RIVETKIT_LOG_HEADERS` | Log HTTP headers in requests | `1` to enable, `0` to disable | `0` | Example: ```bash RIVETKIT_LOG_LEVEL=debug RIVETKIT_LOG_TARGET=1 RIVETKIT_LOG_TIMESTAMP=1 node server.js ``` ### Log Level You can configure the log level programmatically when running your server: ```typescript registry.start( }) ``` ### Custom Pino Logger You can also provide a custom Pino base logger for more advanced logging configurations: ```typescript const customLogger = pino( }); registry.start( }) ``` If using a custom base logger, you must manually configure your own log level in the Pino logger. For more advanced Pino configuration options, see the [Pino API documentation](https://getpino.io/#/docs/api?id=export). ### Disable Welcome Message You can disable the default RivetKit welcome message with: ```typescript registry.start() ``` ## Overview # Overview Get started with Rivet in minutes. Choose your preferred framework or runtime to begin building with actors. Set up actors with Node.js, Bun, and web frameworks Build real-time React applications with actors Build server-rendered Next.js experiences backed by actors Deploy actors on Cloudflare Workers with zero infrastructure ## Submit a Template # Submit a Template Share it with the community by submitting it as a template. ## How to Submit Create your example in the `examples/` folder of the [Rivet repository](https://github.com/rivet-dev/rivet). Add a `template` block to your example's `package.json`: ```json , "scripts": } ``` Run the following command to regenerate the template registry: ```bash pnpm build -F @rivetkit/example-registry ``` Submit your changes by [creating a pull request](https://github.com/rivet-dev/rivet/compare) to the Rivet repository. ## Template Options - `technologies` (required) - Array of technologies used - `tags` (required) - Array of tags categorizing the template - `noFrontend` (optional) - Set to `true` to skip screenshot generation - `priority` (optional) - Lower numbers appear first in the list ## Quickstart # Quickstart Get started with Rivet in minutes. Choose your preferred framework or runtime to begin building with actors. Set up actors with Node.js, Bun, and web frameworks Build real-time React applications with actors Build server-rendered Next.js experiences backed by actors Deploy actors on Cloudflare Workers with zero infrastructure ## Configuration # Configuration Rivet Engine can be configured through environment variables or configuration files. ## Configuration Rivet supports JSON, JSON5, JSONC, YAML, YML, and environment variable configurations. **Environment Variables** Use the `RIVET__` prefix with `__` as separator to configure properties in the config. For example: set the `RIVET__database__postgres__url` environment variable for `database.postgres.url`. **Configuration Paths** Configuration files are automatically discovered in platform-specific directories: - Linux: `/etc/rivet/config.json` - macOS: `/Library/Application Support/rivet/config.json` - Windows: `C:\ProgramData\rivet\config.json` **Multiple Files** Multiple configuration files in the same directory are loaded and merged together. For example: `/etc/rivet/config.json` and `/etc/rivet/database.json` will be merged together. **Override Configuration Path** You can override the default configuration path using the `--config` flag: ```bash # Load from a specific file rivet-engine --config /path/to/config.json # Load from a directory rivet-engine --config /etc/rivet # Load multiple paths (merged in order) rivet-engine --config /etc/rivet/base.json --config /etc/rivet/override.json ``` ## Definition ```typescript interface RivetConfig ; // HTTP/HTTPS traffic handling service guard?: ; }; }; // Public API service configuration api_public?: ; // Private API service configuration api_peer?: ; // Actor orchestration configuration (experimental) pegboard?: ; // Logging configuration logs?: ; // Multi-datacenter topology topology?: ; // Database backend configuration database?: | ; }; } | ; }; // Message pub/sub system pubsub?: | ; } | ; }; } | ; }; // Caching layer configuration cache?: ; // ClickHouse analytics database (optional) clickhouse?: ; }; }; // Vector HTTP endpoint (optional) vector_http?: ; // Telemetry configuration telemetry?: ; // Runtime configuration runtime?: ; } ``` ## PostgreSQL Configuration ### Managed Postgres Compatibility Some hosted PostgreSQL platforms require additional configuration due to platform-specific restrictions. Use direct connection (not connection pooler). ```json } } } ``` ```bash } RIVET__database__postgres__url="postgresql://pscale_api_.:@.pg.psdb.cloud:5432/postgres?sslmode=require" RIVET__database__postgres__unstable_disable_lock_customization=true ``` For better performance, set `deadlock_timeout = 10ms` in the PlanetScale dashboard at **Clusters** → **Parameters** → **Query Tuning**. Use direct connection on port `5432` (not connection pooler). #### Without SSL ```json } } } ``` ```bash } RIVET__database__postgres__url="postgresql://postgres:@db..supabase.co:5432/postgres?sslmode=disable" RIVET__database__postgres__unstable_disable_lock_customization=true ``` #### With SSL Download the root certificate from your Supabase dashboard and specify its path. See [Supabase SSL Enforcement](https://supabase.com/docs/guides/platform/ssl-enforcement) for details. ```json } } } } ``` ```bash } RIVET__database__postgres__url="postgresql://postgres:@db..supabase.co:5432/postgres?sslmode=require" RIVET__database__postgres__unstable_disable_lock_customization=true RIVET__database__postgres__ssl__root_cert_path="/path/to/supabase-ca.crt" ``` ### SSL/TLS Support To enable SSL for Postgres, add `sslmode=require` to your PostgreSQL connection URL: ```json } } } ``` ```bash } RIVET__database__postgres__url="postgresql://user:password@host.example.com:5432/database?sslmode=require" ``` The `sslmode` parameter controls TLS usage: - `disable`: Do not use TLS - `prefer`: Use TLS if available, otherwise connect without TLS (default) - `require`: Require TLS connection (fails if TLS is not available) To verify the server certificate against a CA or verify the hostname, use custom SSL certificates (see below). #### Custom SSL Certificates For databases using custom certificate authorities (e.g., Supabase) or requiring client certificate authentication, you can specify certificate paths in the configuration: ```json } } } } ``` ```bash } RIVET__database__postgres__url="postgresql://user:password@host:5432/database?sslmode=require" RIVET__database__postgres__ssl__root_cert_path="/path/to/root-ca.crt" RIVET__database__postgres__ssl__client_cert_path="/path/to/client.crt" RIVET__database__postgres__ssl__client_key_path="/path/to/client.key" ``` | Parameter | Description | PostgreSQL Equivalent | |-----------|-------------|----------------------| | `root_cert_path` | Path to the root certificate file for verifying the server's certificate | `sslrootcert` | | `client_cert_path` | Path to the client certificate file for client certificate authentication | `sslcert` | | `client_key_path` | Path to the client private key file for client certificate authentication | `sslkey` | All SSL paths are optional. If not specified, Rivet uses the default system root certificates from Mozilla's root certificate store. ### Do Not Use Connection Poolers Rivet requires direct PostgreSQL connections for session-level features and does not support connection poolers. Do not use: - PgBouncer - Supavisor - AWS RDS Proxy ### Troubleshooting #### Permission Denied Errors If you see errors like: ``` ERROR: permission denied to set parameter "deadlock_timeout" ERROR: current transaction is aborted, commands ignored until end of transaction block ``` Add `unstable_disable_lock_customization: true` to your configuration: ```json } } ``` This disables Rivet's attempt to set `lock_timeout = 0` and `deadlock_timeout = 10ms`. Since `lock_timeout` defaults to `0` in PostgreSQL, skipping these settings is safe. Deadlock detection will use the default `1s` timeout instead of `10ms`. ## Connecting Your Backend to Rivet Engine # Connecting Your Backend to Rivet Engine Unless exlpicitly configured, Rivet will default to running on the local file system without using the Rivet Engine. This is perfect for local development and testing. When ready to scale the backend, RivetKit can connect to a Rivet Engine instance using the `RIVET_ENGINE` environment variable. The engine is not required at any point during development. It is only required to scale RivetKit to multiple nodes. ## Connecting Runners To connect a runner to your Rivet Engine, set the `RIVET_ENGINE` environment variable: ```bash RIVET_ENGINE=http://your-engine-host:6420 npm run dev ``` Once connected: - The runner appears in the Runners tab of the dashboard - Your actor names show up in the sidebar - The engine begins routing traffic to your runner ## Environment Variables ### `RIVET_ENGINE` The endpoint of your Rivet Engine instance. ```bash # Local development RIVET_ENGINE=http://localhost:6420 # Production RIVET_ENGINE=https://engine.your-domain.com ``` ### `RIVET_NAMESPACE` The namespace to run actors in. Useful for multi-tenant deployments. ```bash RIVET_NAMESPACE=production ``` ### `RIVET_RUNNER` A name for the runner to allow filtering which nodes to run actors on. ```bash RIVET_RUNNER=worker-01 ``` ### `RIVET_RUNNER_KEY` A unique key for the runner. If another runner connects with the same key, the previous one is disconnected. This is useful for handling zombie runners that weren't shut down gracefully. ```bash RIVET_RUNNER_KEY=unique-runner-key-123 ``` Generate a unique runner key using: `uuidgen` or `openssl rand -hex 16` ## Connection Examples ### Testing Setup You do not need the engine for local development, but it can be helpful for testing your production-readiness: ```bash # Start the engine docker run -p 6420:6420 rivetkit/engine # In another terminal, start your runner RIVET_ENGINE=http://localhost:6420 npm run dev ``` ### Production Setup ```bash # Assume the engine is running at 1.2.3.4 # On runner nodes RIVET_ENGINE=http://1.2.3.4 \ RIVET_NAMESPACE=production \ RIVET_RUNNER=worker-$(hostname) \ RIVET_RUNNER_KEY=$(cat /etc/machine-id) \ npm run start ``` ## Docker Compose # Docker Compose ## Quick Start Run with ephemeral storage: ```yaml services: rivet-engine: image: rivetkit/engine:latest ports: - "6420:6420" restart: unless-stopped ``` Run with persistent storage: ```yaml services: rivet-engine: image: rivetkit/engine:latest ports: - "6420:6420" volumes: - rivet-data:/data environment: RIVET__FILE_SYSTEM__PATH: "/data" restart: unless-stopped volumes: rivet-data: ``` Start the services: ```bash docker-compose up -d ``` ## Configuration ### Environment Variables Configure Rivet using environment variables in your compose file: ```yaml services: rivet-engine: image: rivetkit/engine:latest ports: - "6420:6420" volumes: - rivet-data:/data environment: RIVET__POSTGRES__URL: "postgresql://postgres:password@localhost:5432/db" restart: unless-stopped volumes: rivet-data: ``` Or use a `.env` file: ```txt # .env POSTGRES_PASSWORD=secure_password RIVET__POSTGRES__URL=postgresql://rivet:secure_password@postgres:5432/rivet ``` Reference in compose: ```yaml services: rivet-engine: env_file: - .env ``` ### Config File Mount a JSON configuration file: ```yaml services: rivet-engine: image: rivetkit/engine:latest ports: - "6420:6420" volumes: - ./rivet-config.json:/etc/rivet/config.json:ro - rivet-data:/data restart: unless-stopped volumes: rivet-data: ``` Create the config file (`rivet-config.json`): ```json } ``` ## Production Setup #### With PostgreSQL ```yaml services: postgres: image: postgres:15 environment: POSTGRES_DB: rivet POSTGRES_USER: rivet POSTGRES_PASSWORD: rivet_password volumes: - postgres-data:/var/lib/postgresql/data restart: unless-stopped rivet-engine: image: rivetkit/engine:latest ports: - "6420:6420" environment: RIVET__POSTGRES__URL: postgresql://rivet:rivet_password@postgres:5432/rivet depends_on: - postgres restart: unless-stopped volumes: postgres-data: ``` ## Next Steps - See [Configuration](/docs/self-hosting/configuration) for all options ## Docker Container # Docker Container ## Quick Start Run with ephemeral storage: ```bash docker run -p 6420:6420 rivetkit/engine ``` Run with persistent storage: ```bash docker run \ -p 6420:6420 \ -v rivet-data:/data \ -e RIVET__FILE_SYSTEM__PATH="/data" \ rivetkit/engine ``` ## Configuration ### Environment Variables Configure Rivet using environment variables: ```bash docker run -p 6420:6420 \ -v rivet-data:/data \ -e RIVET__POSTGRES__URL="postgresql://postgres:password@localhost:5432/db" \ rivetkit/engine ``` ### Config File Mount a JSON configuration file: ```bash # Create config file cat rivet-config.json } EOF # Run with mounted config docker run -p 6420:6420 \ -v rivet-data:/data \ -v $(pwd)/rivet-config.json:/etc/rivet/config.json:ro \ rivetkit/engine ``` ## Production Setup ### With PostgreSQL ```bash # Create network docker network create rivet-net # Run PostgreSQL docker run -d \ --name postgres \ --network rivet-net \ -e POSTGRES_DB=rivet \ -e POSTGRES_USER=rivet \ -e POSTGRES_PASSWORD=rivet_password \ -v postgres-data:/var/lib/postgresql/data \ postgres:15 # Run Rivet Engine docker run -d \ --name rivet-engine \ --network rivet-net \ -p 6420:6420 \ -e RIVET__POSTGRES__URL="postgresql://rivet:rivet_password@postgres:5432/rivet" \ rivetkit/engine ``` ## Next Steps - Use [Docker Compose](/docs/self-hosting/docker-compose) for multi-container setups - See [Configuration](/docs/self-hosting/configuration) for all options ## Self-Hosting Overview # Self-Hosting Overview Self-hosting Rivet gives you complete control over the actor orchestration platform and infrastructure. ## Self-Host vs BYOC Rivet supports both BYOC (Bring Your Own Cloud) and self-hosting to fit your deployment needs. | | Self-Hosting | BYOC with Rivet Cloud | |---|---|---| | **You Manage** | Full stack (your backend, engine, Rivet Engine, database) | Only your backend | | **Complexity** | Higher (full stack deployment) | Lower (connect and deploy) | | **Cost** | Higher operational overhead | Usually lower usage-based cost | | **Supports Serverless** | Requires extra infrastructure | Yes | | **Air-Gapped Deployments** | Yes | No | | **Best For** | Air-gapped environments, strict compliance, custom security policies | All other production deployments | | **Support** | [Contact sales](/sales) or community | Community, Slack, and email (varies by plan) | | **Documentation** | Continue below | [See connect guides](/docs/connect) | ## Architecture # Rivet has 3 core components: - **Your Backend**: Your application server that handles user requests and includes a runner component that executes actor code - **Rivet Engine**: Main orchestration service that manages actor lifecycle, routes messages, and provides APIs - **Storage**: Persistence layer for actor state and messaging infrastructure for real-time communication ## Storage Backends Rivet supports multiple storage backends: - **File System**: Suitable for single-node deployments - **PostgreSQL**: Recommended for production - **FoundationDB**: For massive scale ([requires enterprise](/sales)) ## Deployment Platforms Deploy Rivet on your preferred platform: - [Docker Container](/docs/self-hosting/docker-container) - [Docker Compose](/docs/self-hosting/docker-compose) - [Railway](/docs/self-hosting/railway) - [Kubernetes](/docs/self-hosting/kubernetes) - AWS Fargate - Google Cloud Run - Hetzner - VM & Bare Metal _Self-hosting guides coming soon._ ## Next Steps - [Install Rivet Engine](/docs/self-hosting/install) - [Connect your backend](/docs/self-hosting/connect-backend) - [Configure your deployment](/docs/self-hosting/configuration) - [Multi-region setup](/docs/self-hosting/multi-region) ## Installing Rivet Engine # Installing Rivet Engine ## Docker ```bash docker run -p 6420:6420 rivetkit/engine ``` For more options: - [Docker Container](/docs/self-hosting/docker-container) - Persistent storage, configuration, production setup - [Docker Compose](/docs/self-hosting/docker-compose) - Multi-container deployments with PostgreSQL ## Prebuilt Binaries Prebuilt binaries coming soon ## Build From Source ```bash git clone https://github.com/rivet-dev/engine.git cd rivet cargo build --release -p rivet-engine ./target/release/rivet-engine ``` ## Kubernetes # Kubernetes ## Quick Start Deploy Rivet Engine to Kubernetes with PostgreSQL storage. ### 1. Create Namespace Save as `namespace.yaml`: ```yaml apiVersion: v1 kind: Namespace metadata: name: rivet-engine ``` Apply: ```bash kubectl apply -f namespace.yaml ``` ### 2. Deploy PostgreSQL Save as `postgres.yaml`: ```yaml apiVersion: v1 kind: ConfigMap metadata: name: postgres-config namespace: rivet-engine data: POSTGRES_DB: rivet POSTGRES_USER: postgres --- apiVersion: v1 kind: Secret metadata: name: postgres-secret namespace: rivet-engine type: Opaque stringData: # IMPORTANT: Change this password in production! POSTGRES_PASSWORD: "postgres" --- apiVersion: v1 kind: Service metadata: name: postgres namespace: rivet-engine spec: type: ClusterIP ports: - port: 5432 targetPort: 5432 protocol: TCP name: postgres selector: app: postgres --- apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres namespace: rivet-engine spec: serviceName: postgres replicas: 1 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:17 ports: - containerPort: 5432 name: postgres envFrom: - configMapRef: name: postgres-config - secretRef: name: postgres-secret volumeMounts: - name: postgres-data mountPath: /var/lib/postgresql/data subPath: pgdata resources: requests: cpu: 250m memory: 512Mi limits: cpu: 1000m memory: 2Gi livenessProbe: exec: command: - /bin/sh - -c - pg_isready -U postgres initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: exec: command: - /bin/sh - -c - pg_isready -U postgres initialDelaySeconds: 5 periodSeconds: 5 volumeClaimTemplates: - metadata: name: postgres-data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 10Gi ``` Apply and wait for PostgreSQL to be ready: ```bash kubectl apply -f postgres.yaml kubectl -n rivet-engine wait --for=condition=ready pod -l app=postgres --timeout=300s ``` ### 3. Deploy Rivet Engine The Rivet Engine deployment consists of two components: - **Main Engine Deployment**: Runs all services except singleton services. Configured with Horizontal Pod Autoscaling (HPA) to automatically scale between 2-10 replicas based on CPU and memory utilization. - **Singleton Engine Deployment**: Runs singleton services that must have exactly 1 replica (e.g., schedulers, coordinators). Save as `rivet-engine.yaml`: ```yaml apiVersion: v1 kind: ConfigMap metadata: name: engine-config namespace: rivet-engine data: config.jsonc: | , "topology": ] } } --- apiVersion: v1 kind: Service metadata: name: rivet-engine namespace: rivet-engine spec: type: LoadBalancer ports: - name: guard port: 6420 targetPort: 6420 protocol: TCP - name: api-peer port: 6421 targetPort: 6421 protocol: TCP selector: app: rivet-engine --- apiVersion: apps/v1 kind: Deployment metadata: name: rivet-engine namespace: rivet-engine spec: replicas: 2 selector: matchLabels: app: rivet-engine template: metadata: labels: app: rivet-engine spec: containers: - name: rivet-engine image: rivetkit/engine:latest args: - start - --except-services - singleton env: - name: RIVET_CONFIG_PATH value: /etc/rivet/config.jsonc ports: - containerPort: 6420 name: guard - containerPort: 6421 name: api-peer volumeMounts: - name: config mountPath: /etc/rivet readOnly: true resources: requests: cpu: 2000m memory: 4Gi limits: cpu: 4000m memory: 8Gi startupProbe: httpGet: path: /health port: 6421 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 30 readinessProbe: httpGet: path: /health port: 6421 periodSeconds: 5 failureThreshold: 2 livenessProbe: httpGet: path: /health port: 6421 periodSeconds: 10 failureThreshold: 3 volumes: - name: config configMap: name: engine-config --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: rivet-engine namespace: rivet-engine spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: rivet-engine minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 --- apiVersion: apps/v1 kind: Deployment metadata: name: rivet-engine-singleton namespace: rivet-engine spec: replicas: 1 selector: matchLabels: app: rivet-engine-singleton template: metadata: labels: app: rivet-engine-singleton spec: containers: - name: rivet-engine image: rivetkit/engine:latest args: - start - --services - singleton - --services - api-peer env: - name: RIVET_CONFIG_PATH value: /etc/rivet/config.jsonc ports: - containerPort: 6421 name: api-peer volumeMounts: - name: config mountPath: /etc/rivet readOnly: true resources: requests: cpu: 2000m memory: 4Gi limits: cpu: 4000m memory: 8Gi startupProbe: httpGet: path: /health port: 6421 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 30 readinessProbe: httpGet: path: /health port: 6421 periodSeconds: 5 failureThreshold: 2 livenessProbe: httpGet: path: /health port: 6421 periodSeconds: 10 failureThreshold: 3 volumes: - name: config configMap: name: engine-config ``` Apply and wait for the engine to be ready: ```bash kubectl apply -f rivet-engine.yaml kubectl -n rivet-engine wait --for=condition=ready pod -l app=rivet-engine --timeout=300s kubectl -n rivet-engine wait --for=condition=ready pod -l app=rivet-engine-singleton --timeout=300s ``` **Note**: The HPA requires a metrics server to be running in your cluster. Most Kubernetes distributions (including k3d, GKE, EKS, AKS) include this by default. ### 4. Verify Deployment Check that all pods are running (you should see 2+ engine pods and 1 singleton pod): ```bash kubectl -n rivet-engine get pods kubectl -n rivet-engine get hpa ``` ### 5. Access the Engine Get the service URL: ```bash # For LoadBalancer kubectl -n rivet-engine get service rivet-engine # For port forwarding (local development) kubectl -n rivet-engine port-forward service/rivet-engine 6420:6420 6421:6421 ``` Test the health endpoint: ```bash curl http://localhost:6420/health ``` Expected response: ```json ``` ## Local Development with k3d For local Kubernetes testing with k3d: ```bash # Create k3d cluster k3d cluster create rivet \ --api-port 6550 \ -p "6420:30420@loadbalancer" \ -p "6421:30421@loadbalancer" \ --agents 2 # Apply manifests (use NodePort service type for k3d) kubectl apply -f namespace.yaml kubectl apply -f postgres.yaml kubectl -n rivet-engine wait --for=condition=ready pod -l app=postgres --timeout=300s # Modify rivet-engine.yaml service to use NodePort before applying: # Change `type: LoadBalancer` to `type: NodePort` # Add nodePort fields: # - name: guard # port: 6420 # targetPort: 6420 # nodePort: 30420 # - name: api-peer # port: 6421 # targetPort: 6421 # nodePort: 30421 kubectl apply -f rivet-engine.yaml kubectl -n rivet-engine wait --for=condition=ready pod -l app=rivet-engine --timeout=300s # Access at http://localhost:6420 and http://localhost:6421 ``` Cleanup: ```bash k3d cluster delete rivet ``` ## Production Setup ### Security 1. **Change PostgreSQL password** in `postgres-secret` 2. **Use TLS** for external access (configure ingress controller) 3. **Set admin token** via environment variable: ```yaml env: - name: RIVET__AUTH__ADMIN_TOKEN valueFrom: secretKeyRef: name: rivet-secrets key: admin-token ``` ### Scaling The engine is configured with Horizontal Pod Autoscaling (HPA) by default, automatically scaling between 2-10 replicas based on CPU (60%) and memory (80%) utilization. To adjust the scaling parameters, modify the HPA configuration: ```yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: rivet-engine namespace: rivet-engine spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: rivet-engine minReplicas: 2 # Adjust minimum replicas maxReplicas: 20 # Adjust maximum replicas metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 # Adjust CPU threshold - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 # Adjust memory threshold ``` Monitor HPA status: ```bash kubectl -n rivet-engine get hpa kubectl -n rivet-engine describe hpa rivet-engine ``` ## Next Steps - See [Configuration](/docs/self-hosting/configuration) for all options - For advanced multi-datacenter setup, see the [GitHub repository](https://github.com/rivet-gg/rivet/tree/main/k8s) ## Multi-Region # Multi-Region Rivet Engine supports scaling transparently across multiple regions. Documentation coming soon ## Railway Deployment # Railway Deployment Railway provides a simple platform for deploying Rivet Engine with automatic scaling and managed infrastructure. ## Video Tutorial ## Quick Deploy Choose the template that best fits your needs: | **Rivet Template** | **Rivet Starter** | |-------------------|-------------------| | [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/rivet?referralCode=RC7bza&utm_medium=integration&utm_source=template&utm_campaign=generic) | [![Deploy Rivet Starter](https://railway.com/button.svg)](https://railway.com/deploy/rivet-starter) | | **Blank template** to start fresh | **Complete example** with chat app | | - Rivet Engine | - Pre-configured Rivet Engine | | - PostgreSQL database | - Example chat application with Actors | | - Basic configuration | - PostgreSQL database | | - Manual setup required | - Rivet Inspector for debugging | | | - Ready to run immediately | You can also use the [Rivet Railway template](https://github.com/rivet-dev/template-railway) as a starting point for your application. After deploying either template, you can find the `RIVET__AUTH__ADMIN_TOKEN` under the **Variables** tab in the Railway dashboard. This token is required to access the Rivet Inspector. ## Manual Deployment ### Prerequisites 1. [Railway account](https://railway.app) 2. [Railway CLI](https://docs.railway.app/develop/cli) (optional) ### Step 1: Create New Project ```bash # Using Railway CLI railway init # Or create via dashboard # https://railway.app/new ``` ### Step 2: Add Services #### Deploy PostgreSQL Database 1. Click "New Service" → "Database" → "PostgreSQL" 2. Railway automatically provisions and configures PostgreSQL 3. Note the connection string from the service variables #### Deploy Rivet Engine 1. Click "New Service" → "Docker Image" 2. Set image: `rivetkit/engine:latest` 3. Configure environment variables: - `RIVET__POSTGRES__URL=$}` ### Step 3: Deploy Your Application Follow the [Railway Quick Start guide](https://docs.railway.com/quick-start) to deploy your repository: 1. Connect your GitHub account to Railway 2. Select your repository containing your Rivet application 3. Railway will automatically detect and deploy your application 4. Configure environment variables for your application: - `RIVET_ENGINE=$}` - Points to the Rivet Engine service's private domain