>>>> bun.mdx --- title: Bun --- import SetupActor from '/snippets/setup-actor.mdx'; import SetupNextSteps from '/snippets/setup-next-steps.mdx'; import MvpWarning from '/snippets/mvp-warning.mdx'; Install Bun [here](https://bun.sh/). Install Redis [here](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/). By default, ActorCore will connect to `redis://127.0.0.1:6379`. See [instructions on configuring Redis](#configuring-redis). ```sh # Create project mkdir my-project cd my-project bun init --yes # Install ActorCore bun add actor-core @actor-core/bun ``` ActorCore can also be used with existing Bun projects. Create a new file for your actor at `src/counter.ts`: Create `src/index.ts` to start your ActorCore server with: ```typescript src/index.ts import { createHandler } from "@actor-core/bun" import counter from "./counter.ts"; export default createHandler({ actors: { counter } }); ``` Create a client to connect to your actor in `src/client.ts`: ```typescript src/client.ts import { Client } from "actor-core/client"; import type Counter from "./counter.ts"; async function main() { const client = new Client("http://localhost:8787"); const counter = await client.get({ name: "counter" }); counter.on("newCount", (count: number) => console.log("Event:", count)); const out = await counter.increment(5); console.log("RPC:", out); await counter.disconnect(); } main(); ``` Start your development server with: ```sh bun src/index.ts ``` Then run the client in another terminal with: ```sh bun src/client.ts # Outputs: # Event: 43 # RPC: 43 ``` _Request a guide for deploying Bun to your preferred cloud provider on [GitHub Discussions](https://github.com/rivet-gg/actor-core/discussions)._ ## Available Regions Bun can only run in one region at the moment. See [Rivet](https://rivet.gg/docs/setup) and [Cloudflare Workers](/platforms/cloudflare-workers) for supporting multiple regions. ## Configuring Redis Configure your Redis connection like this: ```typescript export default createHandler({ redis: { port: 6379, host: "127.0.0.1", password: "my-top-secret" }, // ... }); ``` See all available options [here](https://redis.github.io/ioredis/index.html#RedisOptions). ## Next Steps >>>> cloudflare-workers.mdx --- title: Cloudflare Workers --- import SetupActor from '/snippets/setup-actor.mdx'; import SetupNextSteps from '/snippets/setup-next-steps.mdx'; import MvpWarning from '/snippets/mvp-warning.mdx'; ActorCore relies on features only available in the Workers Paid plan ([more info](https://developers.cloudflare.com/durable-objects/platform/pricing/)). To deploy hobby projects with ActorCore for free, try deploying to [Rivet](https://rivet.gg/docs/setup). ```sh npm # Create project npm create cloudflare@latest -- my-project --type=hello-world --ts --deploy=false cd my-project # Install ActorCore npm add actor-core @actor-core/cloudflare-workers ``` ```sh pnpm # Create project pnpm create cloudflare@latest my-project --type=hello-world --ts --deploy=false cd my-project # Install ActorCore pnpm add actor-core @actor-core/cloudflare-workers ``` ```sh yarn # Create project yarn create cloudflare@latest my-project --type=hello-world --ts --deploy=false cd my-project # Install ActorCore yarn add actor-core @actor-core/cloudflare-workers ``` ```sh bun # Create project bun create cloudflare@latest my-project --type=hello-world --ts --deploy=false cd my-project # Install ActorCore bun add actor-core @actor-core/cloudflare-workers ``` ActorCore can also be used with existing Cloudflare projects. Create a new file for your actor at `src/counter.ts`: Update both `src/index.ts` and `wrangler.json` to look like this: ```typescript src/index.ts import { createHandler } from "@actor-core/cloudflare-workers"; import Counter from "./counter"; const { handler, ActorHandler } = createHandler({ actors: { "counter": Counter } }); export { handler as default, ActorHandler }; ``` ```json wrangler.json { "name": "my-project", "main": "src/index.ts", "compatibility_date": "2025-01-29", "migrations": [ { "new_classes": ["ActorHandler"], "tag": "v1" } ], "durable_objects": { "bindings": [ { "class_name": "ActorHandler", "name": "ACTOR_DO" } ] }, "kv_namespaces": [ { "binding": "ACTOR_KV", "id": "FILL_ME_IN_WHEN_YOU_DEPLOY" } ] } ``` In `src/index.ts`, `handler` is used to handle HTTP requests made to the worker. `ActorHandler` is a Durable Object used to provide state for your actors. To start your development server, run: ```sh npx wrangler dev ``` Now, write a file named `client.ts` with this: ```typescript client.ts import { Client } from "actor-core/client"; import type Counter from "./src/counter.ts"; async function main() { const client = new Client("http://localhost:8787"); const counter = await client.get({ name: "counter" }); counter.on("newCount", (count: number) => console.log("Event:", count)); const out = await counter.increment(5); console.log("RPC:", out); await counter.disconnect(); } main(); ``` Run the file in a second terminal with: ```sh npx tsx client.ts # Outputs: # Event: 43 # RPC: 43 ``` 1. Create a new KV namespace with: ```sh npx wrangler kv namespace create ACTOR_KV ``` 2. After creating the KV namespace, you will receive an ID. Update your `wrangler.json` file by replacing the placeholder in the `kv_namespaces` section with this ID. It should look like this: ```json { "kv_namespaces": [ { "binding": "ACTOR_KV", "id": "your-namespace-id-here" // Replace with your actual ID } ] // ...etc... } ``` 3. Deploy your project to Cloudflare Workers by running: ```sh npx wrangler deploy ``` 4. Update your client to use the deployed endpoint. Replace the local endpoint in `client.ts` with your Cloudflare Workers URL: ```typescript const client = new Client("https://your-worker-subdomain.workers.dev"); ``` Ensure you replace `your-worker-subdomain` with the actual subdomain assigned to your worker. 5. Test your deployed application by running the client script again: ```sh npx tsx client.ts ``` ## Available Regions See available regions [here](https://developers.cloudflare.com/durable-objects/reference/data-location/#supported-locations-1). Cloudflare does not guarantee your code will run in the requested region. ## Next Steps >>>> concepts/authentication.mdx --- title: Authentication icon: fingerprint --- Authentication can be handled through the `_onBeforeConnect` lifecycle method, which acts as middleware before allowing clients to interact with your actor. ## Using `_onBeforeConnect` The `_onBeforeConnect` method is called whenever a new client attempts to connect to your actor. It takes the value of `opts.parameters` and returns the state to assign to the actor. Throwing an error in `_onBeforeConnect` will abort the connection. Here's a basic example: ```typescript import { Actor, type OnBeforeConnectOptions } from "actor-core"; interface State { // ... } interface ConnectionParams { authToken: string; } interface ConnectionState { userId: string; role: string; } export default class ExampleActor extends Actor { async _onBeforeConnect(opts: OnBeforeConnectOptions): Promise { // Verify the token with your authentication system const userData = await myValidateAuthToken(opts.parameters.authToken); if (!userData) { throw new Error('Invalid auth token'); } // Return the user data to store with the connection return { userId: userData.id, role: userData.role }; } } ``` ## Accessing Connection State After authentication, you can access the connection state in any actor method using `rpc.connection.state`: ```typescript import { Actor, type Rpc } from "actor-core"; export default class AuthenticatedActor extends Actor { exampleAdminCommand(rpc: Rpc) { // Example of validating admin access if (rpc.connection.state.role !== 'admin') throw new Error('User must be an admin'); // ... } } ``` ## Integration Examples ### With API Server Authentication ```typescript import { Actor, type Rpc, type OnBeforeConnectOptions } from "actor-core"; interface State { // ... } interface ConnectionParams { apiKey: string; } interface ConnectionState { userId: string; } export default class APIAuthenticatedActor extends Actor { async _onBeforeConnect(opts: OnBeforeConnectOptions): Promise { // Validate API key with your server const response = await fetch('https://api.yourserver.com/validate', { method: 'POST', headers: { Authorization: `Bearer ${opts.parameters.apiKey}` } }); if (!response.ok) { throw new Error('Invalid API key'); } const user = await response.json(); return { userId: user.id }; } } ``` When authentication fails, throwing an error in `_onBeforeConnect` will prevent the connection from being established, and the client will receive the error message. ### With JWT Authentication ```typescript import { Actor, type OnBeforeConnectOptions } from "actor-core"; interface State { // ... } interface ConnectionParams { jwt: string; } interface ConnectionState { userId: string; permissions: string[]; } export default class JwtAuthenticatedActor extends Actor { async _onBeforeConnect(opts: OnBeforeConnectOptions): Promise { // Verify JWT token (throws error for invalid tokens) const decoded = jwt.verify(opts.parameters.jwt, JWT_SECRET); return { userId: decoded.sub, permissions: decoded.permissions }; } } ``` >>>> concepts/connections.mdx --- title: Connections icon: network-wired --- Connections represent client connections to your actor. They provide a way to handle client authentication, manage connection-specific data, and control the connection lifecycle. ## Parameters When clients connect to an actor, they can pass connection parameters that are handled during the connection process. For example: ```typescript actor.ts import { Actor, type OnBeforeConnectOptions } from "actor-core"; interface ConnectionParams { authToken: string; } export default class Example extends Actor { _onBeforeConnect(opts: OnBeforeConnectOptions) { const authToken = opts.parameters.authToken; // ... } } ``` ```typescript client.ts const actor = client.get( { name: 'example' }, { parameters: { authToken: 'supersekure' } } ); ``` ## Connection State The data returned from `_onBeforeConnect` is used as the initial state of the connection. The connection state can be accessed in any actor method using `connection.state`. For example: ```typescript actor.ts import { Actor, type OnBeforeConnectOptions } from "actor-core"; interface ConnectionState { userId: number; role: string; } export default class Example extends Actor { // The data returned from `_onBeforeConnect` will be assigned to the connection's state _onBeforeConnect(opts: OnBeforeConnectOptions): ConnectionState { return { userId: 123, role: 'admin' }; } // ... } ``` ## Lifecycle Hooks See the documentation on the following lifecycle hooks: - [`_onBeforeConnect`](/concepts/lifecycle#on-before-connect) - [`_onConnect`](/concepts/lifecycle#on-connect) - [`_onDisconnect`](/concepts/lifecycle#on-disconnect) ## Connection List All active connections can be accessed with `this._connections`. This is stored as `Map` where the key is the connection ID. This is frequently used with `conn.send(name, event)` to send messages directly to clients. For example: ```typescript actor.ts import { Actor, type Rpc } from "actor-core"; export default class ChatRoom extends Actor { sendDirectMessage(rpc: Rpc, recipientConnectionId: number, message: string) { this._connections.get(recipientConnectionId)?.send('directMessage', { from: rpc.connection.id, message: message }); } } ``` ## Disconnecting clients Connections can be disconnected with: ```typescript actor.ts await connection.disconnect(); ``` A reason can optionally be provided like: ```typescript actor.ts await connection.disconnect('Too many requests'); ``` Waiting the `disconnect` promise is not required, but recommended in order to ensure the underlying network connections close cleanly before exiting the program. ## Offline & reconnection behavior Clients automatically attempt to reconnect (with [exponential backoff](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/retry-backoff.html)) when disconnected. Remote procedure calls made while disconnected are queued. On reconnection, event subscriptions are reestablished & queued RPCs are executed. >>>> concepts/events.mdx --- title: Events icon: tower-broadcast --- Events are used for clients to receive realtime data from actors. Events are used for actors to publish updates to clients. Clients call RPCs to communicate with the actor. ## Publishing from actors Actors can publish events to clients using `this._broadcast` and `connection.send`. ### Broadcasting events Actors can publish events to all connected clients with `this._broadcast(name, data)`. For example: ```typescript chat_room.ts import { Actor, type Rpc } from "actor-core"; export default class ChatRoom extends Actor { sendMessage(rpc: Rpc, message: string) { this._broadcast('newMessage', { message }); } } ``` ```typescript client.ts const actor = client.get({ name: 'chat_room' }); await actor.sendMessage('Hello, world!'); ``` ### Sending events to specific connections Actors can send messages to specific client connections. All connections are available on the `this._connections` array. For example: ```typescript chat_room.ts import { Actor, type Rpc } from "actor-core"; export default class ChatRoom extends Actor { sendPrivateMessage(rpc: Rpc, connectionId: number, message: string) { const conn = this._connections.find(c => c.id == connectionId); conn.send('newMessage', { message }); } } ``` ```typescript client.ts const actor = client.get({ name: 'chat_room' }); await actor.sendPrivateMessage(123, 'Hello, world!'); ``` ## Subscribing from clients Clients can subscribe to events from actors using `on` and `once`. ### `on(eventName, callback)` {/* [Documentation](https://jsr.io/@rivet-gg/actor-client/doc/~/ActorHandleRaw.prototype.on.html) */} Clients can subscribe to events that will happen repeatedly using `actor.on(name, callback)`. For example: ```typescript client.ts const actor = client.get({ name: 'chat_room' }); actor.on('newMessage', ({ message }) => { console.log('Message', message); }); ``` ```typescript chat_room.ts import { Actor, type Rpc } from "actor-core"; export default class ChatRoom extends Actor { sendMessage(rpc: Rpc, message: string) { this._broadcast('newMessage', { message }); } } ``` ### `once(eventName, callback)` {/* [Documentation](https://jsr.io/@rivet-gg/actor-client/doc/~/ActorHandleRaw.prototype.once.html) */} Clients can listen for an event only one time with `actor.once(name, callback)`. For example: ```typescript client.ts const actor = client.get({ name: 'chat_room' }); actor.once('joinRequestApproved', () => { // This will only be called once console.log('Join request accepted'); }); await actor.requestJoin(); ``` ```typescript chat_root.ts import { Actor, type Rpc } from "actor-core"; export default class ChatRoom extends Actor { requestJoin(rpc: Rpc) { // ...ask the admin to approve the request... } approveJoinRequest(rpc: Rpc, connectionId: number) { const conn = this._connections.get(connectionId); conn.send('joinRequestApproved'); } } ``` ## Connections Connections are used to communicate with clients from the actor. Read more about connections [here](/concepts/connections). >>>> concepts/external-sql.mdx --- title: External SQL Database icon: database --- While actors can serve as a complete database solution, they can also complement your existing databases. For example, you might use actors to handle frequently-changing data that needs real-time access, while keeping less frequently accessed data in your traditional database. Actors can be used with common SQL databases, such as PostgreSQL and MySQL. ## Libraries To facilitate interaction with SQL databases, you can use either ORM libraries or raw SQL drivers. Each has its own use cases and benefits: - **ORM Libraries**: Type-safe and easy way to interact with your database - [Drizzle](https://orm.drizzle.team/) - [Prisma](https://www.prisma.io/) - **Raw SQL Drivers**: Direct access to the database for more flexibility - [PostgreSQL](https://node-postgres.com/) - [MySQL](https://github.com/mysqljs/mysql) ## Hosting Providers There are several options for places to host your SQL database: - [Supabase](https://supabase.com/) - [Neon](https://neon.tech/) - [PlanetScale](https://planetscale.com/) - [AWS RDS](https://aws.amazon.com/rds/) - [Google Cloud SQL](https://cloud.google.com/sql) ## Example Here's a basic example of how you might set up a connection to a PostgreSQL database using Drizzle: ```typescript actor.ts import { Actor, type Rpc } from "actor-core"; import { Pool } from "pg"; export default class DatabaseActor extends Actor { #pool: Pool; constructor() { super(); this.#pool = new Pool({ user: "your_db_user", host: "localhost", database: "your_db_name", password: "your_db_password", port: 5432, }); } // Example RPC to fetch data from database async fetchData(rpc: Rpc) { try { const result = await this.#pool.query("SELECT * FROM your_table"); return result.rows; } catch (error) { console.error("Error fetching data:", error); throw new Error("Failed to fetch data"); } } // Example RPC to insert data into database async insertData(rpc: Rpc, data: any) { try { await this.#pool.query("INSERT INTO your_table (column1, column2) VALUES ($1, $2)", [ data.value1, data.value2, ]); return { success: true }; } catch (error) { console.error("Error inserting data:", error); throw new Error("Failed to insert data"); } } } ``` >>>> concepts/lifecycle.mdx --- title: Lifecycle icon: rotate --- ## Lifecycle Hooks Lifecycle hooks can be implemented by overriding the following methods of actors. ### `_onInitialize()` {/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onInitialize) */} Called only once when the actor is first created. This method returns the initial state of the actor (see [state documentation](/concepts/state)). If you need to call code when the actor is started (e.g. after restarting for upgrading code), see `_onStart`. ### `_onStart()` {/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onStart) */} This method is called any time the actor is started (e.g. after restarting, upgrading code, or crashing). This is called after the actor has been initialized but before any connections are accepted. Use this method to set up any resources or start any background tasks, such as `setInterval`. ### `_onStateChange(newState)` {/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onStateChange) */} Called whenever the actor's state changes. This is often used to broadcast state updates. ### `_onBeforeConnect(opts)` {/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onBeforeConnect) */} Called whenever a new client connects to the actor. Clients can pass parameters when connecting, accessible via `opts.parameters`. The returned value becomes the connection's initial state and can be accessed later via `connection.state`. 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](/concepts/authentication) for details. ### `_onConnect(connection)` {/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onConnect) */} Executed after the client has successfully connected. Messages will not be processed for this actor until this method succeeds. Errors thrown from this method will cause the client to disconnect. ### `_onDisconnect(connection)` {/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onDisconnect) */} Called when a client disconnects from the actor. Use this to clean up any connection-specific resources. ## Destroying Actors Actors can be shut down gracefully with `this._shutdown()`. Clients will be gracefully disconnected. This action is permanent and cannot be reverted. ## Full Example ```typescript import { Actor, type Rpc, OnBeforeConnectOptions } from "actor-core"; interface State { count: number; } interface ConnParams { authToken: string; } interface ConnState { userId: string; } export default class Counter extends Actor { async _onInitialize(): Promise { // Initialize with a count of 0 return { count: 0 }; } async _onStart(): Promise { // Upgrade state if needed console.log('Started'); } async _onStateChange(newState: State): Promise { // Broadcast the new count to all connected clients this._broadcast('countUpdated', { count: newState.count }); } async _onBeforeConnect(opts: OnBeforeConnectOptions): Promise { // Validate auth token with your API and determine the user const auth = await myValidateAuthToken(opts.parameters.authToken); if (!auth) { throw new Error('Invalid auth token'); } // Return connection state that will be available as connection.state return { userId: auth.userId }; } async _onConnect(connection: Connection): Promise { console.log(`User ${connection.state.userId} connected`); } async _onDisconnect(connection: Connection): Promise { console.log(`User ${connection.state.userId} disconnected`); } // Example RPC method async increment(rpc: Rpc) { this._state.count++; } } ``` >>>> concepts/logging.mdx --- title: Logging icon: list-ul --- Actors provide a built-in way to log complex data to the console. When dealing with lots of data, `console.log` often doesn't cut it. Using `this._log` allows you to log complex data using structured logging. Using the actor logging API is completely optional. ## Log levels There are 5 log levels: | Level | Call | Description | | -------- | -------------------------------------- | ---------------------------------------------------------------- | | Critical | `this._log.critical(message, ...args);` | Severe errors that prevent core functionality | | Error | `this._log.error(message, ...args);` | Errors that affect functionality but allow continued operation | | Warning | `this._log.warn(message, ...args);` | Potentially harmful situations that should be addressed | | Info | `this._log.info(message, ...args);` | General information about significant events & state changes | | Debug | `this._log.debug(message, ...args);` | Detailed debugging information, usually used only in development | ## Structured logging The built-in logging API (using `this._log`) provides structured logging to let you log key-value pairs instead of raw strings. Structures logs are readable by both machines & humans to make them easier to parse & search. Passing an object to a log will print as structured data. For example: ```typescript this._log.info('increment', { connection: rpc.connection.id, count }); // Prints: level=INFO msg=increment connection=123 count=456 ``` The first parameter in each log method is the message. The rest of the arguments are used for structured logging. ## `this._log` vs `console.log` logging `this._log` makes it easier to manage complex logs, while `console.log` can become unmaintainable at scale. Consider this example: ```typescript structured_logging.ts import { Actor, type Rpc } from "actor-core"; export default class Counter extends Actor { increment(rpc: Rpc, count: number): number { // Prints: level=INFO msg=increment connection=123 count=456 this._log.info('increment', { connection: rpc.connection.id, count }); this._state.count += count; return this._state.count; } // ...etc... } ``` ```typescript unstructured_logging.ts import { Actor, type Rpc } from "actor-core"; export default class Counter extends Actor { increment(rpc: Rpc, count: number): number { // Prints: Increment for connection 123 with count 456 console.log(`Increment for connection ${rpc.connection.id} with count ${count}`); this._state.count += count; return this._state.count; } // ...etc... } ``` If you need to search through a lot of logs, it's easier to read the structured logs. To find increments for a single connection, you can search `connection=123`. Additionally, structured logs can be parsed and queried at scale using tools like Elasticsearch, Loki, or Datadog. For example, you can parse the log `level=INFO msg=increment connection=123 count=456` in to the JSON object `{"level":"INFO","msg":"increment","connection":123,"count":456}` and then query it as you would any other structured data. >>>> concepts/manage.mdx --- title: Create & Manage Actors icon: square-plus --- This guide will walk through creating & managing actors. ## Actor tags Tags are simple key-value pairs attached to every actor. Tags serve two purposes: 1. **Actor Discovery**: Find specific actors using `client.get(tags, opts)` 2. **Organization**: Group actors for monitoring & administration purposes When choosing tags, follow these best practices: - Keep values short and concise - Use consistent naming patterns - Avoid storing sensitive information The `name` tag is required and specifies which type of actor to spawn. ### Common tag patterns The following examples show different ways tags can be used to organize actors: ```typescript Party Code // Connect to a game server using a party code const gameServer = await client.get({ name: 'game_server', partyCode: 'ABCDEF' }); ``` ```typescript Chat Room Channel // Connect to a specific chat room channel const chatRoom = await client.get({ name: 'chat_room', channel: 'random' }); ``` ```typescript User ID // Connect to a user's profile actor using their database ID const userProfile = await client.get({ name: 'user_profile', userId: '1234' }); ``` ```typescript Document Workspace // Connect to a shared document within a specific workspace const document = await client.get({ name: 'shared_document', workspaceId: 'team-alpha', documentId: 'budget-2024' }); ``` ```typescript Game Instance // Connect to a game instance with specific mode and difficulty const gameInstance = await client.get({ name: 'game_instance', game_mode: 'battle_royale', difficulty: 'hard' }); ``` ```typescript Matchmaking Queue // Connect to a regional matchmaking queue for specific skill level const matchmaker = await client.get({ name: 'matchmaker', region: 'eu_west', skill_level: 'platinum' }); ``` ```typescript App Feature // Connect to a feature flag actor for a specific app feature const featureFlag = await client.get({ name: 'feature_flag', app: 'chat', feature_id: 'reactions' }); ``` ```typescript Service Owner // Connect to an analytics actor owned by a specific service const analytics = await client.get({ name: 'analytics', owner: 'metrics_service', metric_id: 'daily_active_users' }); ``` ## Client Clients are used to connect & manage actors. To create a new client, write: ```typescript client.ts import { Client } from "actor-core/client"; const client = new Client(/* CONNECTION ADDRESS */); ``` See the setup guide for your platform on how to access `/* CONNECTION ADDRESS */`. ### `get(tags, opts)` {/* [Documentation](https://jsr.io/@rivet-gg/actor-client/doc/~/Client.prototype.get) */} Attempts to find an existing actor matching the provided tags. If no matching actor is found, it creates a new one with those tags. ```typescript client.ts const room = await client.get({ name: 'chat_room', // Get or create the actor for the channel `random` channel: 'random' }); // This actor will have the tags: { name: 'chat_room', channel: 'random' } await room.sendMessage('Hello, world!'); ``` ### `create(opts)` {/* [Documentation](https://jsr.io/@rivet-gg/actor-client/doc/~/Client.prototype.create) */} Explicitly create a new actor with the provided tags. ```typescript // Create a new document actor const doc = await client.create({ create: { tags: { name: 'my_document', docId: '123' } } }); await doc.doSomething(); ``` ### `getWithId(id, opts)` {/* [Documentation](https://jsr.io/@rivet-gg/actor-client/doc/~/Client.prototype.getWithId) */} Connects to an actor based on its ID. It's recommended to use tags instead of using actor IDs directly (e.g. see `docId` above). For example: ```typescript client.ts // Get the actor with the given ID const myActorId = '55425f42-82f8-451f-82c1-6227c83c9372'; const doc = await client.getWithId(myActorId); await doc.doSomething(); ``` ### Options #### `opts.parameters` All client methods accept a `parameters` property. This can be used to pass information about the current connection, such as authentication tokens or usernames. For example: ```typescript client.ts const actor = await client.get( { name: 'example' }, { parameters: { authToken: 'supersekure' } } ); ``` ```typescript actor.ts import { Actor, type OnBeforeConnectOptions } from "actor-core"; interface ConnectionParams { authToken: string; } export default class Example extends Actor { _onBeforeConnect(opts: OnBeforeConnectOptions) { const authToken = opts.parameters.authToken; // ... } } ``` This is available on `client.get`, `client.create`, and `client.getWithId`. Read more about parameters & connections [here](/concepts/connections). #### `opts.create.tags` When creating an actor, you can specify additional tags beyond those used for querying. If `opts.create.tags` is not provided, the query tags will be used. For example: ```typescript client.ts const actor = client.get( // Tags used to find the actor { name: 'my_document', document_id: 'budget_2024' }, { create: { // Tags assigned if a new actor is created tags: { name: 'my_document', workspace_id: 'team_alpha', document_id: 'budget_2024' } } } ); ``` This is available on `client.get` and `client.create`. #### `opts.create.region` By default, actors are created in the region with the lowest latency for the client. You can override this by specifying `create.region`: ```typescript client.ts const actor = client.get( { name: 'example' }, { create: { region: 'atl' } } ); ``` This is available on `client.get` and `client.create`. #### `opts.noCreate` To prevent creating a new actor if it does not exist, pass the `noCreate` option. For example: ```typescript client.ts // Get the actor, do not create const doc = await client.get({ name: 'my_document', docId: '123' }, { noCreate: true }); await doc.doSomething(); ``` This is available only on `client.get`. ## Shutdown actor For security reasons, actors must be shut down from within the actor itself using `this._shutdown()`. For example: ```typescript actor.ts import { Actor, type Rpc } from "actor-core"; export default class Example extends Actor { // client will call this rpc to shut down actor myShutdownRpc(rpc: Rpc) { this._shutdown(); } } ``` ```typescript client.ts const actor = client.get({ name: 'example' }); // send rpc to shut down actor await actor.myShutdownRpc(); ``` Read more about the [actor lifecycle](/concepts/lifecycle). >>>> concepts/metadata.mdx --- title: Metadata icon: tag --- Metadata provides information about the currently running actor. ## Region Region can accessed from `this._region`. `this._region` is only supported on Rivet at the moment. ## Tags Tags can be accessed from `this._tags`. For example: ```typescript chat_room.ts import { Actor, type Rpc } from "actor-core"; export default class ChatRoom extends Actor { // Method used to get the channel ID. Returns `random` for this example. getChannelId(rpc: Rpc): string { return this._tags['channel']; } } ``` ```typescript client.ts // Create an actor with `channel` set to `random` const actor = client.get({ name: 'chat_room', channel: 'random' }); ``` >>>> concepts/rpc.mdx --- title: Remote Procedure Calls icon: code --- Remote procedure calls (RPC) are how clients communicate with actors. RPCs are as simple as writing a method on the actor class and then calling it from the client. **Performance** RPCs are very lightweight. They can be called hundreds of times per second to send realtime data to the actor. ## Writing RPCs RPCs can be written as native JavaScript methods on the actor class. For example, the `multiplyByTwo` method is written as: ```typescript import { Actor, type Rpc } from "actor-core"; export class Example extends Actor { // This is an RPC multiplyByTwo(rpc: Rpc, x: number) { return x * 2; } } ``` ### Private Methods Methods starting with `_` or `#` (e.g. `_myMethod` and `#myMethod`) are private and cannot be called by clients. All provided methods start with `_` (e.g. `_broadcast`) so clients cannot call them. For example: ```typescript import { Actor, type Rpc } from "actor-core"; export default class Example extends Actor { // This is private and cannot be called by clients #calcFibonacci(n: number): number { if (n <= 1) return n; return this.#calcFibonacci(n - 1) + this.#calcFibonacci(n - 2); } // This is public and can be called by clients fetchFibonacci(rpc: Rpc, n: number): number { return this.#calcFibonacci(n); } } ``` ### Streaming Return Data RPCs have a single return value. In order to stream realtime data in response to an RPC, use [events](/concepts/events). ## Calling RPCs Calling RPCs is as simple as calling any other JavaScript function. ```typescript import type { Counter } from "./counter.ts"; const actor = await client.get({ name: "counter" }); await actor.increment(42); ``` Calling RPCs from the client are async and require an `await`, even if the actor's method is not async. ### Type Safety The actor client includes type safety out of the box. The first generic parameter in `get(...)` defines the actor class. You can safely import the actor's type with `import type` in to the client, like this: ```typescript client.ts import type { Counter } from "./counter.ts"; // ...setup... const actor = await client.get(/* ... */); await actor.increment(123); // passes await actor.increment("non-number type"); // compile error await actor.nonexistentMethod(123); // compile error ``` ```typescript actor.ts import { Actor, type Rpc } from "actor-core"; // ...setup... export class Counter extends Actor { increment(rpc: Rpc, count: number) { // ...body... } // ...etc... } ``` ## Error Handling Actors provide robust error handling out of the box for RPCs. ### User Errors `UserError` error can be used to return rich error data to the client. You can provide: - A human-readable message - A machine-readable code that's useful for matching errors in a try-catch (optional) - A metadata object for providing richer error context (optional) For example: ```typescript actor.ts // Throw a simple error with a message throw new UserError("Invalid username"); // Throw an error with a code throw new UserError("Invalid username", { code: "invalid_username", }); // Throw an error with custom metadata throw new UserError("Invalid username", { code: "invalid_username", meta: { maxLength: 32, }, }); ``` ```typescript client.ts try { await myActor.myRpc(); } catch (error) { console.log("Message", error.message); // "Invalid username" console.log("Code", error.code); // "invalid_username" console.log("Metadata", error.metadata); // { maxLength; 42 } } ``` {/* Read the documentation for `UserError` [here](https://jsr.io/@rivet-gg/actor/doc/~/UserError). */} ### 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 Data schemas are not validated by default. For production applications, use a library like [zod](https://zod.dev/) to validate input types. In the previous example, providing a non-number value to `count` could corrupt the actor's state (e.g. by passing a string instead of a number). For example, to validate the `increment` request schema: ```typescript import { Actor, type Rpc } from "actor-core"; import { z } from "zod"; // Define schemas for user requests const IncrementOptionsSchema = z.object({ count: z.number().int(), }); type IncrementOptions = z.infer; export class Counter extends Actor { increment(rpc: Rpc, opts: IncrementOptions) { // Will throw error if input is invalid const validatedOpts = IncrementOptionsSchema.parse(opts); // ...etc... } } ``` ## Authentication By default, clients can call all RPCs on an actor without restriction. Make sure to implement authentication if needed. Documentation on authentication is available [here](/concepts/authentication). >>>> concepts/scaling.mdx --- title: Scaling & Concurrency icon: maximize --- This document covers how actors are able to scale better than traditional applications & provides tips on architecting your actors. ## How actors scale Actors scale by design through these key properties: | Property | Description | | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Independent State** | Each actor manages its own private data separately from other actors, so they never conflict with each other when running at the same time (i.e. using locking mechanisms). | | **RPC- & Event-Based Communication** | Actors communicate through asynchronous [RPCs](/concepts/rpc) or [events](/concepts/events), making it easy to distribute them across different machines. | | **Location Transparency** | Unlike traditional servers, actors don't need to know which machine other actors are running on in order to communicate with each other. They can run on the same machine, across a network, and across the world. Actors handle the network routing for you under the hood. | | **Horizontal Scaling** | Actors distribute workload by splitting responsibilities into small, focused units. Since each actor handles a limited scope (like a single user, document, or chat room), the system automatically spreads load across many independent actors rather than concentrating it in a single place. | ## Tips for architecting actors for scale Here are key principles for architecting your actor system: **Single Responsibility** - Each actor should represent one specific entity or concept from your application (e.g., `User`, `Document`, `ChatRoom`). - This makes your system scale better, since actors have small scopes and do not conflict with each other. **State Management** - Each actor owns and manages only its own state - Use [RPCs](/concepts/rpc) to request data from other actors - Keep state minimal and relevant to the actor's core responsibility **Granularity Guidelines** - Too coarse: Actors handling too many responsibilities become bottlenecks - Too fine: Excessive actors create unnecessary communication overhead - Aim for actors that can operate independently with minimal cross-actor communication ### Examples **Good actor boundaries** - `User`: Manages user profile, preferences, and authentication - `Document`: Handles document content, metadata, and versioning - `ChatRoom`: Manages participants and message history **Poor actor boundaries** - `Application`: Too broad, handles everything - `DocumentWordCount`: Too granular, should be part of DocumentActor >>>> concepts/schedule.mdx --- title: Schedule icon: clock --- 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 ### `after(duration, fn, ...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. - `fn` (string): The name of the method on the actor to be executed. - `...args` (unknown[]): Additional arguments to pass to the function. ### `at(timestamp, fn, ...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. - `fn` (string): The name of the method on the actor to be executed. - `...args` (unknown[]): Additional arguments to pass to the function. ## Full Example ```typescript import { Actor, type Rpc } from "actor-core"; interface State { count: number; } export default class ScheduledCounter extends Actor { async _onInitialize(): Promise { return { count: 0, }; } _increment(x: number) { this._state.count += x; } async delayedIncrement(rpc: Rpc, x: number): Promise { // Increment the counter after 5 seconds this.after(5000, "_increment", x); } } ``` >>>> concepts/state.mdx --- title: State icon: floppy-disk --- Actor state provides the best of both worlds: it's stored in-memory and persisted automatically. This lets you work with the data without added latency while still being able to survive crashes & upgrades. **Using External SQL Databases** Actors can also be used with external SQL databases. This can be useful to integrate actors with existing applications or for storing relational data. Read more [here](/concepts/external-sql). ## State Isolation Each actor's state is completely isolated, meaning it cannot be accessed directly by other actors or clients. This allows actors to maintain a high level of security and data integrity, ensuring that state changes are controlled and predictable To interact with an actor's state, you must use [Remote Procedure Calls](/concepts/rpc) (RPC). RPCs provide a controlled way to read from and write to the state. ### Shared State If you need a shared state between multiple actors, you have two options: 1. Create an actor that holds the shared state that actors can make RPCs to 2. Use an external database, see [External SQL Databases](/concepts/external-sql) ## Native State Native state is a native JavaScript object stored in-memory on `this._state`. This makes building realtime & stateful applications as simple as working with native JavaScript objects. ### Initializing & Updating State Actors with native state require an `_initializeState` method. The object returned will be automatically persisted and assigned to `this._state`. `_initializeState` is only called once when the actor is created. See [Lifecycle](/concepts/lifecycle) for more details. To update state, assign or update `this._state`. Any modifications will be automatically persisted. For example: ```typescript actor.ts import { Actor, type Rpc } from "actor-core"; // Define the state's structure interface State { count: number; } export default class Counter extends Actor { // Automatically called the first time the actor is created _onInitialize(): State { // Initialize the state with a count of 0 return { count: 0 }; } // Define RPC call to update state increment(rpc: Rpc) { // Update state, this will automatically be persisted this._state.count += 1; } } ``` Only state stored on the `this._state` property will be persisted. All other properties of the `Counter` class are kept in-memory and not persisted. ### State Saves Actors automatically handle persisting state transparently. This happens at the end of every remote procedure call if the state has changed. In the rare occasion you need to force a state change mid-RPC, you can use `_saveState`. This should only be used if your remote procedure call makes an important state change that needs to be persisted before the RPC exits. ### Limitations State is constrained to the available memory. Only JSON-serializable types can be stored in state. In serverless runtimes that support it (Rivet, Cloudflare Workers), state is persisted under the hood in a compact, binary format. This is because JavaScript classes cannot be serialized & deserialized. >>>> experimental/concepts/edge.mdx --- title: Edge Networking icon: globe --- Actors run near your users on Rivet's global network by default. ## Region selection ### Automatic region selection By default, actors will choose the optimal region based on the client's location. Under the hood, Rivet uses [Anycast routing](https://en.wikipedia.org/wiki/Anycast) to automatically find the best location for the client to connect to without relying on slow & expensive manual pinging process. ### Manual region selection The region an actor is created in can be overridden by defining `create.region`: ```typescript client.ts const actor = client.get( { name: "example" }, { create: { region: "atl", }, } ); ``` See [Create & Manage Actors](/docs/manage) for more information. ## Available regions See available regions [here](/docs/regions). ### Fetching region list It's common to need to display a list of available regions in your application. To fetch a full list of regions, you can use the `GET https://api.rivet.gg/regions` HTTP endpoint. See API documentation [here](/docs/api/actor/regions/list). We don't recommend hard-coding the region list. This allows you to develop your application with a local development cluster. >>>> experimental/concepts/multiplayer.mdx --- title: Multiplayer icon: gamepad-modern --- TODO >>>> experimental/frameworks/react.mdx --- title: React & RSC icon: react --- This integration is unstable and not recommended for production use. If you're looking for a stable integration, please let us know by upvoting the issue on our [GitHub repository](). ## Getting Started First, add Rivet's Actor Client SDK to your project. ```bash npm npm i @rivet-gg/actor-client --save ``` ```bash yarn yarn add @rivet-gg/actor-client ``` ```bash pnpm pnpm add @rivet-gg/actor-client ``` ```bash jsr npx jsr add @rivet-gg/actor-client ``` [Generate a Actor Manager endpoint by using the `rivet` CLI and guide here](https://rivet.gg/docs/setup). Then add the generated URL to your env file. ### Create React App / Vite If you're using Create React App or Vite, you can put `ActorClientProvider` in `App.tsx`. ```tsx App.tsx import { Client } from "@rivet-gg/actor-client"; import { ActorClientProvider } from "@rivet-gg/actor-client/unstable-react"; export const App = () => { const [actorClient] = useState(() => new ActorClient({ endpoint: process.env.VITE_APP_ACTOR_ENDPOINT, })); return ( /* your other context providers / your app */ ); }; ``` ### Next.js Pages Router If you're using Next.js Pages Router you can put `ActorClientProvider` in `_app.tsx`, or in the page where you're going to use actors. ```tsx {{"title": "pages/_app.tsx"}} import { Client } from "@rivet-gg/actor-client"; import { ActorClientProvider } from "@rivet-gg/actor-client/unstable-react"; export const App = ({ Component, pageProps }) => { const [actorClient] = useState(() => new ActorClient({ endpoint: process.env.NEXT_PUBLIC_APP_ACTOR_ENDPOINT, })); return ( ); }; ``` ### Next.js App Router If you're using Next.js app router you can put `ActorClientProvider` in `layout.tsx`, or in the layout where you're going to use actors. We recommend creating a new component for your providers as shown below, because this SDK needs to be initialized on the client side with `use client` directive. ```tsx {{"title":"src/app/providers.tsx"}} 'use client'; import { Client } from "@rivet-gg/actor-client"; import { ActorClientProvider } from '@rivet-gg/actor-client/unstable-react'; export function Providers({ children, }) { const [actorClient] = useState(() => new ActorClient({ endpoint: process.env.NEXT_PUBLIC_APP_ACTOR_ENDPOINT, })); return ( {children} ) } ``` ```tsx {{"title": "src/app/layout.tsx"}} import { Providers } from './providers'; export default function Layout({ children, }) { return ( {children} ) } ``` ## Usage ### Creating an Actor `useActor` is a hook that allows you to interact with actors in your React components. Pass to it a name of your actor, [and optionally any other create parameters](https://rivet.gg/docs/manage#options). It will connect to the actor, once the component is mounted, and disconnect when the component is unmounted. The hook also provides the actor's state, error, and loading status. Actor created by `useActor` is available to that particular component tree only. If you call `useActor` somewhere else (even with the same arguments), it will create a new actor instance. To share the actor between components, you can use React's context or a state management library. It's safe to pass `actor` instance to child components as a prop, as it does not change between renders. Optionally, if you're using TypeScript, you can pass your Actor class type to the hook to get the correct types for [RPC methods](https://rivet.gg/docs/rpc). ```tsx {{"title": "JavaScript"}} import { useActor } from "@rivet-gg/actor-client/unstable-react"; export const MyComponent = () => { const [{ isLoading, error, actor }] = useActor({ name: "counter" }); // [!code focus] /* ... */ }; ``` ```tsx {{"title": "TypeScript"}} import { useActor } from "@rivet-gg/actor-client/unstable-react"; import type CounterActor from "../your-path-to-actors-directory/your-actor-file-name"; // replace with your actor file path // [!code focus] export const MyComponent = () => { const [{ isLoading, error, actor }] = useActor({ name: "counter" }); // [!code focus] /* ... */ }; ``` ### Remote Procedure Call (RPC) To call any defined Remote Procedure Call (RPC) method on the actor, you can use the `actor.methodName` syntax. Make sure to replace `methodName` with the name of the method you want to call. Be aware that those methods are asynchronous and return a promise. ```tsx {{"title": "JavaScript"}} import { useActor } from "@rivet-gg/actor-client/unstable-react"; export const MyComponent = () => { const [{ isLoading, error, actor }] = useActor({ name: "counter" }); return (
// [!code focus]
); }; ``` ```tsx {{"title": "TypeScript"}} import { useActor } from "@rivet-gg/actor-client/unstable-react"; import type CounterActor from "../your-path-to-actors-directory/your-actor-file-name"; // replace with your actor file path export const MyComponent = () => { const [{ isLoading, error, actor }] = useActor({ name: "counter" }); return (
// [!code focus]
); }; ```
### Events To subscribe to actor events changes, you can use the `actor.on` method. ```tsx {{"title": "TypeScript"}} import { useActor } from "@rivet-gg/actor-client/unstable-react"; export const MyComponent = () => { const [{ isLoading, error, actor }] = useActor({ name: "counter" }); useEffect(() => { const unsubscribe = actor.on("your event name", (event) => { // [!code focus] console.log(event); // [!code focus] }); // [!code focus] return () => { // [!code focus] unsubscribe(); // [!code focus] }; // [!code focus] }, []); }; ``` ### Troubleshooting #### JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists If you're getting this error, you need to add types for React. Normally React does not come with types, so you need to tell Deno to use the types from the different package. ```tsx your-actor.tsx // @deno-types=npm:@types/react import React from "react"; ``` ### Advanced Usage #### Server Driven UI You can use the Actor RPC methods to return React components. [This is done by using React Server Components paradigm](https://react.dev/reference/rsc/server-components). In order to render components from your Actor, you need to ensure that your Actor extends the `RscActor` class from the `@rivet-gg/actor` package. Your RPC methods can receive props like any other React component. ```tsx {{"title": "your-actor.tsx"}} import { Rpc } from "@rivet-gg/actor"; import { RscActor } from "@rivet-gg/actor/unstable-react"; // [!code focus] export default class ActorName extends RscActor { // [!code focus] /* ... */ _getMessages() { /* your code for getting messages, either from state or external source */ } messages(_rpc: Rpc, props: {limit: number}) { // [!code focus] const messages = this._getMessages(); // [!code focus] // [!code focus] return
    // [!code focus] {messages.slice(0, props.limit).map((message) => ( // [!code focus]
  • {message.text}
  • // [!code focus] ))} // [!code focus]
// [!code focus] } // [!code focus] } ``` Then, you can use the second return value of the `useActor` hook to use your RscActor's elements. Make sure to wrap those elements in `Suspense`. ```tsx {{"title": "your-component.tsx"}} import { useActor } from "@rivet-gg/actor-client/unstable-react"; import type ActorName from "../your-path-to-actor.tsx"; // replace with your actor file path export const MyComponent = () => { const [// [!code focus] state,// [!code focus] { messages: Messages }// [!code focus] ] = useActor({ name: "actor-name" }); // [!code focus] return ( // [!code focus] // [!code focus] // [!code focus] ); }; ``` ## Experimental Features Methods described below are unstable and may change in the future, do not use them in production. ### Manual RSC update If you need to re-render the component when the something happens in your actor, use `this._updateRsc()` method. This will trigger a re-render of the RSC elements in your actor. Behind the scenes, this method broadcasts an internal event `__rsc` that all RSC elements are listening to. ```tsx your-actor.tsx import { Rpc } from "@rivet-gg/actor"; import { RscActor } from "@rivet-gg/actor/unstable-react"; export default class ActorName extends RscActor { /* ... */ someFn(_rpc: Rpc) { this._updateRsc(); // [!code focus] } /* ... */ messages(_rpc: Rpc, props: {limit: number}) { /* ... */ } } ``` ### `unstable_createActorHooks` `unstable_createActorHooks` is a function that allows you to create type-safe hooks for your actors. It takes an actor name and returns a hook that can be used to interact with that actor. This is useful when you want to create a custom hook that encapsulates the logic for a specific actor and its types. ```tsx {{"title": "your-actor-hooks.tsx"}} import { unstable_createActorHooks } from "@rivet-gg/actor-client/unstable-react"; export const { useActor, useActorEventCallback, useActorRsc } = unstable_createActorHooks("counter"); ``` #### `useActor` `useActor` is a hook that allows you to interact with actors in your React components. Pass to it a name of your actor, [and optionally any other create parameters](https://rivet.gg/docs/manage#options). It will connect to the actor, once the component is mounted, and disconnect when the component is unmounted. ```tsx {{"title": "your-component.tsx"}} import { useActor } from "./your-actor-hooks"; export const MyComponent = () => { const [{ isLoading, error, actor }] = useActor(); // [!code focus] return (
// [!code focus]
); }; ``` #### `useActorEventCallback` `useActorEventCallback` is a hook that allows you to subscribe to actor events changes. It takes an event name and a callback function that will be called when the event is triggered. You do not need to wrap the callback in `useCallback` as the hook will handle that for you. ```tsx {{"title": "your-component.tsx"}} import { useActorEventCallback } from "./your-actor-hooks"; export const MyComponent = ({ actor }) => { useActorEventCallback({ actor, event: "your event name"}, (event) => { // [!code focus] console.log(event); // [!code focus] }); // [!code focus] return null; }; ``` #### `useActorRsc` `useActorRsc` is a hook that allows you to use React Server Components from your actor. It takes an actor instance and returns a function that can be used to render the RSC elements. Make sure to wrap those elements in Suspense. The second return value of the hook is the function to refetch the RSC elements. This hook won't re-render the component when the actor state changes. If you need to re-render the component when the actor state changes. Use the second return value of the hook to refetch the response. ```tsx {{"title": "your-component.tsx"}} import { useActorRsc } from "./your-actor-hooks"; export const MyComponent = ({ actor }) => { const [Messages, update] = useActorRsc({ actor, fn: 'messages' }); // [!code focus] return ( // [!code focus] // [!code focus] // [!code focus] ); }; ``` >>>> introduction.mdx --- title: ActorCore description: Stateful, Scalable, Realtime Backend Framework sidebarTitle: Introduction mode: wide --- import Bun from "/images/platforms/bun.svg";

ActorCore

Stateful, Scalable, Realtime Backend Framework

The modern way to build multiplayer, realtime, or AI agent backends.

Supports Rivet, Cloudflare Workers, Bun, and Node.js.

```typescript import { Actor, type Rpc } from "actor-core"; export interface State { messages: { username: string; message: string }[]; } export default class ChatRoom extends Actor { // initialize this._state _onInitialize() { return { messages: [] }; } // receive an remote procedure call from the client sendMessage(rpc: Rpc, username: string, message: string) { // save message to persistent storage this._state.messages.push({ username, message }); // broadcast message to all clients this._broadcast("newMessage", username, message); } } ```
Fast in-memory access with built-in durability — no external databases or caches needed. Real-time state updates with ultra-low latency, powered by co-locating compute and data. Integrated support for state, RPC, events, scheduling, and multiplayer — no extra boilerplate code needed. Effortless scaling, scale-to-zero, and easy deployments on any serverless runtime.
Features

Everything you need to build realtime, stateful backends

ActorCore provides a solid foundation with the features you'd expect for modern apps.

Feature ActorCore Durable Objects Socket.io Redis AWS Lambda
In-Memory State
Persisted State
RPC
Events (Pub/Sub)
Scheduling
Edge Computing ¹
No Vendor Lock

= requires significant boilerplate code or external service

¹ = on supported platforms

## Quickstart ```sh npm npm add actor-core ``` ```sh pnpm pnpm add actor-core ``` ```sh yarn yarn add actor-core ``` ```sh bun bun add actor-core ``` ```typescript Actor (TypeScript) import { Actor, type Rpc } from "actor-core"; export interface State { messages: { username: string; message: string }[]; } export default class ChatRoom extends Actor { // initialize this._state _onInitialize() { return { messages: [] }; } // receive an remote procedure call from the client sendMessage(rpc: Rpc, username: string, message: string) { // save message to persistent storage this._state.messages.push({ username, message }); // broadcast message to all clients this._broadcast("newMessage", username, message); } } ``` ```javascript Actor (JavaScript) import { Actor } from "actor-core"; export default class ChatRoom extends Actor { // initialize this._state _onInitialize() { return { messages: [] }; } // receive an remote procedure call from the client sendMessage(rpc, username, message) { // save message to persistent storage this._state.messages.push({ username, message }); // broadcast message to all clients this._broadcast("newMessage", username, message); } } ``` ```typescript Browser (TypeScript) import { Client } from "actor-core/client"; import type ChatRoom from "../src/chat-room.ts"; const client = new Client(/* manager endpoint */); // connect to chat room const chatRoom = await client.get({ name: "chat" }); // listen for new messages chatRoom.on("newMessage", (username: string, message: string) => console.log(`Message from ${username}: ${message}`), ); // send message to room await chatRoom.sendMessage("william", "All the world's a stage."); ``` ```javascript Browser (JavaScript) import { Client } from "actor-core/client"; const client = new Client(/* manager endpoint */); // connect to chat room const chatRoom = await client.get({ name: "chat" }); // listen for new messages chatRoom.on("newMessage", (username, message) => console.log(`Message from ${username}: ${message}`), ); // send message to room await chatRoom.sendMessage("william", "All the world's a stage."); ``` Deploy to your platform of choice: - [Rivet](https://rivet.gg/docs/setup) - [Cloudflare Workers](/platforms/cloudflare-workers) - [Bun](/platforms/bun) - [Node.js](/platforms/nodejs) ## Community & Support {" "} >>>> mvp-warning.mdx ActorCore is still pre-v1.0. Please help us by report bugs on [GitHub Issues](https://github.com/rivet-gg/actor-core/issues)! >>>> nodejs.mdx --- title: Node.js --- import SetupActor from '/snippets/setup-actor.mdx'; import SetupNextSteps from '/snippets/setup-next-steps.mdx'; import MvpWarning from '/snippets/mvp-warning.mdx'; Install Node.js [here](https://nodejs.org/en/download). Install Redis [here](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/). By default, ActorCore will connect to `redis://127.0.0.1:6379`. See [instructions on configuring Redis](#configuring-redis). ```sh npm # Create project npm init -y # Install ActorCore npm add actor-core @actor-core/nodejs ``` ```sh pnpm # Create project pnpm init -y # Install ActorCore pnpm add actor-core @actor-core/nodejs ``` ```sh yarn # Create project yarn init -y # Install ActorCore yarn add actor-core @actor-core/nodejs ``` ```sh bun # Create project bun init --yes # Install ActorCore bun add actor-core @actor-core/nodejs ``` ActorCore can also be used with existing Node.js projects. Create a new file for your actor at `src/counter.ts`: Create `src/index.ts` to start your ActorCore server with: ```typescript src/index.ts import { serve } from "@actor-core/nodejs"; import counter from "./counter.ts"; serve({ actors: { counter } }); ``` Create a client to connect to your actor in `src/client.ts`: ```typescript src/client.ts import { Client } from "actor-core/client"; import type Counter from "./counter.ts"; async function main() { const client = new Client("http://localhost:8787"); const counter = await client.get({ name: "counter" }); counter.on("newCount", (count: number) => console.log("Event:", count)); const out = await counter.increment(5); console.log("RPC:", out); await counter.disconnect(); } main(); ``` Start your development server with: ```sh npx tsx src/index.ts ``` Then run the client in another terminal with: ```sh npx tsx src/client.ts # Outputs: # Event: 43 # RPC: 43 ``` _Request a guide for deploying Node.js to your preferred cloud provider on [GitHub Discussions](https://github.com/rivet-gg/actor-core/discussions)._ ## Available Regions Node.js can only run in one region at the moment. See [Rivet](https://rivet.gg/docs/setup) and [Cloudflare Workers](/platforms/cloudflare-workers) for supporting multiple regions. ## Configuring Redis Configure your Redis connection like this: ```typescript serve({ redis: { port: 6379, host: "127.0.0.1", password: "my-top-secret" }, // ... }); ``` See all available options [here](https://redis.github.io/ioredis/index.html#RedisOptions). ## Next Steps >>>> rivet.mdx --- title: Rivet url: https://rivet.gg/docs/setup --- import SetupActor from '/snippets/setup-actor.mdx'; import SetupNextSteps from '/snippets/setup-next-steps.mdx'; import MvpWarning from '/snippets/mvp-warning.mdx'; Ensure you've installed the [Rivet CLI](https://rivet.gg/docs/setup). ```sh npm # Create project rivet init cd my-project # Install ActorCore npm add actor-core @actor-core/rivet ``` ```sh pnpm # Create project rivet init cd my-project # Install ActorCore pnpm add actor-core @actor-core/cloudflare-workers ``` ```sh yarn # Create project rivet init cd my-project # Install ActorCore yarn add actor-core @actor-core/cloudflare-workers ``` ```sh bun # Create project rivet init cd my-project # Install ActorCore bun add actor-core @actor-core/cloudflare-workers ``` ActorCore can also be used with existing Rivet projects. Create a new file for your actor at `src/counter.ts`: Update both `src/manager.ts` and `rivet.json` to look like this: ```typescript src/manager.ts import { createManagerHandler } from "@actor-core/rivet"; export default createManagerHandler(); ``` ```json rivet.json { "builds": { "manager": { "script": "src/manager.ts", "access": "private" }, "counter": { "script": "src/counter.ts", "access": "public" } }, "unstable": { "manager": { "enable": false } } } ``` The `manager` actor is a special type of actor used for exposing a public API to create & manage actors easily. 1. Deploy your project with: ```sh rivet deploy ``` 1. Navigate to the [Rivet Dashboard](https://hub.rivet.gg) (or run `rivet view` for a shortcut). Navigate to _Your Team > Your Project > Production > Tokens_. Under _Service token_, click _Generate_. ![Service Token](/media/platforms/rivet/service-token.png) 2. Run this command to create the manager actor. Replace `your-service-token-here` with the token above. (This command only needs to be ran once.) ```sh # Only works on macOS & Linux rivet actor create -r atl -t name=manager --env-var RIVET_SERVICE_TOKEN=your-service-token-here -p name=http,protocol=https,guard --durable | grep -o 'https://[^"]*' ``` This will output a URL that looks something like: `https://xxxx.actor.xxxx.rivet.run:443` Now, write a file named `client.ts`. Make sure to replace `your-manager-url-here` with the URL from above. ```typescript client.ts import { Client } from "actor-core/client"; import type Counter from "../src/counter.ts"; async function main() { const client = new Client("your-manager-url-here"); const counter = await client.get({ name: "counter" }); counter.on("newCount", (count: number) => console.log("Event:", count)); const out = await counter.increment(5); console.log("RPC:", out); await counter.disconnect(); } main(); ``` Run the file with: ```sh npx tsx client.ts # Outputs: # Event: 43 # RPC: 43 ``` Visit the [Rivet Dashboard](https://hub.rivet.gg) (or run `rivet view` as a shortcut) to view your project. You'll see the actor you created here: ![Dashboard](/media/platforms/rivet/dash.png) ## Available Regions See available regions [here](https://rivet.gg/docs/regions). ## Next Steps >>>> setup-actor.mdx ```typescript src/counter.ts import { Actor, type Rpc } from "actor-core"; export interface State { count: number; } export default class Counter extends Actor { _onInitialize() { return { count: 0 }; } increment(rpc: Rpc, x: number) { this._state.count += x; this._broadcast("newCount", this._state.count); return this._state.count; } } ``` >>>> setup-next-steps.mdx