# Rivet Documentation - Complete This file contains the complete documentation for Rivet, an open-source alternative to Durable Objects. ## Overview ## Quickstart ## 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. State is persisted to Rivet for long term storage, so it survives server restarts. - **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. ## When to Use Rivet Actors - **AI agents & sandboxes**: multi-step toolchains, conversation memory, sandbox orchestration. - **Multiplayer or collaborative apps**: CRDT docs, shared cursors, realtime dashboards, chat. - **Workflow automation**: background jobs, cron, rate limiters, durable queues, backpressure control. - **Data-intensive backends**: geo-distributed or per-tenant databases, in-memory caches, sharded SQL. - **Networking workloads**: WebSocket servers, custom protocols, local-first sync, edge fanout. ## Minimal Project ### Backend **actors.ts** ```ts const counter = actor(, events: , actions: , }, }); use: , }); ``` **server.ts** Integrate with the user's existing server if applicable. Otherwise, default to Hono. ### Client Docs Use the client SDK that matches your app: - [JavaScript Client](/docs/clients/javascript) - [React Client](/docs/clients/react) - [Swift Client](/docs/clients/swift) ## Actor Quick Reference ### In-Memory State Persistent data that survives restarts, crashes, and deployments. State is persisted on Rivet Cloud or Rivet self-hosted, so it survives restarts if the current process crashes or exits. [Documentation](/docs/actors/state) ### Keys Keys uniquely identify actor instances. Use compound keys (arrays) for hierarchical addressing: ```ts const chatRoom = actor(, actions: ), }, }); const registry = setup( }); const client = createClient(); // Compound key: [org, room] client.chatRoom.getOrCreate(["org-acme", "general"]); // Access key inside actor via c.key ``` Don't build keys with string interpolation like `"org:$"` when `userId` contains user data. Use arrays instead to prevent key injection attacks. [Documentation](/docs/actors/keys) ### Input Pass initialization data when creating actors. ```ts const game = actor() => (), actions: , }); const registry = setup( }); const client = createClient(); // Client usage const gameHandle = client.game.getOrCreate(["game-1"], }); ``` [Documentation](/docs/actors/input) ### Temporary Variables Temporary data that doesn't survive restarts. Use for non-serializable objects (event emitters, connections, etc). [Documentation](/docs/actors/ephemeral-variables) ### Actions Actions are the primary way clients and other actors communicate with an actor. ```ts const counter = actor(, actions: , }); ``` [Documentation](/docs/actors/actions) ### Events & Broadcasts Events enable real-time communication from actors to connected clients. ```ts const chatRoom = actor(, events: >(), }, actions: ); }, }, }); ``` [Documentation](/docs/actors/events) ### Connections Access the current connection via `c.conn` or all connected clients via `c.conns`. Use `c.conn.id` or `c.conn.state` to securely identify who is calling an action. Connection state is initialized via `connState` or `createConnState`, which receives parameters passed by the client on connect. [Documentation](/docs/actors/connections) ### Queues Use queues to process durable messages in order inside a `run` loop. ```ts const counter = actor(, queues: >(), }, run: async (c) => }, }); ``` [Documentation](/docs/actors/queues) ### Workflows Use workflows when your `run` logic needs durable, replayable multi-step execution. ```ts const worker = actor(, queues: >(), }, run: workflow(async (ctx) => ); }); }), }); async function processTask(url: string): Promise ); if (!res.ok) throw new Error(`Task failed: $`); } ``` [Documentation](/docs/actors/workflows) ### Actor-to-Actor Communication Actors can call other actors using `c.client()`. ```ts const inventory = actor(, actions: } }); const order = actor(, actions: , }, }); const registry = setup( }); ``` [Documentation](/docs/actors/communicating-between-actors) ### Scheduling Schedule actions to run after a delay or at a specific time. Schedules persist across restarts, upgrades, and crashes. ```ts const reminder = actor(, events: >(), }, actions: , // Schedule action to run at specific timestamp setReminderAt: (c, message: string, timestamp: number) => , sendReminder: (c) => ); }, }, }); ``` [Documentation](/docs/actors/schedule) ### Destroying Actors Permanently delete an actor and its state using `c.destroy()`. ```ts const userAccount = actor(, onDestroy: (c) => deleted`); }, actions: , }, }); ``` [Documentation](/docs/actors/destroy) ### Lifecycle Hooks Actors support hooks for initialization, background processing, connections, networking, and state changes. Use `run` for long-lived background loops, and exit cleanly on shutdown with `c.aborted` or `c.abortSignal`. ```ts interface RoomState interface RoomInput interface ConnState const chatRoom = actor( } as RoomState, vars: , connState: as ConnState, events: , // ... }); ``` [Documentation](/docs/actors/appearance) ## Client Documentation Find the full client guides here: - [JavaScript Client](/docs/clients/javascript) - [React Client](/docs/clients/react) - [Swift Client](/docs/clients/swift) ## Common Patterns Actors scale naturally through isolated state and message-passing. Structure your applications with these patterns: [Documentation](/docs/actors/design-patterns) ### Actor Per Entity Create one actor per user, document, or room. Use compound keys to scope entities: ### Coordinator & Data Actors **Data actors** handle core logic (chat rooms, game sessions, user data). **Coordinator actors** track and manage collections of data actors—think of them as an index. ### Run Loop Use a `run` loop for continuous background work inside an actor. Process queue messages in order, run logic on intervals, stream AI responses, or coordinate long-running tasks. ```ts const counterWorker = actor(, queues: >(), }, run: async (c) => }, actions: , }); const registry = setup( }); ``` ### Workflow Loop Use this pattern for long-lived, durable workflows that initialize resources, process commands in a loop, then clean up. ```ts type WorkMessage = ; type ControlMessage = ; const worker = actor(, queues: , run: workflow(async (ctx) => ); ctx.state.phase = "running"; ctx.state.stopReason = null; }); const stopReason = await ctx.loop("worker-loop", async (loopCtx) => ); if (message.name === "work") ), }); loopCtx.state.processed += 1; loopCtx.state.total += message.body.amount; }); return; } return Loop.break((message.body as ControlMessage).reason); }); await ctx.step("teardown", async () => ); ctx.state.phase = "stopped"; ctx.state.stopReason = stopReason; }); }), }); const registry = setup( }); ``` [Documentation](/docs/actors/workflows) ### Actions vs Queues - **Actions** are not durable. Use them for realtime reads, ephemeral data, and low-latency communication like player input. - **Queues** are durable. Use them to serialize mutations through the run loop, avoiding race conditions with SQLite and other local state. Callers can still wait for a response from queued work. ### Authentication, Security, & CORS - Validate credentials in `onBeforeConnect` or `createConnState` and throw an error to reject unauthorized connections. - Use `c.conn.state` to securely identify users in actions rather than trusting action parameters. - For cross-origin access, validate the request origin in `onBeforeConnect`. [Authentication Documentation](/docs/actors/authentication) · [CORS Documentation](/docs/general/cors) ### Versions & Upgrades When deploying new code, set a version number so Rivet can route new actors to the latest runner and optionally drain old ones. Use a build timestamp, git commit count, or CI build number as the version. [Documentation](/docs/actors/versions) ### Anti-Patterns #### Never build a "god" actor Do not put all your logic in a single actor. A god actor serializes every operation through one bottleneck, kills parallelism, and makes the entire system fail as a unit. Split into focused actors per entity. #### Never create an actor per request Actors are long-lived and maintain state across requests. Creating a new actor for every incoming request throws away the core benefit of the model and wastes resources on actor creation and teardown. Use actors for persistent entities and regular functions for stateless work. ## Access Control Use access control to decide what authenticated clients are allowed to do. This is authorization, not authentication: - Use [authentication](/docs/actors/authentication) to identify who is calling. - Use access-control rules to decide what they can do after connecting. ## Permission Surfaces RivetKit authorization is explicit per surface: - `onBeforeConnect` rejects unauthenticated or malformed connections. - Action handlers (`actions.*`) enforce action permissions. - `queues..canPublish` allows or denies inbound queue publishes. - `events..canSubscribe` allows or denies event subscriptions. ## Fail By Default Use deny-by-default rules everywhere: 1. Keep `onBeforeConnect` strict and reject invalid credentials. 2. In each action, explicitly allow expected roles and throw `forbidden` otherwise. 3. In `canPublish` and `canSubscribe`, return `true` only for allowed roles and end with `return false`. ```ts type ConnParams = ; type ConnState = ; async function authenticate( authToken: string, ): Promise ; } if (authToken === "member-token") ; } return null; } state: > }, onBeforeConnect: async (_c, params: ConnParams) => ); } const session = await authenticate(params.authToken); if (!session) ); } }, createConnState: async (_c, params: ConnParams): Promise => ); } return session; }, events: >(), moderationLog: event<>( return false; }, }), }, queues: >( return false; }, }), }, actions: ); } const message = ; c.state.messages.push(message); c.broadcast("messages", message); }, }, }); ``` ## Return Value Contract `canPublish` and `canSubscribe` must return a boolean: - `true`: allow - `false`: deny with `forbidden` Returning `undefined`, `null`, or any non-boolean throws an internal error. ## Notes - `canPublish` only applies to queue names defined in `queues`. - Incoming queue messages for undefined queues are ignored and logged as warnings. - `canSubscribe` only applies to event names defined in `events`. - Broadcasting an event not defined in `events` logs a warning but still publishes. ## Actions 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). By default, actions run in parallel. If you need advanced control over concurrency, use [queues](/docs/actors/queues). ## 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: ### Type Safety The actor client includes type safety out of the box. When you use `createClient()`, TypeScript automatically infers action parameter and return types: ## 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: ### 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 actor.ts // Define schema for action parameters const IncrementSchema = z.object(); const counter = actor(, actions: }); } c.state.count += result.data.count; return c.state.count; } } }); ``` ## 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. ## Debugging - `GET /inspector/rpcs` lists all available actions on an actor. - `POST /inspector/action/:name` executes an action with JSON args and returns output. - `GET /inspector/traces` helps inspect action timings and failures. - In non-dev mode, inspector endpoints require authorization. ## 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 ## 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 ## Icons & Names # Icons & Names Actors can be customized with a display name and icon that appear in the Rivet inspector & dashboard. This helps identify actors at a glance when managing your application. ## Configuration Set the `name` and `icon` properties in your actor's `options`: ```typescript const chatRoom = actor(, state: , actions: }); ``` ## Icon Formats The `icon` property accepts two formats: ### Emoji Use any emoji character directly: ```typescript const notificationService = actor(, // ... }); ``` ### FontAwesome Icons Use [FontAwesome](https://fontawesome.com/search) icon names without the "fa" prefix: ```typescript const gameServer = actor(, // ... }); const analyticsWorker = actor(, // ... }); ``` ## Default Behavior If no `icon` is specified, actors display the default actor icon. If no `name` is specified, the actor's registry key (e.g., `chatRoom`, `gameServer`) is displayed instead. ## Examples Here are some common patterns: ```typescript // Chat/messaging actors const chatRoom = actor(, // ... }); // Game-related actors const matchmaker = actor(, // ... }); const gameSession = actor(, // ... }); // Data processing actors const dataProcessor = actor(, // ... }); // Using emojis for quick identification const alertService = actor(, // ... }); ``` ## Advanced: Run Handler Metadata For library developers creating reusable run handlers, you can bundle icon and name metadata directly with the `run` property. This allows libraries to provide sensible defaults without requiring users to configure them manually. Instead of returning a function from your run handler factory, return an object with `name`, `icon`, and `run`: ```typescript type MyOptions = ; function myCustomRunHandler(_options: MyOptions): RunConfig ; return ; } ``` Users can then use this directly: ```typescript const myCustomRunHandler = (_options: Record) => (, }); const myActor = actor(), // Automatically gets "My Custom Handler" name and "bolt" icon }); ``` Actor-level `options.name` and `options.icon` always take precedence, allowing users to override library defaults: ```typescript const myCustomRunHandler = (_options: Record) => (, }); const myActor = actor(, run: myCustomRunHandler(), }); ``` The built-in `workflow()` helper uses this pattern to automatically display the workflow icon for workflow-based actors. ## Authentication ## Do You Need Authentication? ## 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` ## Access Control After a connection is authenticated, use [Access Control](/docs/actors/access-control) to enforce authorization: - Check permissions in action handlers. - Use `queues..canPublish` to gate inbound queue publishes. - Use `events..canSubscribe` to gate event subscriptions. ### `onBeforeConnect` The `onBeforeConnect` hook validates credentials before allowing a connection. Throw an error to reject the connection. ```typescript interface ConnParams // Example token validation function async function validateToken(token: string, roomKey: string[]): Promise interface Message 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 interface Message // Example token validation function async function validateToken(token: string, roomKey: string[]): Promise ; } return null; } const chatRoom = actor(, createConnState: async (c, params: ConnParams): Promise ### Handling Errors Authentication errors use the same system as regular errors. See [errors](/docs/actors/errors) for more details. ## Examples ### JWT Validate JSON Web Tokens and extract user claims: ```typescript interface ConnParams interface ConnState interface JwtPayload // Example JWT verification function - in production use a JWT library function verifyJwt(token: string, secret: string): JwtPayload 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 // Example token validation function async function validateToken(token: string): Promise<> ; } function requireRole(requiredRole: keyof typeof ROLE_HIERARCHY) }) => role required`, ); } }; } function requirePermission(permission: string) }) => ' 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 // Example token validation function async function validateToken(token: string): Promise<> ; } const rateLimitedActor = actor(, createVars: () => ( 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 ; } return null; } const cachedAuthActor = actor(, createVars: () => ( 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 ## Communicating Between Actors Actors can communicate with each other using the server-side actor client, enabling complex workflows and data sharing between different actor instances. ## 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 interface Order interface ProcessedOrder extends Order ; } const inventory = actor(, actions: ; } } }); const payment = actor(, actions: ) } }); const orderProcessor = actor(, actions: ); return ; } } }); const registry = setup( }); ``` ## Use Cases and Patterns ### Actor Orchestration Use a coordinator actor to manage complex workflows: ```typescript interface WorkflowResult ; completedAt: number; } const dataProcessor = actor(, actions: ) } }); const validator = actor(, actions: ) => () } }); const finalizer = actor(, actions: ) => () } }); const workflowActor = actor(, actions: ); return result; } } }); const registry = setup( }); ``` ### Data Aggregation Collect data from multiple actors: ```typescript interface Stats interface Report ; generatedAt: number; } const userMetrics = actor(, actions: ) } }); const orderMetrics = actor(, actions: ) } }); const systemMetrics = actor(, actions: ) } }); const analyticsActor = actor(, actions: , generatedAt: Date.now() }; c.state.reports.push(report); return report; } } }); const registry = setup( }); ``` ### Event-Driven Architecture Use connections to listen for events from other actors: ```typescript interface User interface Order interface AuditLog const userActor = actor(, actions: ; c.broadcast("userCreated", user); return user; } } }); const orderActor = actor(, actions: ; c.broadcast("orderCompleted", order); return order; } } }); const auditLogActor = actor(, actions: ); }); // Listen for order events orderActorConn.on("orderCompleted", (order: Order) => ); }); return ; } } }); const registry = setup( }); ``` ### Batch Operations Process multiple items in parallel: ```typescript interface Item const processor = actor(, actions: ) } }); const registry = setup( }); const client = createClient(); // Process items in parallel const items: Item[] = [ , ]; 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 For documentation on connecting to actors from clients, see the [Clients documentation](/docs/clients). ## Parameters When clients connect to an actor, they can pass connection parameters that are handled during the connection process. For example: ## Connection State There are two ways to define an actor's connection state: ## 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 interface Message interface ConnParams interface ConnState function validateToken(token: string): boolean const chatRoom = actor(, // Dynamically create connection state createConnState: (c, params: ConnParams): ConnState => ; }, // Validate connections before accepting them onBeforeConnect: (c, params: ConnParams) => // Authentication is valid, connection will proceed // The actual connection state will come from 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 interface ConnState interface UserStatus const chatRoom = actor( as Record, messages: [] as string[] }, createConnState: (): ConnState => (), 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 interface ConnState interface UserStatus const chatRoom = actor( as Record, messages: [] as string[] }, createConnState: (): ConnState => (), 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 interface ConnState const chatRoom = actor( as Record }, createConnState: (): ConnState => (), actions: } if (recipientConn) ); } } } }); ``` ## Disconnecting clients Connections can be disconnected from within an action: ```typescript interface ConnState const secureRoom = actor(, createConnState: (): ConnState => (), actions: } } } }); ``` If you need to wait for the disconnection to complete, you can use `await`: ```typescript const myActor = actor(, actions: } }); ``` 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 ## Debugging # List all actors with a given name curl http://localhost:6420/actors?name=my-actor # Look up one actor by key (name is required when key is provided) curl "http://localhost:6420/actors?name=my-actor&key=%5B%22my-key%22%5D" # List actors by IDs (comma-separated) curl http://localhost:6420/actors?actor_ids=id1,id2 ``` Rules: - `key` requires `name`. - `actor_ids` cannot be combined with `name` or `key`. Returns: ```json ] } ``` ### Create Actor `POST /actors` creates a new actor. ```bash curl -X POST http://localhost:6420/actors \ -H 'Content-Type: application/json' \ -d '' ``` ### Create or Get Actor `PUT /actors` creates an actor if it does not exist, otherwise returns the existing one. ```bash curl -X PUT http://localhost:6420/actors \ -H 'Content-Type: application/json' \ -d '' ``` Returns the actor object with its `actor_id`. ### List Actor Names ```bash curl http://localhost:6420/actors/names?namespace=default ``` Returns all registered actor names and their metadata. ### Read Actor KV Requires authentication (see above). ```bash curl http://localhost:6420/actors//kv/keys/ \ -H 'x-rivet-token: YOUR_RIVET_TOKEN' ``` Returns the value stored at the given key. See the [OpenAPI spec](https://github.com/rivet-dev/rivet/tree/main/rivetkit-openapi) for the full schema of all management endpoints. ## Actor API All actor-level endpoints are accessed through the gateway. The gateway routes requests to the correct actor instance using the actor ID in the URL path: ``` http://localhost:6420/gateway// ``` The gateway only accepts actor IDs, not names. Use `GET /actors?name=...` from the management API to look up actor IDs first. ### Authentication Standard actor endpoints (health, actions, requests) and inspector endpoints have separate authentication requirements. #### Standard Endpoints | Environment | Authentication | |---|---| | **Local development** | No authentication required. | | **Self-hosted engine** | The Rivet Engine handles authentication at the gateway level. | | **Rivet Cloud** | Authentication is handled by the Rivet Cloud platform at the gateway level. | #### Inspector Endpoints | Environment | Authentication | |---|---| | **Local development** | No authentication required if `RIVET_INSPECTOR_TOKEN` is not set. A warning is logged. | | **Self-hosted engine** | Set the `RIVET_INSPECTOR_TOKEN` environment variable. Pass it as a bearer token in the `Authorization` header. | | **Rivet Cloud** | Token is required. Pass it as a bearer token in the `Authorization` header. | ```bash curl http://localhost:6420/gateway//inspector/summary \ -H 'Authorization: Bearer YOUR_INSPECTOR_TOKEN' ``` ### Standard Actor Endpoints These are the built-in actor endpoints available through the gateway: ```bash # Health check curl http://localhost:6420/gateway//health # Metadata curl http://localhost:6420/gateway//metadata # Call an action curl -X POST http://localhost:6420/gateway//action/myAction \ -H 'Content-Type: application/json' \ -d '' # Send queue message (body includes queue name) curl -X POST http://localhost:6420/gateway//queue \ -H 'Content-Type: application/json' \ -d '}' # Send queue message (queue name in path) curl -X POST http://localhost:6420/gateway//queue/jobs \ -H 'Content-Type: application/json' \ -d '}' # Send queue message and wait for completion (optional timeout in ms) curl -X POST http://localhost:6420/gateway//queue/jobs \ -H 'Content-Type: application/json' \ -d ',"wait":true,"timeout":5000}' # Forward an HTTP request to the actor's onRequest handler curl http://localhost:6420/gateway//request/my/custom/path ``` Queue send responses include: ```json ``` If `wait: true` and the timeout is reached, `status` is `"timedOut"`. ### Inspector Endpoints The inspector HTTP API exposes JSON endpoints for querying and modifying actor internals at runtime. These are designed for agent-based debugging and tooling. #### Get State ```bash curl http://localhost:6420/gateway//inspector/state ``` Returns the actor's current persisted state: ```json , "isStateEnabled": true } ``` #### Set State ```bash curl -X PATCH http://localhost:6420/gateway//inspector/state \ -H 'Content-Type: application/json' \ -d '}' ``` Returns: ```json ``` #### Get Connections ```bash curl http://localhost:6420/gateway//inspector/connections ``` Returns all active connections with their params, state, and metadata: ```json , "stateEnabled": true, "state": , "subscriptions": 2, "isHibernatable": true } } ] } ``` #### Get RPCs ```bash curl http://localhost:6420/gateway//inspector/rpcs ``` Returns a list of available actions: ```json ``` #### Execute Action ```bash curl -X POST http://localhost:6420/gateway//inspector/action/increment \ -H 'Content-Type: application/json' \ -d '' ``` Returns: ```json ``` #### Get Queue Status ```bash curl http://localhost:6420/gateway//inspector/queue?limit=10 ``` Returns queue status with messages: ```json ] } ``` #### Get Traces Query trace spans in OTLP JSON format: ```bash curl "http://localhost:6420/gateway//inspector/traces?startMs=0&endMs=9999999999999&limit=100" ``` Returns: ```json ] } ] } ] }, "clamped": false } ``` #### Get Workflow History ```bash curl http://localhost:6420/gateway//inspector/workflow-history ``` Returns: ```json ``` #### Summary Get a full snapshot of the actor in a single request: ```bash curl http://localhost:6420/gateway//inspector/summary ``` Returns: ```json , "connections": [], "rpcs": ["increment", "getCount"], "queueSize": 0, "isStateEnabled": true, "isDatabaseEnabled": false, "isWorkflowEnabled": false, "workflowHistory": null } ``` ### Polling Inspector endpoints are safe to poll. For live monitoring, poll at 1-5 second intervals. The `/inspector/summary` endpoint is useful for periodic snapshots since it returns all data in a single request. ## OpenAPI Spec The full OpenAPI specification including all management and actor endpoints is available: - In the repository at [`rivetkit-openapi/openapi.json`](https://github.com/rivet-dev/rivet/tree/main/rivetkit-openapi) - Served at `/doc` on the manager when running locally ## Design Patterns ## 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** ## 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** **Example: Random Sharding** 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** ## 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** ### 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** `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 processor = actor(, actions: ), destroy: (c) => , }, }); const registry = setup( }); const client = createClient("http://localhost:8080"); const app = new Hono(); // Bad: creating an actor for each API request app.post("/process", async (c) => ); ``` **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 - 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. ### 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 interface UserState // Example email service interface const emailService = ) => , }; const userActor = actor( as UserState, 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 `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: ## Using Variables Vars can be accessed and modified through the context object with `c.vars`: ```typescript // Mock event emitter for demonstration interface EventEmitter function createEventEmitter(): EventEmitter ; return , emit: (event, data) => }; } 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 ## Errors 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: ## Error Codes Use error codes for explicit error matching in try-catch blocks: ## Errors With Metadata Include metadata to provide additional context for rich error handling: ## 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. ### 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 const payment = actor(, actions: } }); const registry = setup( }); const client = createClient("http://localhost:8080"); const paymentActor = client.payment.getOrCreate([]); // 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 ## Realtime 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 Define event names and payload types with `events` and `event ### One-time Event Listeners Use `connection.once(eventName, callback)` for events that should only trigger once: ### Removing Event Listeners Use the callback returned from `.on()` to remove event listeners: ## Debugging - `GET /inspector/connections` shows active connections and connection metadata. - Use this to confirm clients are connected before expecting broadcasts. - `GET /inspector/summary` provides connections, RPCs, and queue size in one response. - In non-dev mode, inspector endpoints require authorization. ## 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 ## Fetch and WebSocket Handler ## Helper Types ## Vanilla HTTP API TODO ## Input Parameters 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 interface GameInput const game = actor(, createState: (c, input: GameInput) => (), actions: }); const registry = setup( }); const client = createClient(); // Client side - create with input const gameHandle = await client.game.create(["game-123"], }); // getOrCreate can also accept input (used only if creating) const gameHandle2 = client.game.getOrCreate(["game-456"], }); ``` ## Accessing Input in Lifecycle Hooks Input is available in lifecycle hooks via the `opts.input` parameter: ```typescript interface ChatRoomInput interface ChatRoomState // Mock function for demonstration function setupPrivateRoomLogging(roomName: string) `); } const chatRoom = actor(, messages: [] } as ChatRoomState, createState: (c, input: ChatRoomInput): ChatRoomState => (, messages: [], }), onCreate: (c, 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(); type GameInput = z.infer; interface GameState const game = actor(, gameState: "waiting" } as GameState, createState: (c, inputRaw: GameInput): GameState => , 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 interface RoomInput const chatRoom = actor(, createState: (c, input: RoomInput) => (), connState: , createConnState: (c, params: ) => (), actions: }); const registry = setup( }); const client = createClient(); // Actor creation with input const room = await client.chatRoom.create(["room-123"], , }); ``` ## Input Best Practices ### Use Type Safety Define input types to ensure type safety: ```typescript interface GameInput interface GameState const game = actor( as GameState, createState: (c, input: GameInput): GameState => (), actions: , }); ``` ### Store Input in State If you need to access input data in actions, store it in the actor's state: ```typescript interface GameInput interface GameConfig interface GameState const game = actor(, players: , gameState: "waiting" } as GameState, createState: (c, input: GameInput): GameState => (, // 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 ## Key Format Actor keys can be either a string or an array of strings: ```typescript const counter = actor(, actions: }); const chatRoom = actor(, actions: }); const registry = setup( }); const client = createClient(); // String key const counterHandle = client.counter.getOrCreate(["my-counter"]); // Array key (compound key) const chatRoomHandle = 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 const chatRoom = actor(, actions: }); const gameRoom = actor(, actions: }); const workspace = actor( }, actions: }); const registry = setup( }); const client = createClient(); // Example user data const userId = "user-123"; const gameId = "game-456"; const tenantId = "tenant-789"; const workspaceId = "workspace-abc"; // User-specific chat rooms const userRoomHandle = client.chatRoom.getOrCreate(["user", userId, "private"]); // Game rooms by region and difficulty const gameRoomHandle = client.gameRoom.getOrCreate(["us-west", "hard", gameId]); // Multi-tenant resources const workspaceHandle = client.workspace.getOrCreate(["tenant", tenantId, workspaceId]); ``` This allows you to create hierarchical addressing schemes and organize actors by multiple dimensions. ### 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 const globalActor = actor( }, actions: }); const registry = setup( }); const client = createClient(); // Get the singleton session const globalActorHandle = 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 const chatRoom = actor(, actions: }); const userProfile = actor(, actions: }); const registry = setup( }); const client = createClient(); // These are different actors, same key is fine const userChat = client.chatRoom.getOrCreate(["user-123"]); const userProfileHandle = client.userProfile.getOrCreate(["user-123"]); ``` ## Accessing Keys in Metadata Access the actor's key within the actor using the [metadata](/docs/actors/metadata) API: ## Configuration Examples ### Simple Configuration with Keys Use keys to provide basic actor configuration: ### Complex Configuration with Input For more complex configuration, use [input parameters](/docs/actors/input): ```typescript client.ts interface ChatRoomInput ; } const chatRoom = actor( }, createState: (c, input: ChatRoomInput) => (), actions: }); const registry = setup( }); const client = createClient("http://localhost:8080"); const roomName = "general"; // Create with both key and input const chatRoomHandle = 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 ## Low-Level KV Storage Every Rivet Actor includes a lightweight key-value store on `c.kv`. It is useful for dynamic keys, blobs, or data that does not fit well in structured state. If your data has a known schema, prefer [state](/docs/actors/state). KV is best for flexible or user-defined keys. ## Basic Usage Keys and values default to `text`, so you can use strings without extra options. ```typescript const greetings = actor(, actions: `, message); }, getGreeting: async (c, userId: string) => `); } } }); ``` ## Value Types You can store binary values by passing `Uint8Array` or `ArrayBuffer` directly. Use `type` when reading to get the right return type. ```typescript const assets = actor(, actions: , getAvatar: async (c) => ); }, putSnapshot: async (c, data: ArrayBuffer) => } }); ``` TypeScript returns a concrete type based on the option you pass in: ```typescript const example = actor(, actions: ); // ^? Uint8Array | null } } }); ``` ## Key Types Keys accept either `string` or `Uint8Array`. String keys are encoded as UTF-8 by default. When listing by prefix, you can control how keys are decoded with `keyType`. Returned keys have the prefix removed. ```typescript const example = actor(, actions: ); for (const [key, value] of results) } } }); ``` If you use binary keys, set `keyType: "binary"` so the returned keys stay as `Uint8Array`. ## Batch Operations KV supports batch operations for efficiency. Defaults are still `text` for both keys and values. ```typescript const example = actor(, actions: } }); ``` ## API Reference - [`ActorContext`](/typedoc/interfaces/rivetkit.mod.ActorContext.html) - `c.kv` is available on the context ## 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` 5. `run` (background, does not block) **On Destroy** 1. `onDestroy` **On Wake** (after sleep, restart, or crash) 1. `createVars` 2. `onWake` 3. `run` (background, does not block) **On Sleep** (after idle period) 1. Wait for `run` to complete (with timeout) 2. `onSleep` **On Connect** (per client) 1. `onBeforeConnect` 2. `createConnState` 3. `onConnect` **On Disconnect** (per client) 1. `onDisconnect` **On Inbound Action Invoke** (per action call) 1. Action handler executes **On Inbound Queue Publish** (per `handle.send(...)`) 1. `queues..canPublish` (if defined) 2. Queue message is enqueued **On Event Subscription Request** (per subscribe request) 1. `events..canSubscribe` (if defined) 2. Subscription is applied ## 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: }); ``` ### `run` [API Reference](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) The `run` hook is called after the actor starts and runs in the background without blocking actor startup. This is ideal for long-running background tasks like: - Reading from message queues in a loop - Tick loops for periodic work - Custom workflow logic - Background processing The handler exposes `c.aborted` for loop checks and `c.abortSignal` for canceling operations when the actor is stopping. You should always check or listen for shutdown to exit gracefully. **Important behavior:** - The actor may go to sleep at any time during the `run` handler. Use `c.keepAwake(promise)` to wrap async operations that should not be interrupted. - If the `run` handler exits (returns), the actor will crash and reschedule - If the `run` handler throws an error, the actor will crash and reschedule - On shutdown, the actor waits for the `run` handler to complete (with configurable timeout via `options.runStopTimeout`) ```typescript // Example: Tick loop const tickActor = actor(, run: async (c) => ); // Wait 1 second, but exit early if aborted await new Promise((resolve) => , ); }); } c.log.info("Background loop exiting gracefully"); }, actions: }); ``` ```typescript // Example: Queue consumer const queueConsumer = actor(, run: async (c) => ); const message = messages[0]; if (message) ); // Process the message... c.state.processedCount++; } } c.log.info("Queue consumer exiting gracefully"); }, 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 function validateToken(token: string): boolean type ConnParams = ; const chatRoom = actor(, // Method 2: Dynamically create connection state createConnState: (_c, params: ConnParams) => ; }, // Validate connections before accepting them onBeforeConnect: (_c, params: ConnParams) => // 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( as Record, messages: [] as string[], }, createConnState: (_c, params: ) => (), 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. ### `canPublish` and `canSubscribe` [API Reference](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) Use schema-level hooks to authorize queue publishes and event subscriptions. Both hooks can be async and must return booleans: - `queues..canPublish` runs before inbound queue publishes. - `events..canSubscribe` runs before inbound event subscription requests. For actions, enforce authorization directly inside each action handler. ```typescript type ConnState = ; const securedActor = actor(, createConnState: (_c, params: ): ConnState => (), events: >(), adminFeed: event<>(), }, queues: >(), }, actions: ); } return "secret"; }, }, }); ``` Use deny-by-default rules for each hook and return `false` unless explicitly allowed. See [Access Control](/docs/actors/access-control) for full guidance. ### `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( as Record, messages: [] as string[], }, createConnState: (_c, params: ) => (), 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. See [Request Handler](/docs/actors/request-handler) for more details. ```typescript const apiActor = actor(, onRequest: (c, request) => ), }); } return new Response("Not found", ); }, 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); c.state.requestCount++; c.broadcast("actionResponseLogged", ); return output; }, 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 | | `runStopTimeout` | 15000ms | Max time to wait for run handler to stop 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 ### Preventing Sleep with `keepAwake` The actor may go to sleep at any time when idle, including during the `run` handler. If you have async operations that should not be interrupted by sleep, wrap them with `c.keepAwake(promise)`. The method prevents the actor from sleeping while the promise is running, returns the resolved value, and resets the sleep timer on completion. Errors are propagated to the caller. ```typescript const tickActor = actor(, run: async (c) => ); // Wait before next iteration await new Promise((resolve) => , ); }); } }, actions: }); ``` ### Fire-and-Forget with `waitUntil` The `c.waitUntil` method allows you to execute promises asynchronously without blocking the actor's main execution flow. This is useful for fire-and-forget operations where you don't need to wait for completion or handle errors. 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( as Record, scores: as Record, }, actions: ; // Send analytics event without blocking c.waitUntil( fetch('https://analytics.example.com/events', ) }).then(() => console.log('Analytics sent')) ); return ; }, } }); ``` Note that errors thrown in `waitUntil` promises are logged but not propagated to the caller. ### Actor Shutdown Abort Signal The `c.abortSignal` provides an `AbortSignal` that fires when the actor is stopping, and `c.aborted` is the shorthand boolean for loop checks. Use these 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(, actions: , }); // 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); }, // Background task (does not block startup) run: async (c) => " is at $`); await new Promise((resolve) => , ); }); } }, onStateChange: (c, newState) => ); }, onBeforeConnect: (c, params: ConnParams) => console.log(`User $ attempting to connect`); }, onConnect: (c, conn) => connected to "$"`); }, onDisconnect: (c, conn) => disconnected from "$"`); }, // Observe action responses before they are sent onBeforeActionResponse: (c, actionName, args, output) => called`, args); return output; }, // Define actions actions: , getInfo: (c) => (), } }); ``` ## Limits This page documents the limits for Rivet Actors. There are two types of limits: - **Soft Limit**: Application-level limit, configurable in RivetKit. These cannot exceed the hard limit. - **Hard Limit**: Infrastructure-level limit that cannot be configured. Soft limits can be configured in RivetKit by passing options to `setup`: ```typescript const rivet = setup(, maxIncomingMessageSize: 1_048_576, maxOutgoingMessageSize: 10_485_760, // ... }); ``` ## Limits ### WebSocket These limits affect actions that use `.connect()` and [low-level WebSockets](/docs/actors/websocket-handler). | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Max incoming message size | 64 KiB | 32 MiB | Maximum size of incoming WebSocket messages. Soft limit configurable via `maxIncomingMessageSize`. | | Max outgoing message size | 1 MiB | 32 MiB | Maximum size of outgoing WebSocket messages. Soft limit configurable via `maxOutgoingMessageSize`. | | WebSocket open timeout | — | 15 seconds | Time allowed for WebSocket connection to be established, including `onBeforeConnect` and `createConnState` hooks. Connection is closed if exceeded. | | Message ack timeout | — | 30 seconds | Time allowed for message acknowledgment before connection is closed. Only relevant in the case of a network issue and does not affect your application. | ### Hibernating WebSocket Hibernating WebSockets allow actors to sleep while keeping client connections alive. All WebSocket limits above also apply to hibernating WebSockets. See [WebSocket Hibernation](/docs/actors/websocket-handler#web-socket-hibernation) for details. | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Max pending buffer size | — | 128 MiB | Total size of all buffered messages per connection while actor is sleeping. | | Max pending message count | — | 65,535 | Maximum number of buffered messages per connection while actor is sleeping. | | Hibernation timeout | — | 90 seconds | Maximum time an actor has to wake up before the client is disconnected. Only relevant if something is wrong with starting actors. | ### HTTP These limits affect actions that do not use `.connect()` and [low-level HTTP requests](/docs/actors/request-handler). | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Max request body size | — | 128 MiB | Maximum size of HTTP request bodies. | | Max response body size | — | 128 MiB | Maximum size of HTTP response bodies. | | Request timeout | — | 5 minutes | Maximum time for a request to complete. | ### Networking | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Connection ping timeout | — | 30 seconds | Connection is closed if a ping is not acknowledged within this time. Applies to both HTTP and WebSocket. Only relevant in the case of a network issue and does not affect your application. | ### Queue | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Max queue size | 1,000 messages | — | Maximum number of messages in the queue before new messages are rejected. Configurable via `maxQueueSize`. | | Max queue message size | 64 KiB | 128 KiB (effective) | Maximum size of each individual queue message. Configurable via `maxQueueMessageSize`. Actual payload is slightly lower after queue serialization overhead. | ### Actor KV Storage These limits apply to the low-level KV storage interface powering Rivet Actors. They likely do not affect your application, but are documented for completeness. | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Max key size | — | 2 KiB | Maximum size of a single key. | | Max value size | — | 128 KiB | Maximum size of a single value. | | Max keys per operation | — | 128 | Maximum number of keys in a single get/put/delete operation. | | Max batch put payload size | — | 976 KiB | Maximum total size of all key-value pairs in a single batch put operation. | | Max storage size per actor | — | 10 GiB | Maximum total KV storage size for a single actor. | | List default limit | — | 16,384 | Default maximum number of keys returned by a list operation. | ### Actor SQLite Storage These limits apply to the [SQLite database](/docs/actors/state#sqlite-database) available via `this.sql`. SQLite data is stored through the KV layer, so the storage limit is shared with KV storage. | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Max storage size per actor | — | 10 GiB | Maximum total storage size for a single actor. This limit is shared with KV storage. | ### Actor Input See [Actor Input](/docs/actors/input) for details. | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Max actor input size | — | 4 MiB | Maximum size of the input passed when creating an actor. | | Max connection params size | — | 4 KiB | Maximum size of connection parameters passed when connecting to an actor. | | Max actor key component size | — | 128 bytes | Maximum size of each component in an actor key array. | | Max actor key total size | — | 1,024 bytes | Maximum total size of the serialized actor key string. | | Max actor name length | — | 64 characters | Maximum length for actor and project identifiers. | ### Rate Limiting | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Rate limit | — | 1200 requests/minute | Default rate limit per actor per IP address with a 1 minute time bucket. | | Max in-flight requests | — | 32 | Default maximum concurrent requests to an actor per IP address. | ### Timeouts | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Action timeout | 60 seconds | — | Timeout for RPC actions. Configurable via `actionTimeout`. | | Create vars timeout | 5 seconds | — | Timeout for `createVars` hook. Configurable via `createVarsTimeout`. | | Create conn state timeout | 5 seconds | — | Timeout for `createConnState` hook. Configurable via `createConnStateTimeout`. | | On connect timeout | 5 seconds | — | Timeout for `onConnect` hook. Configurable via `onConnectTimeout`. | | On sleep timeout | 5 seconds | — | Timeout for `onSleep` hook. Configurable via `onSleepTimeout`. | | On destroy timeout | 5 seconds | — | Timeout for `onDestroy` hook. Configurable via `onDestroyTimeout`. | | Wait until timeout | 15 seconds | — | Max time to wait for `waitUntil` background promises during shutdown. Configurable via `waitUntilTimeout`. | | Run stop timeout | 15 seconds | — | Max time for `run` handler to stop during shutdown. Configurable via `runStopTimeout`. | | Sleep timeout | 30 seconds | — | Time of inactivity before actor hibernates. Configurable via `sleepTimeout`. | | State save interval | 10 seconds | — | Interval between automatic state saves. Configurable via `stateSaveInterval`. | ### Actor Lifecycle | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Actor start threshold | — | 30 seconds | Maximum time for an actor to start before it is considered lost and rescheduled. | | Actor stop threshold | — | 30 seconds | Maximum time for an actor to stop before it is considered lost. | ## Increasing Limits These limits are sane defaults designed to protect your application from exploits and accidental runaway bugs. If you have a use case that requires different limits, [contact us](https://rivet.dev/contact) to discuss your requirements. ## Metadata ## Actor ID Get the unique instance ID of the actor: ```typescript const example = actor(, actions: } }); ``` ## Actor Name Get the actor type name: ```typescript const example = actor(, actions: } }); ``` 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 example = actor(, actions: } }); ``` 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 example = actor(, actions: } }); ``` ## Example Usage ## API Reference - [`ActorDefinition`](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) - Interface for defining metadata - [`CreateOptions`](/typedoc/interfaces/rivetkit.client_mod.CreateOptions.html) - Includes metadata options ## Persistence ## External SQL 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: ### Using Drizzle ORM Here's the same user actor pattern using Drizzle ORM for more type-safe database operations: ## Queues & Run Loops ## What are queues? - **Realtime**: messages are delivered to a live actor as soon as possible. - **Durable**: messages are persisted and survive actor sleep/restart. - **Request/response**: clients can wait for a queue completion response. - **Scalable**: queues absorb large bursts and handle heavy backpressure safely. - **Local per actor**: each actor instance has its own queue storage (scoped by actor key/id). Queues are commonly referred to as "mailboxes" in other actor frameworks. ## What are queues good for? - Great for any task that changes actor state. - Helps avoid race conditions by handling work in order. - Makes complex behavior easier to organize. ## Basic queue This is the default pattern. Define queue names in `queues`, process them in `run`, and publish from the client with `handle.send(...)`. ## Completable messages Use this when you want explicit completion/ack semantics but do not need to return data. - If processing fails before `message.complete()`, the message is not acknowledged. - Unacknowledged messages are retried, so mutation handlers should be idempotent. - `status: "timedOut"` means sender timeout elapsed before `message.complete(...)`. ## Request/reply pattern Use this when the sender needs data back from queued work. ## Queue messages from within an actor Queueing is useful from inside actor logic too, not just from clients. - Use actions as entrypoints, then enqueue into the run loop to keep mutations serialized. - You can also call `c.queue.send(...)` from other parts of `run` when needed. - `c.queue.send(...)` confirms durable enqueue. It does not wait for processing to finish. ## Defining queue schemas You can define queue types with `queue ## Multiple queues Multiple queues let you separate message flows by purpose. By default, receive calls race across all queues when `names` is not specified. In this pattern, prompt messages run through a streaming loop while stop messages act as control signals on a separate receive path. Use `iter()` as the main stream and `next()` as a stop signal. ## Sleeping behavior If an actor has a `run` handler, it does not sleep while that handler is actively doing work. It only can sleep when the run loop is blocked waiting for queue entries (for example inside `iter(...)` or `next(...)`). This means you can run normal code in `run` without worrying about sleep interrupting it mid-call. ## Debugging - `GET /inspector/queue?limit=50` returns queue size and pending message metadata. - `GET /inspector/summary` includes `queueSize` for quick queue health checks. - `POST /queue/:name` with `wait: true` is useful to verify completable/request-response behavior. - In non-dev mode, inspector endpoints require authorization. ## Recommendations - Actions are for getting data, queue entries are for mutating data. - Implement connection auth in `onBeforeConnect`. See [Authentication](/docs/actors/authentication). - Route most state changes through one queue loop so ordering stays predictable. - If you need more complex multi-step run loops, consider using workflows. - Use `c.aborted` and `c.abortSignal` for graceful shutdown and cancellation. - Add `timeout` when callers need bounded wait behavior. - Use `wait: true` only when the caller actually needs a response. ## Quickstart ## Node.js & Bun Quickstart ## Steps See the [JavaScript client documentation](/docs/clients/javascript) for more information. See the [React documentation](/docs/clients/react) for more information. ## Configuration Options ## Cloudflare Workers Quickstart # Increment counter curl -X POST http://localhost:8787/increment/my-counter ``` ## Configuration Options ### Connect To The Rivet Actor Create a type-safe client to connect from your frontend or another service: See the [JavaScript client documentation](/docs/clients/javascript) for more information. See the [React documentation](/docs/clients/react) for more information. ## Next.js Quickstart For information about the Next.js client API, see the [React Client API Reference](/docs/clients/react). ## React Quickstart ## Steps For detailed information about the React client API, see the [React Client API Reference](/docs/clients/react). ## Configuration Options ## Low-Level HTTP Request 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. 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. ### Via getGatewayUrl Use `.getGatewayUrl()` to get the raw gateway URL for the actor. This is useful when you need to use the URL with external tools or custom HTTP clients. ### 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: 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 ## Actor Scheduling 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. ## 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 interface Reminder interface ReminderState // Mock email function function sendEmail(to: string, message: string) : $`); } const reminderService = actor( } as ReminderState, actions: ; // Schedule the sendReminder action to run after the delay c.schedule.after(delayMs, "sendReminder", reminderId); return ; }, sendReminder: (c, reminderId: string) => ); } } else // Clean up the processed reminder delete c.state.reminders[reminderId]; } } }); ``` ## Sharing and Joining State ## SQLite ## What is SQLite? - **Database per actor**: each actor instance has its own SQLite database, scoped to that actor. - **High performance**: Rivet Actors keep compute and storage together, so queries avoid network round trips to an external database. - **Larger-than-memory storage**: SQLite stores data on disk, so you can work with datasets that do not fit in actor memory. - **Embedded relational database**: use tables, indexes, and SQL queries directly inside actor logic. ### SQLite features - **Indexes**: speed up lookups on frequently queried fields. - **Search and filtering**: use `WHERE`, `LIKE`, and `ORDER BY` instead of manual in-memory loops. - **Relationships**: use multiple tables and `JOIN` queries for connected data. - **Constraints**: use primary keys, unique constraints, and foreign keys for data integrity. - **Transactions**: apply multiple writes atomically when changes must stay consistent. ## Raw SQL vs ORM (Drizzle) Rivet supports both raw SQL and [Drizzle](https://orm.drizzle.team/) for actor-local SQLite. Use **raw SQL** when you want direct query control and minimal abstraction. ```ts @nocheck await c.db.execute("INSERT INTO todos (title) VALUES (?)", title); const rows = await c.db.execute("SELECT id, title FROM todos ORDER BY id DESC"); ``` Use **Drizzle** when you want typed schema and typed query APIs. ```ts @nocheck await c.vars.drizzle.insert(todos).values(); const rows = await c.vars.drizzle.select().from(todos).orderBy(desc(todos.id)); ``` You can mix both in the same actor. For Drizzle setup, see [SQLite + Drizzle](/docs/actors/sqlite-drizzle). ## Basic setup Define `db: db()` on your actor, create your schema in `onMigrate`, and execute SQL with `c.db.execute(...)`. ## Queries `c.db.execute(...)` returns an array of row objects for `SELECT` queries. ```ts @nocheck const rows = await c.db.execute( "SELECT id, title FROM todos WHERE title LIKE ?", `%$%`, ); ``` ### Parameterized queries Use `?` placeholders for dynamic values and pass parameters in order after the SQL string. ```ts @nocheck await c.db.execute("INSERT INTO todos (title) VALUES (?)", title); ``` ### Transactions Use transactions when multiple writes must succeed or fail together. - Start with `BEGIN`. - End with `COMMIT` if all queries succeed. - On error, run `ROLLBACK` and rethrow. - A transaction is global for the entire shared `c.db` connection. Be careful with other interleaved queries. ```ts @nocheck await c.db.execute("BEGIN"); try catch (error) ``` ## Queues It's recommended to use queues for mutations and actions for read-only queries. This is the same code structure as the basic setup, but mutation writes are routed through queues. ## Debugging - `GET /inspector/summary` includes `isDatabaseEnabled` so you can confirm SQLite is configured. - `GET /inspector/traces` helps inspect slow query paths and SQL-heavy actions. - Keep a small read-only action for quick query verification while debugging. - In non-dev mode, inspector endpoints require authorization. ## Recommendations - Keep schema creation and migration steps in `onMigrate`. - Use `?` placeholders for dynamic values. - Prefer queue-driven writes for ordered or background work. - Use transactions for related multi-step mutations when atomicity matters. ## Read more - [SQLite + Drizzle in Rivet Actors](/docs/actors/sqlite-drizzle) - [SQLite docs](https://sqlite.org/docs.html) - [SQLite SQL language reference](https://sqlite.org/lang.html) ## SQLite + Drizzle Use Drizzle when you want typed schema, typed queries, and generated migrations on top of actor-local SQLite. ## What is Drizzle good for? - **Typed schema**: define tables in TypeScript and get typed query results. - **Typed query builder**: write SQL-like queries with autocompletion. - **Migration workflow**: generate SQL migration files from schema changes. - **Raw SQL escape hatch**: use `c.db.execute(...)` for direct SQLite when needed. ## Project structure Use one folder per actor database: ```txt src/ actors/ todo-list/ index.ts schema.ts drizzle.config.ts drizzle/ 0000_init.sql migrations.js migrations.d.ts meta/ _journal.json ``` - `index.ts` is the actor implementation. - `drizzle/` contains files managed by `drizzle-kit`. - Commit generated migration files to source control. ## Basic setup ## Queries ### Query builder Use Drizzle's typed query APIs for most reads and writes. ```ts @nocheck await c.db.insert(todos).values(); const rows = await c.db .select() .from(todos) .where(eq(todos.title, title)); ``` ### Raw SQL `rivetkit/db/drizzle` also exposes raw SQLite access through `c.db.execute(...)`. ```ts @nocheck await c.db.execute( "CREATE INDEX IF NOT EXISTS idx_todos_created_at ON todos(created_at)", ); ``` ## Queues Use queues for ordered mutations and keep actions read-only. ```ts @nocheck queues: >(), }, run: async (c) => ); } } }, actions: , ``` ## Recommendations - Prefer Drizzle query APIs for app code and use raw SQL for advanced SQLite features. - Keep one `drizzle.config.ts` per actor folder. - Re-run `db:generate` after schema changes and commit generated migration files. - Use queues for writes and actions for reads. - Keep related writes in one action or queue message to reduce interleaved query risk. ## Read more - [Drizzle SQLite quickstart](https://orm.drizzle.team/docs/get-started-sqlite) - [Drizzle `drizzle-kit generate`](https://orm.drizzle.team/docs/drizzle-kit-generate) - [Drizzle + Cloudflare D1](https://orm.drizzle.team/docs/connect-cloudflare-d1) - [Drizzle + Cloudflare Durable Objects](https://orm.drizzle.team/docs/connect-cloudflare-do) - [Cloudflare Durable Objects SQLite storage](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) ## In-Memory State ## Initializing State There are two ways to define an actor's initial state: 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: number) => } }); ``` 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 // Mock risky operation async function someRiskyOperation() const criticalProcess = actor(, actions: `); // Force save state before the async operation await 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 ## Debugging - `GET /inspector/state` returns the actor's current persisted state and `isStateEnabled`. - `PATCH /inspector/state` lets you set state directly while debugging. - In non-dev mode, inspector endpoints require authorization. ## 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 # 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 // Define the actor const myActor = actor(, actions: , getState: (c) => } }); // Create the registry const registry = setup( }); // Test the actor test("my actor test", async (testCtx) => = await setupTest(testCtx, registry); // Now you can interact with your actor through the client const myActorHandle = client.myActor.get(["test"]); // Test your actor's functionality await myActorHandle.someAction(); // Make assertions const result = await myActorHandle.getState(); expect(result).toEqual("updated"); }); ``` ## 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 // Define the counter actor const counter = actor(, actions: , getCount: (c) => } }); // Create the registry const registry = setup( }); // Test state persistence test("actor should persist state", async (testCtx) => = await setupTest(testCtx, registry); const counterHandle = client.counter.get(["test"]); // Initial state expect(await counterHandle.getCount()).toBe(0); // Modify state await counterHandle.increment(); // Verify state was updated expect(await counterHandle.getCount()).toBe(1); }); ``` ## Testing Events For actors that emit events, you can verify events are correctly triggered by subscribing to them: ```ts interface ChatMessage // Define the chat room actor const chatRoom = actor(, actions: ); c.broadcast("newMessage", username, message); }, getHistory: (c) => , }, }); // Create the registry const registry = setup( }); // Test event emission test("actor should emit events", async (testCtx) => = await setupTest(testCtx, registry); const chatRoomHandle = client.chatRoom.get(["test"]); // Set up event handler with a mock function const mockHandler = vi.fn(); const conn = chatRoomHandle.connect(); conn.on("newMessage", mockHandler); // Trigger the event await conn.sendMessage("testUser", "Hello world"); // Wait for the event to be emitted await vi.waitFor(() => ); }); ``` ## Testing Schedules Rivet's schedule functionality can be tested using Vitest's time manipulation utilities: ```ts // Define the scheduler actor const scheduler = actor(, actions: ; }, completeTask: (c, taskName: string) => ; }, getCompletedTasks: (c) => } }); // Create the registry const registry = setup( }); // Test scheduled tasks test("scheduled tasks should execute", async (testCtx) => = await setupTest(testCtx, registry); const schedulerHandle = client.scheduler.get(["test"]); // Set up a scheduled task await schedulerHandle.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 schedulerHandle.getCompletedTasks()).toContain("reminder"); }); ``` 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 ## 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 ) } ``` ### All Context Types Each lifecycle hook and handler has a corresponding `*ContextOf` type, exported from `"rivetkit"`. Pass `typeof myActor` as the type parameter. | Hook / Handler | Context Type | |---|---| | `createState` | `CreateContextOf` | | `createVars` | `CreateVarsContextOf` | | `createConnState` | `CreateConnStateContextOf` | | `onBeforeConnect` | `BeforeConnectContextOf` | | `onConnect` | `ConnectContextOf` | | `onDisconnect` | `DisconnectContextOf` | | `onDestroy` | `DestroyContextOf` | | `onWake` | `WakeContextOf` | | `onSleep` | `SleepContextOf` | | `onStateChange` | `StateChangeContextOf` | | `onBeforeActionResponse` | `BeforeActionResponseContextOf` | | `actions.*` | `ActionContextOf` | | `run` | `RunContextOf` | | `workflow` root context helpers | `WorkflowContextOf` | | `workflow` loop helpers | `WorkflowLoopContextOf` | | `workflow` branch helpers | `WorkflowBranchContextOf` | | `workflow` standalone step helpers | `WorkflowStepContextOf` | | `onRequest` | `RequestContextOf` | | `onWebSocket` | `WebSocketContextOf` | `ActorContextOf`, `ConnContextOf`, and `ConnInitContextOf` are general-purpose base context types useful for helper functions that don't correspond to a specific hook. Workflow context extractors are exported from both `"rivetkit"` and `"rivetkit/workflow"`. ## Versions & Upgrades ## How Versions Work Each runner has a **version number**. When you deploy new code with a new version, Rivet handles the transition automatically: - **New actors go to the newest version**: When allocating actors, Rivet always prefers runners with the highest version number - **Multiple versions can coexist**: Old actors continue running on old versions while new actors are created on the new version - **Drain old actors**: When enabled, a runner connecting with a newer version number will gracefully stop old actors to be rescheduled to the new version ### Example Scenario ## Configuration ### Setting the Version Configure the runner version using an environment variable or programmatically: We recommend injecting a value at built time that increments every deployment, such as: - Build timestamp - Git commit number (`git rev-list --count HEAD`) - CI build number ([`github.run_number`](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#:~:text=github%2Erun%5Fnumber)) ### Drain on Version Upgrade The `drainOnVersionUpgrade` option controls whether old actors are stopped when a new version is deployed. This is configured in the Rivet dashboard under your runner configuration. | Value | Behavior | |-------|----------| | `false` (default) | Old actors continue running. New actors go to new version. Versions coexist. | | `true` | Old actors receive stop signal and have 30s to finish gracefully. | ## Advanced ### How Version Upgrade Detection Works When `drainOnVersionUpgrade` is enabled, Rivet uses two mechanisms to detect version changes: **New Runner Connection** When a runner connects with a newer version number, the engine immediately drains all older runners with the same name. This is the primary mechanism for [runner mode](/docs/general/runtime-modes) deployments. In serverless mode, this also provides faster upgrades if a new request arrives before the next metadata poll interval. **Metadata Polling** (Serverless Only) In [serverless mode](/docs/general/runtime-modes), runners poll the engine's metadata endpoint periodically to check for newer versions. If a newer version is detected, the runner initiates a self-drain. This is necessary because serverless functions may continue handling requests without reconnecting after a new deployment. Polling ensures old runners eventually drain even if no new requests trigger a runner connection. ## Related - [Runtime Modes](/docs/general/runtime-modes): Serverless vs runner deployment modes - [Lifecycle](/docs/actors/lifecycle): Actor lifecycle hooks including `onSleep` ## Low-Level WebSocket 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 state: , onWebSocket: (c, websocket) => )); }); websocket.addEventListener("message", (event) => ); }); }, actions: }); ``` 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. The `.websocket()` method returns a standard WebSocket. ### Via getGatewayUrl Use `.getGatewayUrl()` to get the raw gateway URL for the actor. This is useful when you need to use the URL with external tools or custom WebSocket clients. ### 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: 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`: 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 chatActor = actor(, actions: }); const registry = setup( }); 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: MessageEvent, ws: WSContext) => , onClose: () => , }; })); ``` 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 state: , options: , actions: }); ``` 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 const myActor = actor(, onWebSocket: (c, websocket) => }, actions: }); ``` ### Async Handlers The `onWebSocket` handler can be async, allowing you to perform async code before setting up event listeners: ```typescript const myActor = actor(, onWebSocket: async (c, websocket) => )); }); websocket.addEventListener("message", (event) => ); }, actions: }); ``` ## 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 ## Workflows Use workflows for durable, multi-step execution with replay safety. ## What are workflows? A workflow is a durable, replayable run handler for a Rivet Actor. - Survives restarts: workflow progress is saved automatically. - Re-runs safely: replay follows the same recorded steps. - Event-driven: workflows can pause for queue messages, then continue. ## Getting started ### Simple workflow Use this when you need a short multi-step sequence. ### Loops This is the recommended workflow shape for most actor workloads. - Use a queue wait inside the loop to receive the next unit of work. - Keep actor state changes in a single workflow loop. - This gives you one durable workflow that manages all actor progress. ### Setup & teardown Use this when the workflow should initialize resources, process queued commands, then clean up. ## Features ### Queue Use this for fire-and-forget commands where the client does not need a reply. Use the `Loops` example above as the baseline pattern. ### Request/response (using queue) Use this when the caller needs a response from queued processing. ### Timers Use queue messages as the trigger source, then sleep durably inside the workflow. ### Join Use `join` when several independent tasks can run in parallel. ### Race Use `race` when you need first-winner behavior. ### Timeouts Use step timeouts and retries for slow or flaky dependencies. ```ts async function chargeCard(orderId: string): Promise `; } state: , queues: >(), }, run: workflow(async (ctx) => ); await loopCtx.step("save-charge", async () => ); }); }), }); use: }); ``` ### Rollback Use rollback checkpoints before steps that have compensating actions. ```ts state: , queues: >(), }, run: workflow(async (ctx) => , }); await loopCtx.step(, }); await loopCtx.step("confirm", async () => ); }); }), actions: , }); async function reserveInventory(orderId: string): Promise ), }); return ((await res.json()) as ).reservationId; } async function releaseInventory(reservationId: string): Promise /release`, ); } async function chargeCard(orderId: string): Promise ` }, body: JSON.stringify(), }); return ((await res.json()) as ).id; } async function refundCharge(chargeId: string): Promise ` }, body: JSON.stringify(), }); } use: }); ``` ## Patterns ### Store workflow progress in state + broadcast Store progress in `state` so replay and recovery always restore it. Broadcast state changes so clients can render progress in realtime. ### Cron (queue-driven) Rivet scheduling triggers actions. For cron-like workflows, use a small scheduled action as a bridge that enqueues work, then process that work in the workflow loop. ```ts function nextMinute(timestamp: number): number state: , queues: >(), }, onCreate: async (c) => , actions: ); const nextTickAt = nextMinute(scheduledAt + 1); await c.schedule.at(nextTickAt, "enqueueCronTick", nextTickAt); }, getState: (c) => c.state, }, run: workflow(async (ctx) => ); }); }), }); use: }); ``` These are common workflow shapes used in production systems. ### Queue-driven worker Use this when external systems enqueue work and the actor should process each item durably. ```ts type Job = ; state: , run: workflow(async (ctx) => ); if (!message) return; const job = message.body as Job; await loopCtx.step("process-job", async () => ); }); }), actions: , }); use: }); ``` ### Setup & teardown Use this when you need one-time initialization before a long-lived loop, plus cleanup when the actor stops sleeping or is destroyed. ```ts function openResource(): string function closeResource(_resource: string): void vars: , state: , onWake: (c) => , onSleep: (c) => , run: workflow(async (ctx) => ); await ctx.loop("main-loop", async (loopCtx) => ); }); }), actions: , }); use: }); ``` ### Human approval gate Use this when an operation must pause for a user or system decision before continuing. ```ts state: , queues: >(), }, run: workflow(async (ctx) => ); const decision = await ctx.queue.next("wait-approval"); if (decision.body.approved) ); } else ); } }), actions: , }); async function validateOrder(orderId: string): Promise /validate`, , ); if (!res.ok) throw new Error("Order validation failed"); } async function fulfillOrder(orderId: string): Promise /fulfill`, ); } async function cancelOrder(orderId: string): Promise /cancel`, ); } use: }); ``` ### Fan-out / fan-in (join) Use this when independent work items can run in parallel and you need a single merged result. ```ts state: , run: workflow(async (ctx) => ); if (!message) return; const joined = await loopCtx.join("parallel-work", , orders: , invoices: , }); await loopCtx.step("merge-results", async () => ); }); }), actions: , }); async function fetchCount(path: string): Promise `); if (!res.ok) throw new Error(`fetch $ failed: $`); return ((await res.json()) as ).count; } use: }); ``` ### Batch drainer Use this when throughput matters and handling one message at a time is too expensive. ```ts type MetricMessage = ; state: , run: workflow(async (ctx) => ); if (message) ); } if (loopCtx.state.pending.length flushBatch(loopCtx)); }); }), actions: , }); function flushBatch(ctx: WorkflowLoopContextOf): void use: }); ``` ### Coordinator -> worker RPC Use this when one actor orchestrates work by calling actions on other actors. ```ts type TaskMessage = ; actions: , }); state: , run: workflow(async (ctx) => ); if (!message) return; const task = message.body as TaskMessage; const result = await loopCtx.step("dispatch-rpc", async () => dispatchTask(loopCtx, task), ); await loopCtx.step("record-result", async () => ); }); }), actions: , }); async function dispatchTask( ctx: WorkflowLoopContextOf, task: TaskMessage, ): Promise use: }); ``` ### Request/response over queue (async RPC) Use this when you want decoupled actor-to-actor communication with durable waits and explicit completion. ### Scatter-gather across actors Use this when multiple actors can process independent parts of a request in parallel, then return a merged response. ```ts type ScatterMessage = ; actions: , }); state: , run: workflow(async (ctx) => ); if (!message) return; const scatter = message.body as ScatterMessage; const gathered = await loopCtx.join("gather", , shardB: , shardC: , }); await loopCtx.step("aggregate", async () => ); }); }), actions: , }); async function callShard( ctx: WorkflowLoopContextOf, shardId: "a" | "b" | "c", input: number, ): Promise use: }); ``` ### Timeout + fallback actor Use this when a primary actor call might be slow or unavailable and you need a deterministic fallback path. ```ts actions: , }, }); actions: , }); state: , run: workflow(async (ctx) => ); const winner = await loopCtx.race("primary-vs-timeout", [ , , }, ]); let value = winner.value as string; let source: "primary" | "fallback" = "primary"; if (winner.winner === "timeout") await loopCtx.step("record-choice", async () => ); }); }), actions: , }); async function callPrimaryValue( ctx: WorkflowLoopContextOf, ): Promise async function callFallbackValue( ctx: WorkflowLoopContextOf, ): Promise use: , }); ``` ### Cross-actor saga (compensating actions) Use this when a workflow spans multiple actors and each side effect may need compensation. ```ts type CheckoutMessage = ; actions: `, release: async (_c, reservationId: string) => reservationId, }, }); actions: `, refund: async (_c, chargeId: string) => chargeId, }, }); state: , run: workflow(async (ctx) => ); if (!message) return; const checkout = message.body as CheckoutMessage; await loopCtx.rollbackCheckpoint("checkout-saga"); await loopCtx.step(, }); await loopCtx.step(, }); await loopCtx.step("mark-complete", async () => markOrderComplete(loopCtx)); }); }), actions: , }); async function reserveInventoryForCheckout( ctx: WorkflowLoopContextOf, orderId: string, ): Promise async function releaseInventoryForCheckout( ctx: WorkflowLoopContextOf, reservationId: string, ): Promise async function chargeCheckout( ctx: WorkflowLoopContextOf, amount: number, ): Promise async function refundCheckout( ctx: WorkflowLoopContextOf, chargeId: string, ): Promise function markOrderComplete( ctx: WorkflowLoopContextOf, ): void use: , }); ``` ### Signal-driven control loop Use this when workflow progress should be triggered by commands/events instead of fixed polling intervals. ```ts type ControlSignal = ; state: , run: workflow(async (ctx) => ); if (!message) return; const signal = message.body as ControlSignal; await loopCtx.step("apply-signal", async () => applyControlSignal(loopCtx, signal.kind), ); }); }), actions: , }); function applyControlSignal( ctx: WorkflowLoopContextOf, kind: ControlSignal["kind"], ): void use: }); ``` ### Poll + backoff loop Use this when an external dependency has variable availability and retries should slow down after failures. ```ts async function pollExternal(attempt: number): Promise state: , run: workflow(async (ctx) => ); if (success) ); await loopCtx.sleep("healthy-interval", 1_000); return; } await loopCtx.step("grow-backoff", async () => ); await loopCtx.sleep("retry-delay", loopCtx.state.backoffMs); }); }), actions: , }); use: }); ``` ### Child worker orchestration Use this when one workflow coordinates many child workers (actors or worker workflows) and manages their lifecycle. ```ts type BatchMessage = ; actions: , }); state: , run: workflow(async (ctx) => ); if (!message) return; const batch = message.body as BatchMessage; const results = await loopCtx.join("collect-updates", , b: , c: , }); await loopCtx.step("reconcile", async () => ); }); }), actions: , }); async function startChildren( ctx: WorkflowContextOf, ): Promise async function runChildWorker( ctx: WorkflowBranchContextOf, workerId: "child-a" | "child-b" | "child-c", payload: number, ): Promise use: }); ``` ### Bounded drain + concurrency cap Use this when inbound work can spike and you need predictable per-iteration limits. ```ts type WorkMessage = ; const MAX_PER_ITERATION = 10; const CONCURRENCY_LIMIT = 3; async function processWork(value: number): Promise async function runWithLimit( limit: number, items: T[], fn: (item: T) => Promise, ): Promise , async () => }); await Promise.all(workers); } state: , run: workflow(async (ctx) => ); if (!message) break; window.push(message.body as WorkMessage); } if (window.length === 0) return; await loopCtx.step("process-window", async () => processWindow(loopCtx, window), ); }); }), actions: , }); async function processWindow( ctx: WorkflowLoopContextOf, window: WorkMessage[], ): Promise ); ctx.state.processed += window.length; ctx.state.lastWindowSize = window.length; ctx.state.lastWindowTotal = windowTotal; } use: }); ``` ### Versioned workflow evolution Use this when workflow structure changes across deployments and old histories must still replay. ```ts state: , run: workflow(async (ctx) => ); await ctx.removed("validate-v1", "step"); await ctx.loop("main-loop-v2", async (loopCtx) => ); }); }), actions: , }); use: }); ``` ### Checkpoint-friendly loop design Use this when you need reliable replay and resume semantics across crashes and restarts. ```ts type PaymentMessage = ; state: , run: workflow(async (ctx) => ); if (!message) return; const payment = message.body as PaymentMessage; await loopCtx.rollbackCheckpoint("apply-payment-checkpoint"); const plan = (await loopCtx.step("build-plan", async () => buildPaymentPlan(payment), )) as ; await loopCtx.step("apply-side-effects", async () => ); }); }), actions: , }); function buildPaymentPlan(payment: PaymentMessage): ; } use: }); ``` ## Migrations - Keep workflow entry names stable once deployed. - If an old entry was removed or renamed, call `ctx.removed(name, originalType)`. - This keeps replay compatible across deployments. ## Step-only access to actor APIs `state`, `vars`, `db`, `client()`, and connection/event APIs are only valid inside `ctx.step(...)` callbacks. Use non-step workflow code for orchestration only: queue waits, sleeps, loops, joins, races, and rollback boundaries. Keep actor-local side effects in steps. ## Debugging - `GET /inspector/workflow-history` returns workflow history status for an actor. - Response includes `isWorkflowEnabled` and `history`. - In non-dev mode, inspector endpoints require authorization. ## Recommendations - Prefer queue-driven loops for long-lived workflows. - Structure long-lived workflows with setup and teardown around the main loop. - Keep actor state changes and side effects inside steps. - Store workflow progress in `state` and broadcast updates as progress changes. - Use timeouts and rollback for external side effects. ## Clients The JavaScript, React, Swift, and SwiftUI clients automatically read `RIVET_ENDPOINT`, `RIVET_NAMESPACE`, `RIVET_TOKEN`, and `RIVET_RUNNER` for configuration. See each client page for defaults and examples. ## Node.js & Bun ## Getting Started See the [backend quickstart guide](/docs/actors/quickstart/backend) for getting started. ## Minimal Client ## Stateless vs Stateful ```typescript const client = createClient(); const handle = client.counter.getOrCreate(["my-counter"]); // Stateless: each call is independent await handle.increment(1); // Stateful: keep a connection open for realtime events const conn = handle.connect(); conn.on("count", (value: number) => console.log(value)); await conn.increment(1); ``` ## Getting Actors ```typescript const client = createClient(); const room = client.chatRoom.getOrCreate(["room-42"]); const existing = client.chatRoom.get(["room-42"]); const created = await client.game.create(["game-1"], , }); const byId = client.chatRoom.getForId("actor-id"); const resolvedId = await room.resolve(); ``` ## Connection Parameters ```typescript const client = createClient(); const chat = client.chatRoom.getOrCreate(["general"], , }); const conn = chat.connect(); ``` ## Subscribing to Events ```typescript const client = createClient(); const conn = client.chatRoom.getOrCreate(["general"]).connect(); conn.on("message", (msg: string) => console.log(msg)); conn.once("gameOver", () => console.log("done")); ``` ## Connection Lifecycle ```typescript const client = createClient(); const conn = client.chatRoom.getOrCreate(["general"]).connect(); conn.onOpen(() => console.log("connected")); conn.onClose(() => console.log("disconnected")); conn.onError((err) => console.error("error:", err)); conn.onStatusChange((status) => console.log("status:", status)); await conn.dispose(); ``` ## Low-Level HTTP & WebSocket For actors that implement `onRequest` or `onWebSocket`, call them directly: ```ts @nocheck const client = createClient(); const handle = client.chatRoom.getOrCreate(["general"]); const response = await handle.fetch("history"); const history = await response.json(); const ws = await handle.webSocket("stream"); ws.addEventListener("message", (event) => ); ws.send("hello"); ``` ## Calling from Backend ```typescript const app = new Hono(); const client = createClient(); app.post("/increment/:name", async (c) => ); }); ``` ## Error Handling ```typescript const client = createClient(); try catch (error) } ``` ## Concepts ### Keys Keys uniquely identify actor instances. Use compound keys (arrays) for hierarchical addressing: Don't build keys with string interpolation like `"org:$"` when `userId` contains user data. Use arrays instead to prevent key injection attacks. ### Environment Variables `createClient()` automatically reads: - `RIVET_ENDPOINT` (endpoint) - `RIVET_NAMESPACE` - `RIVET_TOKEN` - `RIVET_RUNNER` Defaults to `window.location.origin + "/api/rivet"` in the browser or `http://127.0.0.1:6420` on the server when unset. ### Endpoint Format Endpoints support URL auth syntax: ``` https://namespace:token@api.rivet.dev ``` You can also pass the endpoint without auth and provide `RIVET_NAMESPACE` and `RIVET_TOKEN` separately. For serverless deployments, use your app's `/api/rivet` URL. See [Endpoints](/docs/general/endpoints#url-auth-syntax) for details. ## API Reference **Package:** [rivetkit](https://www.npmjs.com/package/rivetkit) See the [RivetKit client overview](/docs/clients). - [`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 ## React ## Getting Started See the [React quickstart guide](/docs/actors/quickstart/react) for getting started. ## Install ## Minimal Client ## Stateless vs Stateful ```tsx const = createRivetKit(); function Counter() ); const increment = async () => ; return +; } ``` ```tsx // Stateless: use createClient for one-off calls (SSR or utilities) const client = createClient(); await client.counter.getOrCreate(["my-counter"]).increment(1); ``` ## Getting Actors ```tsx const = createRivetKit(); function ChatRoom() ); return ; } // For get/getOrCreate/create/getForId, use createClient const client = createClient(); const handle = client.chatRoom.getOrCreate(["room-42"]); const existing = client.chatRoom.get(["room-42"]); const created = await client.game.create(["game-1"], }); const byId = client.chatRoom.getForId("actor-id"); const resolvedId = await handle.resolve(); ``` ## Connection Parameters ```tsx const = createRivetKit(); function Chat() , }); return ; } ``` ## Subscribing to Events ```tsx const = createRivetKit(); function Chat() ); chat.useEvent("message", (msg) => ); return null; } ``` ## Connection Lifecycle ```tsx const = createRivetKit(); function CounterStatus() ); if (actor.connStatus === "connected") if (actor.error) return null; } ``` ## Low-Level HTTP & WebSocket Use the JavaScript client for raw HTTP and WebSocket access: ```tsx @nocheck const client = createClient(); const handle = client.chatRoom.getOrCreate(["general"]); const response = await handle.fetch("history"); const history = await response.json(); const ws = await handle.webSocket("stream"); ws.addEventListener("message", (event) => ); ws.send("hello"); ``` ## Calling from Backend Use the JavaScript client on your backend (Node.js/Bun). See the [JavaScript client docs](/docs/clients/javascript). ## Error Handling ```tsx const = createRivetKit(); function Profile() ); const updateUsername = async () => catch (error) } }; return Update; } ``` ## Concepts ### Keys Keys uniquely identify actor instances. Use compound keys (arrays) for hierarchical addressing: Don't build keys with string interpolation like `"org:$"` when `userId` contains user data. Use arrays instead to prevent key injection attacks. ### Environment Variables `createRivetKit()` (and the underlying `createClient()` instance) automatically read: - `RIVET_ENDPOINT` - `RIVET_NAMESPACE` - `RIVET_TOKEN` - `RIVET_RUNNER` Defaults to `window.location.origin + "/api/rivet"` in the browser or `http://127.0.0.1:6420` on the server when unset. ### Endpoint Format Endpoints support URL auth syntax: ``` https://namespace:token@api.rivet.dev ``` You can also pass the endpoint without auth and provide `RIVET_NAMESPACE` and `RIVET_TOKEN` separately. For serverless deployments, use your app's `/api/rivet` URL. See [Endpoints](/docs/general/endpoints#url-auth-syntax) for details. ## API Reference **Package:** [@rivetkit/react](https://www.npmjs.com/package/@rivetkit/react) - [`createRivetKit`](/docs/clients/react) - Create hooks for React - [`useActor`](/docs/clients/react) - Hook for actor instances ## Swift ## Install Add the Swift package dependency and import `RivetKitClient`: ```swift // Package.swift dependencies: [ .package(url: "https://github.com/rivet-dev/rivetkit-swift", from: "2.0.0") ] targets: [ .target( name: "MyApp", dependencies: [ .product(name: "RivetKitClient", package: "rivetkit-swift") ] ) ] ``` ## Minimal Client ## Stateless vs Stateful ```swift import RivetKitClient let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) let handle = client.getOrCreate("counter", ["my-counter"]) // Stateless: each call is independent let current: Int = try await handle.action("getCount", as: Int.self) print("Current count: \(current)") // Stateful: keep a connection open for realtime events let conn = handle.connect() // Subscribe to events using AsyncStream let eventTask = Task } _ = try await conn.action("increment", 1, as: Int.self) eventTask.cancel() await conn.dispose() await client.dispose() ``` ## Getting Actors ```swift import RivetKitClient struct GameInput: Encodable let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) // Get or create an actor let room = client.getOrCreate("chatRoom", ["room-42"]) // Get an existing actor (fails if not found) let existing = client.get("chatRoom", ["room-42"]) // Create a new actor with input let created = try await client.create( "game", ["game-1"], options: CreateOptions(input: GameInput(mode: "ranked")) ) // Get actor by ID let byId = client.getForId("chatRoom", "actor-id") // Resolve actor ID let resolvedId = try await room.resolve() print("Resolved ID: \(resolvedId)") await client.dispose() ``` Actions support positional overloads for 0–5 args: ```swift import RivetKitClient let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) let handle = client.getOrCreate("counter", ["my-counter"]) let count: Int = try await handle.action("getCount") let updated: String = try await handle.action("rename", "new-name") let ok: Bool = try await handle.action("setScore", "user-1", 42) print("Count: \(count), Updated: \(updated), OK: \(ok)") await client.dispose() ``` If you need more than 5 arguments, use the raw JSON fallback: ```swift import RivetKitClient let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) let handle = client.getOrCreate("counter", ["my-counter"]) let args: [JSONValue] = [ .string("user-1"), .number(.int(42)), .string("extra"), .string("more"), .string("args"), .string("here") ] let ok: Bool = try await handle.action("setScore", args: args, as: Bool.self) print("OK: \(ok)") await client.dispose() ``` ## Connection Parameters ```swift import RivetKitClient struct ConnParams: Encodable let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) let chat = client.getOrCreate( "chatRoom", ["general"], options: GetOrCreateOptions(params: ConnParams(authToken: "jwt-token-here")) ) let conn = chat.connect() // Use the connection... for await status in await conn.statusChanges() } await conn.dispose() await client.dispose() ``` ## Subscribing to Events ```swift import RivetKitClient let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) let conn = client.getOrCreate("chatRoom", ["general"]).connect() // Subscribe to events using AsyncStream let messageTask = Task } // For one-time events, break after receiving let gameOverTask = Task } // Let it run for a bit try await Task.sleep(for: .seconds(5)) // Cancel when done messageTask.cancel() gameOverTask.cancel() await conn.dispose() await client.dispose() ``` Event streams support 0–5 typed arguments. If you need raw values or more than 5 arguments, use `JSONValue`: ```swift import RivetKitClient let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) let conn = client.getOrCreate("chatRoom", ["general"]).connect() let rawTask = Task } try await Task.sleep(for: .seconds(5)) rawTask.cancel() await conn.dispose() await client.dispose() ``` ## Connection Lifecycle ```swift import RivetKitClient let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) let conn = client.getOrCreate("chatRoom", ["general"]).connect() // Monitor status changes (immediately yields current status) let statusTask = Task } // Monitor errors let errorTask = Task } // Monitor open/close events let openTask = Task } let closeTask = Task } // Check current status let current = await conn.currentStatus print("Current status: \(current.rawValue)") // Let it run for a bit try await Task.sleep(for: .seconds(5)) // Cleanup statusTask.cancel() errorTask.cancel() openTask.cancel() closeTask.cancel() await conn.dispose() await client.dispose() ``` ## Low-Level HTTP & WebSocket For actors that implement `onRequest` or `onWebSocket`, you can call them directly: ```swift import RivetKitClient let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) let handle = client.getOrCreate("chatRoom", ["general"]) // Raw HTTP request let response = try await handle.fetch("history") let history: [String] = try response.json([String].self) print("History: \(history)") // Raw WebSocket connection let websocket = try await handle.websocket(path: "stream") try await websocket.send(text: "hello") let message = try await websocket.receive() print("Received: \(message)") await client.dispose() ``` ## Calling from Backend Use the same client in server-side Swift (Vapor, Hummingbird, etc.): ```swift import RivetKitClient let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) let handle = client.getOrCreate("counter", ["server-counter"]) let count: Int = try await handle.action("increment", 1, as: Int.self) print("Count: \(count)") await client.dispose() ``` ## Error Handling ```swift import RivetKitClient let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) do catch let error as ActorError await client.dispose() ``` If you need an untyped response, you can decode to `JSONValue`: ```swift import RivetKitClient let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) let handle = client.getOrCreate("data", ["raw"]) let value: JSONValue = try await handle.action("getRawPayload") print("Raw value: \(value)") await client.dispose() ``` ## Concepts ### Keys Keys uniquely identify actor instances. Use compound keys (arrays) for hierarchical addressing: ```swift import RivetKitClient let config = try ClientConfig(endpoint: "http://localhost:3000/api/rivet") let client = RivetKitClient(config: config) // Use compound keys for hierarchical addressing let room = client.getOrCreate("chatRoom", ["org-acme", "general"]) let actorId = try await room.resolve() print("Actor ID: \(actorId)") await client.dispose() ``` Don't build keys with string interpolation like `"org:\(userId)"` when `userId` contains user data. Use arrays instead to prevent key injection attacks. ### Environment Variables `ClientConfig` reads optional values from environment variables: - `RIVET_NAMESPACE` - Namespace (can also be in endpoint URL) - `RIVET_TOKEN` - Authentication token (can also be in endpoint URL) - `RIVET_RUNNER` - Runner name (defaults to `"default"`) The `endpoint` parameter is always required. There is no default endpoint. ### Endpoint Format Endpoints support URL auth syntax: ``` https://namespace:token@api.rivet.dev ``` You can also pass the endpoint without auth and provide `RIVET_NAMESPACE` and `RIVET_TOKEN` separately. For serverless deployments, set the endpoint to your app's `/api/rivet` URL. See [Endpoints](/docs/general/endpoints#url-auth-syntax) for details. ## API Reference ### Client - `RivetKitClient(config:)` - Create a client with a config - `ClientConfig` - Configure endpoint, namespace, and token - `client.get()` / `getOrCreate()` / `getForId()` / `create()` - Get actor handles - `client.dispose()` - Dispose the client and all connections ### ActorHandle - `handle.action(name, args..., as:)` - Stateless action call - `handle.connect()` - Create a stateful connection - `handle.resolve()` - Get the actor ID - `handle.getGatewayUrl()` - Get the raw gateway URL - `handle.fetch(path, request:)` - Raw HTTP request - `handle.websocket(path:)` - Raw WebSocket connection ### ActorConnection - `conn.action(name, args..., as:)` - Action call over WebSocket - `conn.events(name, as:)` - AsyncStream of typed events - `conn.statusChanges()` - AsyncStream of status changes - `conn.errors()` - AsyncStream of connection errors - `conn.opens()` - AsyncStream that yields on connection open - `conn.closes()` - AsyncStream that yields on connection close - `conn.currentStatus` - Current connection status - `conn.dispose()` - Close the connection ### Types - `ActorConnStatus` - Connection status enum (`.idle`, `.connecting`, `.connected`, `.disconnected`, `.disposed`) - `ActorError` - Typed actor errors with `group`, `code`, `message`, `metadata` - `JSONValue` - Raw JSON value for untyped responses ## SwiftUI ## Install Add the Swift package dependency and import `RivetKitSwiftUI`: ```swift // Package.swift dependencies: [ .package(url: "https://github.com/rivet-dev/rivetkit-swift", from: "2.0.0") ] targets: [ .target( name: "MyApp", dependencies: [ .product(name: "RivetKitSwiftUI", package: "rivetkit-swift") ] ) ] ``` `RivetKitSwiftUI` re-exports `RivetKitClient` and `SwiftUI`, so a single import covers both. ## Minimal Client ## Actor Options The `@Actor` property wrapper always uses get-or-create semantics and accepts: - `name` (required) - `key` as `String` or `[String]` (required) - `params` (optional connection parameters) - `createWithInput` (optional creation input) - `createInRegion` (optional creation hint) - `enabled` (toggle connection lifecycle) ```swift import RivetKitSwiftUI import SwiftUI struct ConnParams: Encodable struct ChatView: View } ``` ## Actions ```swift import RivetKitSwiftUI import SwiftUI struct CounterView: View } Button("Increment") } } } ``` ## Subscribing to Events ```swift import RivetKitSwiftUI import SwiftUI struct GameView: View } .onActorEvent(game, "newCount") .onActorEvent(game, "gameOver") } } ``` ## Async Event Streams ```swift import RivetKitSwiftUI import SwiftUI struct ChatView: View .task } } } ``` ## Connection Status ```swift import RivetKitSwiftUI import SwiftUI struct StatusView: View Button("Fetch via Handle") } } .disabled(!counter.isConnected) } } } ``` ## Error Handling ```swift import RivetKitSwiftUI import SwiftUI struct UserView: View catch let error as ActorError } } if let errorMessage } .onActorError(user) } } ``` ## Concepts ### Keys Keys uniquely identify actor instances. Use compound keys (arrays) for hierarchical addressing: ```swift import RivetKitSwiftUI import SwiftUI struct OrgChatView: View } ``` Don't build keys with string interpolation like `"org:\(userId)"` when `userId` contains user data. Use arrays instead to prevent key injection attacks. ### Environment Configuration Call `.rivetKit(endpoint:)` or `.rivetKit(client:)` once at the root of your view tree: ```swift // With endpoint string (recommended for most apps) @main struct MyApp: App } } // With custom client (for advanced configuration) @main struct MyApp: App } } ``` When using `.rivetKit(endpoint:)`, the client is created once and cached per endpoint. When using `.rivetKit(client:)`, store the client as a property on `App` (not inside `body`) since SwiftUI can call `body` multiple times. ### Environment Variables `ClientConfig` reads optional values from environment variables: - `RIVET_NAMESPACE` - Namespace (can also be in endpoint URL) - `RIVET_TOKEN` - Authentication token (can also be in endpoint URL) - `RIVET_RUNNER` - Runner name (defaults to `"default"`) The endpoint is always required. There is no default endpoint. ### Endpoint Format Endpoints support URL auth syntax: ``` https://namespace:token@api.rivet.dev ``` You can also pass the endpoint without auth and provide `RIVET_NAMESPACE` and `RIVET_TOKEN` separately. For serverless deployments, set the endpoint to your app's `/api/rivet` URL. See [Endpoints](/docs/general/endpoints#url-auth-syntax) for details. ## API Reference ### Property Wrapper - `@Actor(name, key:, params:, createWithInput:, createInRegion:, enabled:)` - SwiftUI property wrapper for actor connections ### View Modifiers - `.rivetKit(endpoint:)` - Configure client with an endpoint URL (creates cached client) - `.rivetKit(client:)` - Configure client with a custom instance - `.onActorEvent(actor, event) ` - Subscribe to actor events (supports 0–5 typed args) - `.onActorError(actor) ` - Handle actor errors ### ActorObservable - `actor.action(name, args..., as:)` - Async action call - `actor.send(name, args...)` - Fire-and-forget action - `actor.events(name, as:)` - AsyncStream of typed events - `actor.connStatus` - Current connection status - `actor.isConnected` - Whether connected - `actor.handle` - Underlying `ActorHandle` (optional) - `actor.connection` - Underlying `ActorConnection` (optional) - `actor.error` - Most recent error (optional) ### Types - `ActorConnStatus` - Connection status enum (`.idle`, `.connecting`, `.connected`, `.disconnected`, `.disposed`) - `ActorError` - Typed actor errors with `group`, `code`, `message`, `metadata` ## Deploy ))} ## Deploying to AWS ECS 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 ## Deploying to Cloudflare Workers Deploy your Cloudflare Workers + RivetKit app to [Cloudflare Workers](https://workers.cloudflare.com/). ## Steps ## Advanced ### Accessing Environment Bindings You can access Cloudflare Workers environment bindings directly using the importable `env`: ```typescript @nocheck // 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 @nocheck 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 @nocheck interface DriverContext ``` ## Custom Platform ## Steps ## Requirements Your hosting platform must support: - **HTTP endpoints** - Your app must be reachable via a public URL - **Environment variables** - To configure the Rivet connection - **Persistent processes or serverless functions** - To handle incoming requests to the `/api/rivet` endpoint ## Troubleshooting ### Connection fails - Verify your app is publicly accessible - Check that the `/api/rivet` endpoint returns a valid response - Ensure environment variables are correctly set ### Actors not responding - Confirm `RIVET_ENDPOINT` and `RIVET_PUBLIC_ENDPOINT` are both configured - Check your hosting platform's logs for connection errors ## Deploying to Freestyle 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. ## Setup ## Deploying to Google Cloud Run ## Steps ## Deploying to Hetzner ## Deploying to Kubernetes ## Steps ## Deploying to Railway ## Steps ## Supabase ## Deploying to Vercel ## Steps ## Troubleshooting ## Deploying to VMs & Bare Metal ## Steps ## Operating ### Restart Restart the service after deploying new builds or environment changes: ```bash sudo systemctl restart rivetkit-app.service ``` ### Logs Follow realtime logs when debugging: ```bash sudo journalctl -u rivetkit-app.service -f ``` ## Actor Configuration ## Basic Example ```typescript const myActor = actor(, actions: , }, options: }); const registry = setup(, }); ``` ## Configuration Reference ## Related - [Registry Configuration](/docs/general/registry-configuration): Configure the RivetKit registry - [State](/docs/actors/state): Managing actor state - [Actions](/docs/actors/actions): Defining actor actions - [Lifecycle](/docs/actors/lifecycle): Actor lifecycle hooks ## 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 `RIVET_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 - Rivet uses 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/gateway/* and it automatically routes to the correct actor in the appropriate 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 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 server.ts 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 client.ts const myActor = actor(, actions: }); const registry = setup( }); const client = createClient(); try catch (error) } ``` ## Documentation for LLMs & AI ## Skills (Recommended) For AI coding assistants like Claude Code, Cursor, or Windsurf, install Rivet skills for the best development experience: ```sh npx skills add rivet-dev/skills ``` Skills provide your AI assistant with Rivet-specific knowledge, best practices, and code patterns directly in your project context. ## 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 ## 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 example = actor(, actions: }); const registry = setup( }); const client = createClient(); // Create actor in a specific region using getOrCreate const actorHandle = client.example.getOrCreate(["my-actor"], ); ``` See [Create Manage Actors](/docs/actors/communicating-between-actors) for more information. ## Endpoints ## Local Development No configuration is needed for local development. RivetKit runs entirely on your local machine without any extra configuration to run Rivet Actors. ## Production Deployment When deploying to production, you need to configure endpoints so your backend can communicate with Rivet Engine and clients can reach your actors. ### Private Endpoint The private endpoint tells your backend where to find the Rivet Engine. ### Public Endpoint The public endpoint tells clients where to connect to reach your actors. This endpoint and token will be exposed to the internet. Use a public token (`pk_`), not your secret token (`sk_`). The public endpoint is only required if using the [serverless runtime mode](/docs/general/runtime-modes#runners) and if you have a frontend using RivetKit. ## Advanced ### URL Auth Syntax Endpoint URLs support embedding namespace and token directly in the URL: ``` https://namespace:token@host/path ``` This is the recommended approach for simplicity. Alternatively, you can use separate environment variables: ```bash RIVET_ENDPOINT=https://api.rivet.dev RIVET_NAMESPACE=my-namespace RIVET_TOKEN=sk_xxxxx ``` ### Security In serverless mode, the private endpoint is used to validate that requests to `GET /api/rivet/start` are coming from your trusted Rivet endpoint. If the private endpoint is not configured, anyone can run a self-hosted instance of Rivet and connect to your backend from any endpoint. ### How Clients Connect This flow applies to [serverless runtime mode](/docs/general/runtime-modes#serverless). For [runner runtime mode](/docs/general/runtime-modes#runners) or [clients configured to connect directly to Rivet](/docs/clients/javascript), clients connect directly to Rivet and this metadata flow is not needed. When a client connects to your serverless application, it follows this flow: 1. Client makes a request to `https://my-app.example.com/api/rivet/metadata` 2. Your app returns the public endpoint configuration: ```json ``` 3. Client caches these values and uses them for subsequent requests 4. Client connects to `https://api.rivet.dev/gateway/`, which routes requests to your actors This indirection exists because Rivet acts as a gateway between clients and your actors. This is because Rivet handles routing, load balancing, and actor lifecycle management of actors. ## Reference | Environment Variable | Config Option | Description | |---------------------|---------------|-------------| | `RIVET_ENDPOINT` | `endpoint` | Rivet Engine URL for your backend | | `RIVET_NAMESPACE` | `namespace` | Namespace for actor isolation | | `RIVET_TOKEN` | `token` | Authentication token for engine connection | | `RIVET_PUBLIC_ENDPOINT` | `serverless.publicEndpoint` | Client-facing endpoint | | `RIVET_PUBLIC_TOKEN` | `serverless.publicToken` | Client-facing token | ## Environment Variables ## Connection | Environment Variable | Description | |---------------------|-------------| | `RIVET_ENDPOINT` | Endpoint URL to connect to Rivet Engine. Supports [URL auth syntax](/docs/general/endpoints#url-auth-syntax). | | `RIVET_TOKEN` | Authentication token for Rivet Engine | | `RIVET_NAMESPACE` | Namespace to use (default: "default") | ## Public Endpoint These variables configure how clients connect to your actors. | Environment Variable | Description | |---------------------|-------------| | `RIVET_PUBLIC_ENDPOINT` | Public endpoint for client connections. Supports [URL auth syntax](/docs/general/endpoints#url-auth-syntax). | | `RIVET_PUBLIC_TOKEN` | Public token for client authentication | ## Runner Configuration | Environment Variable | Description | |---------------------|-------------| | `RIVET_RUNNER` | Runner name (default: "default") | | `RIVET_RUNNER_KEY` | Authentication key for the runner | | `RIVET_RUNNER_VERSION` | Version number for the runner. See [Versions & Upgrades](/docs/actors/versions). | | `RIVET_RUNNER_KIND` | Type of runner | | `RIVET_TOTAL_SLOTS` | Total actor slots available (default: 100000) | ## Engine | Environment Variable | Description | |---------------------|-------------| | `RIVET_RUN_ENGINE` | Set to `1` to spawn the engine process | | `RIVET_RUN_ENGINE_VERSION` | Version of engine to download | ## Inspector | Environment Variable | Description | |---------------------|-------------| | `RIVET_INSPECTOR_TOKEN` | Token for accessing the Rivet Inspector | | `RIVET_INSPECTOR_DISABLE` | Set to `1` to disable the inspector | ## Experimental | Environment Variable | Description | |---------------------|-------------| | `RIVET_EXPERIMENTAL_OTEL` | Set to `1` to enable experimental OTel tracing in Rivet Actors | ## Storage | Environment Variable | Description | |---------------------|-------------| | `RIVETKIT_STORAGE_PATH` | Overrides the default file-system storage path used by RivetKit when using the default driver. | ## Logging | Environment Variable | Description | |---------------------|-------------| | `RIVET_LOG_LEVEL` | Log level: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `silent` | | `RIVET_LOG_TARGET` | Set to `1` to include log target | | `RIVET_LOG_TIMESTAMP` | Set to `1` to include timestamps | | `RIVET_LOG_MESSAGE` | Set to `1` to include message formatting | | `RIVET_LOG_ERROR_STACK` | Set to `1` to include error stack traces | | `RIVET_LOG_HEADERS` | Set to `1` to log request headers | ## HTTP Server ## Methods of Running Your Server ### With Fetch Handlers A [fetch handler](https://wintercg.org/) is a function that takes a `Request` and returns a `Response`. This is the standard pattern used by Cloudflare Workers, Deno Deploy, Bun, and other modern runtimes. The simplest setup uses `registry.serve()`: ```ts server.ts const myActor = actor(, actions: }); const registry = setup( }); ``` To integrate with a router like [Hono](https://hono.dev/) or [Elysia](https://elysiajs.com/), use `registry.handler()`: Then run your server: ### Explicit HTTP Server If you need to explicitly start the HTTP server instead of using the fetch handler pattern: ## Logging 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 const myActor = actor(, actions: ); // 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 | | -------- | ----------- | ------ | ------- | | `RIVET_LOG_LEVEL` | Sets the minimum log level to display | `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `silent` | `warn` | | `RIVET_LOG_TARGET` | Include the module name that logged the message | `1` to enable, `0` to disable | `0` | | `RIVET_LOG_TIMESTAMP` | Include timestamp in log output | `1` to enable, `0` to disable | `0` | | `RIVET_LOG_MESSAGE` | Enable detailed message logging for debugging | `1` to enable, `0` to disable | `0` | | `RIVET_LOG_ERROR_STACK` | Include stack traces in error output | `1` to enable, `0` to disable | `0` | | `RIVET_LOG_HEADERS` | Log HTTP headers in requests | `1` to enable, `0` to disable | `0` | Example: ```bash RIVET_LOG_LEVEL=debug RIVET_LOG_TARGET=1 RIVET_LOG_TIMESTAMP=1 node server.js ``` ### Log Level You can configure the log level programmatically when setting up your registry: ```typescript const counter = actor(, actions: }); const registry = setup(, logging: }); ``` ### Custom Pino Logger You can also provide a custom Pino base logger for more advanced logging configurations: ```typescript const counter = actor(, actions: }); const customLogger = pino( }); const registry = setup(, logging: }); ``` 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 const counter = actor(, actions: }); const registry = setup(, noWelcome: true }); ``` ## Registry Configuration ## Example Configurations ### Basic Setup ```typescript const myActor = actor(, actions: }); const registry = setup(, }); ``` ### Connecting to Rivet Engine ## Starting Your App After configuring your registry, start it using one of two runtime modes: See [Runtime Modes](/docs/general/runtime-modes) for details on when to use each mode. ## Environment Variables Many configuration options can be set via environment variables. See [Environment Variables](/docs/general/environment-variables) for a complete reference. ## Configuration Reference ## Related - [Actor Configuration](/docs/general/actor-configuration): Configure individual actors - [HTTP Server Setup](/docs/general/http-server): Set up HTTP routing and middleware - [Architecture](/docs/general/architecture): Understand how RivetKit works ## Runtime Modes - **Serverless**: Default mode. Responds to HTTP requests and scales automatically. - **Runners**: Background processes without HTTP endpoints. Only needed for advanced scenarios. ## Serverless Serverless is the default and recommended mode. Rivet sends HTTP requests to your backend to run actor logic, allowing your infrastructure to scale automatically. ### Benefits - **Platform support**: Works with serverless platforms (Vercel, Cloudflare Workers, etc.) - **Scale to zero**: No cost when idle - **Edge deployments**: Easier to deploy to edge locations - **Preview deployments**: Integrates with preview deployments on platforms like Vercel and Railway - **Efficient autoscaling**: Request-based autoscaling can be faster and more efficient than CPU-based autoscaling depending on the platform ### Example See [Server Setup](/docs/general/http-server/) for more configuration options. ### Architecture When a client creates an actor, it sends a request to the Rivet Engine. The engine then calls `GET /api/rivet/start` on your serverless backend to run the actor. ### Advanced #### Endpoints Rivet exposes the following endpoints: - `GET /api/rivet/metadata`: Validates configuration - `GET /api/rivet/start`: Runs an actor You should never call these endpoints yourself, this is included purely for comprehension of how Rivet works under the hood. #### Timeouts Serverless platforms like Vercel have function timeouts. Rivet handles this automatically by migrating actors between function invocations, preserving state through `ctx.state`. Write your code as if it runs forever, Rivet handles the rest. Read more about [how we handle timeouts](/blog/2025-10-20-how-we-built-websocket-servers-for-vercel-functions/#timeouts-and-failover). ## Runners Runners run actors as long-running background processes without exposing an HTTP endpoint. ### When to Use Runners - **No HTTP server**: Your app does not or cannot expose an HTTP server - **No load balancer**: You don't have a load balancer to distribute HTTP requests across your servers - **Custom scaling**: You have custom scaling requirements ### Example ```typescript runner.ts const myActor = actor(, actions: }); const registry = setup( }); registry.startRunner(); ``` The runner runs in the background, ready to run actors. ### Architecture On startup, your backend calls `registry.startRunner()` which opens a persistent connection to the Rivet Engine. When a client creates an actor, the engine sends a command through this connection to start the actor on your backend. ### Configuration #### Runner Pool Use `RIVET_RUNNER` to assign runners to a pool. This lets you control which runners handle specific actors. ```bash RIVET_RUNNER=gpu-workers ``` ```typescript const myActor = actor(, actions: }); const registry = setup(, runner: , }); ``` #### Runner Key Use `RIVET_RUNNER_KEY` to uniquely identify a runner. If another runner connects with the same key, the previous one is disconnected. This handles zombie runners that weren't shut down gracefully. ```bash RIVET_RUNNER_KEY=worker-abc123 ``` ```typescript const myActor = actor(, actions: }); const registry = setup(, runner: , }); ``` ## Comparison | Mode | Method | Use Case | |------|--------|----------| | Serverless | `registry.serve()` | Default, recommended for most deployments | | Serverless | `registry.handler()` | Integrating with existing routers (Hono, Elysia, etc.) | | Runner | `registry.startRunner()` | Long-running processes without HTTP endpoints | ## Skills Skills are documentation files that help AI coding assistants (like Claude Code, Cursor, Windsurf, and others) understand how to work with Rivet. When you add skills, your AI assistant gets access to Rivet-specific knowledge, best practices, and code patterns. ## Installation Add Rivet skills to your project: ```sh npx skills add rivet-dev/skills ``` This command downloads the latest Rivet skills and makes them available to your AI coding assistant. ## What Skills Provide - **Actor patterns**: Best practices for designing and implementing actors - **State management**: How to work with persistent state effectively - **Realtime features**: Implementing broadcasts, events, and connections - **Deployment guides**: Platform-specific deployment instructions - **Error handling**: Common issues and their solutions ## Supported AI Assistants Skills work with any AI coding assistant that supports the skills format, including: - Claude Code - Cursor - Windsurf - And others ## Updating Skills To get the latest skills: ```sh npx skills add rivet-dev/skills ``` Running this command again will update your skills to the latest version. ## Quickstart ## Self-Hosting Overview # 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/general/endpoints) - [Configure your deployment](/docs/self-hosting/configuration) - [Multi-region setup](/docs/self-hosting/multi-region) ## Configuration # 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 ``` ## Configuration Reference ## Related - [PostgreSQL](/docs/self-hosting/postgres): Configure PostgreSQL for production - [File System](/docs/self-hosting/filesystem): Configure file system storage for development ## Docker Compose # .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: rivetdev/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: rivetdev/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 # 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 \ rivetdev/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" \ rivetdev/engine ``` ## Next Steps - Use [Docker Compose](/docs/self-hosting/docker-compose) for multi-container setups - See [Configuration](/docs/self-hosting/configuration) for all options ## File System ## Configuration ## Default Paths If no path is specified, Rivet uses platform-specific default locations: - Linux: `~/.local/share/rivet-engine/db` - macOS: `~/Library/Application Support/rivet-engine/db` - Windows: `%APPDATA%\rivet-engine\db` When running in a container or as a service, the path defaults to `./data/db` relative to the working directory. ## When to Use File System The file system backend is ideal for: - Local development - Single-node deployments - Testing and prototyping - Air-gapped environments without database infrastructure For production deployments with multiple nodes or high availability requirements, use [PostgreSQL](/docs/self-hosting/postgres) instead. ## Installing Rivet Engine For more options: - [Docker Container](/docs/self-hosting/docker-container) for persistent storage, configuration, and production setups - [Docker Compose](/docs/self-hosting/docker-compose) for multi-container deployments with PostgreSQL ## Docker ```bash docker run -p 6420:6420 rivetdev/engine ``` ## Prebuilt Binaries ## 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 # 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 ## PostgreSQL ## Basic Configuration ## Managed Postgres Compatibility Some hosted PostgreSQL platforms require additional configuration due to platform-specific restrictions. #### 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. ## SSL/TLS Support To enable SSL for Postgres, add `sslmode=require` to your PostgreSQL connection URL: 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: | 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`. ## Railway Deployment # 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: `rivetdev/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_ENDPOINT=$}` - Points to the Rivet Engine service's private domain