diff --git a/CLAUDE.md b/CLAUDE.md index 2eeda6c49..a2da8668b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,7 @@ Always include a README.md for new packages. The `README.md` should always follow this structure: ```md - # RivetKit {subname, e.g. library: RivetKit Workers, driver and platform: RivetKit Redis Adapter, RivetKit Cloudflare Workers Adapter} + # RivetKit {subname, e.g. library: RivetKit Actors, driver and platform: RivetKit Redis Adapter, RivetKit Cloudflare Workers Adapter} _Lightweight Libraries for Backends_ @@ -30,16 +30,16 @@ Always include a README.md for new packages. The `README.md` should always follo ## Common Terminology -- **Worker**: A stateful, long-lived entity that processes messages and maintains state -- **Manager**: Component responsible for creating, routing, and managing worker instances -- **Remote Procedure Call (RPC)**: Method for an worker to expose callable functions to clients -- **Event**: Asynchronous message sent from an worker to connected clients +- **Actor**: A stateful, long-lived entity that processes messages and maintains state +- **Manager**: Component responsible for creating, routing, and managing actor instances +- **Remote Procedure Call (RPC)**: Method for an actor to expose callable functions to clients +- **Event**: Asynchronous message sent from an actor to connected clients - **Alarm**: Scheduled callback that triggers at a specific time ### Coordinated Topology Terminology -- **Peer**: Individual worker instance in a coordinated network -- **Node**: Physical or logical host running one or more worker peers +- **Peer**: Individual actor instance in a coordinated network +- **Node**: Physical or logical host running one or more actor peers ## Build Commands @@ -56,19 +56,19 @@ Run these commands from the root of the project. They depend on Turborepo, so yo ### Topologies -rivetkit supports three topologies that define how workers communicate and scale: +rivetkit supports three topologies that define how actors communicate and scale: -- **Singleton:** A single instance of an worker running in one location -- **Partition:** Multiple instances of an worker type partitioned by ID, useful for horizontal scaling -- **Coordinate:** Workers connected in a peer-to-peer network, sharing state between instances +- **Singleton:** A single instance of an actor running in one location +- **Partition:** Multiple instances of an actor type partitioned by ID, useful for horizontal scaling +- **Coordinate:** Actors connected in a peer-to-peer network, sharing state between instances ### Driver Interfaces Driver interfaces define the contract between rivetkit and various backends: -- **WorkerDriver:** Manages worker state, lifecycle, and persistence -- **ManagerDriver:** Manages worker discovery, routing, and scaling -- **CoordinateDriver:** Handles peer-to-peer communication between worker instances +- **ActorDriver:** Manages actor state, lifecycle, and persistence +- **ManagerDriver:** Manages actor discovery, routing, and scaling +- **CoordinateDriver:** Handles peer-to-peer communication between actor instances - Only applicable in coordinate topologies ### Driver Implementations @@ -110,7 +110,7 @@ This ensures imports resolve correctly across different build environments and p - UPPER_CASE for constants - Use `#` prefix for private class members (not `private` keyword) - **Error Handling:** - - Extend from `WorkerError` base class (packages/core/src/worker/errors.ts) + - Extend from `ActorError` base class (packages/core/src/actor/errors.ts) - Use `UserError` for client-safe errors - Use `InternalError` for internal errors - Don't try to fix type issues by casting to unknown or any. If you need to do this, then stop and ask me to manually intervene. @@ -119,7 +119,7 @@ This ensures imports resolve correctly across different build environments and p - Do not store `logger()` as a variable, always call it using `logger().info("...")` - Use structured logging where it makes sense, for example: `logger().info("foo", { bar: 5, baz: 10 })` - Supported logging methods are: trace, debug, info, warn, error, critical -- Instead of returning errors as raw HTTP responses with c.json, use or write an error in packages/rivetkit/src/worker/errors.ts and throw that instead. The middleware will automatically serialize the response for you. +- Instead of returning errors as raw HTTP responses with c.json, use or write an error in packages/rivetkit/src/actor/errors.ts and throw that instead. The middleware will automatically serialize the response for you. ## Project Structure @@ -140,7 +140,7 @@ This ensures imports resolve correctly across different build environments and p ## Test Guidelines -- Do not check if errors are an instanceOf WorkerError in tests. Many error types do not have the same prototype chain when sent over the network, but still have the same properties so you can safely cast with `as`. +- Do not check if errors are an instanceOf ActorError in tests. Many error types do not have the same prototype chain when sent over the network, but still have the same properties so you can safely cast with `as`. ## Examples diff --git a/docs/clients/javascript.mdx b/docs/clients/javascript.mdx index 7e9901d42..3d42dff86 100644 --- a/docs/clients/javascript.mdx +++ b/docs/clients/javascript.mdx @@ -4,12 +4,12 @@ icon: node-js --- import MvpWarning from "/snippets/mvp-warning.mdx"; -import StepDefineWorker from "/snippets/step-define-worker.mdx"; +import StepDefineActor from "/snippets/step-define-actor.mdx"; import StepRunStudio from "/snippets/step-run-studio.mdx"; import StepDeploy from "/snippets/step-deploy.mdx"; import SetupNextSteps from "/snippets/setup-next-steps.mdx"; -The RivetKit JavaScript client allows you to connect to and interact with workers from browser and Node.js applications. +The RivetKit JavaScript client allows you to connect to and interact with actors from browser and Node.js applications. @@ -71,20 +71,20 @@ The RivetKit JavaScript client allows you to connect to and interact with worker - + - Create a file `src/client.ts` in your project to connect to your worker: + Create a file `src/client.ts` in your project to connect to your actor: ```typescript src/client.ts import { createClient } from "rivetkit/client"; - import type { App } from "../workers/app"; + import type { App } from "../actors/app"; async function main() { // Replace with your endpoint URL after deployment const client = createClient("http://localhost:6420"); - // Get or create a worker instance + // Get or create a actor instance const counter = await client.counter.get(); // Subscribe to events @@ -139,5 +139,5 @@ The RivetKit JavaScript client allows you to connect to and interact with worker ## Next Steps -See the [Interacting with Workers](/concepts/interacting-with-workers) documentation for information on how to use the client. +See the [Interacting with Actors](/concepts/interacting-with-actors) documentation for information on how to use the client. diff --git a/docs/clients/python.mdx b/docs/clients/python.mdx index 3cf4f301f..4fd8e009b 100644 --- a/docs/clients/python.mdx +++ b/docs/clients/python.mdx @@ -4,12 +4,12 @@ icon: python --- import MvpWarning from "/snippets/mvp-warning.mdx"; -import StepDefineWorker from "/snippets/step-define-worker.mdx"; +import StepDefineActor from "/snippets/step-define-actor.mdx"; import StepRunStudio from "/snippets/step-run-studio.mdx"; import StepDeploy from "/snippets/step-deploy.mdx"; import SetupNextSteps from "/snippets/setup-next-steps.mdx"; -The RivetKit Python client provides a way to connect to and interact with workers from Python applications. +The RivetKit Python client provides a way to connect to and interact with actors from Python applications. @@ -39,7 +39,7 @@ The RivetKit Python client provides a way to connect to and interact with worker ``` - + Create a new file `main.py`: @@ -53,7 +53,7 @@ The RivetKit Python client provides a way to connect to and interact with worker # Replace with your endpoint URL after deployment client = AsyncClient("http://localhost:6420") - # Get or create a worker instance + # Get or create a actor instance counter = await client.get("counter") # Subscribe to events using callback @@ -82,7 +82,7 @@ The RivetKit Python client provides a way to connect to and interact with worker # Replace with your endpoint URL after deployment client = Client("http://localhost:6420") - # Get or create a worker instance + # Get or create a actor instance counter = client.get("counter") # Subscribe to events using callback diff --git a/docs/concepts/cors.mdx b/docs/concepts/cors.mdx index 546ae19c9..118e96115 100644 --- a/docs/concepts/cors.mdx +++ b/docs/concepts/cors.mdx @@ -8,8 +8,8 @@ Cross-Origin Resource Sharing (CORS) is a security mechanism that allows a web a You'll need to configure CORS when: -- **Local Development**: You're developing locally and your client runs on a different port than your worker service -- **Different Domain**: Your frontend application is hosted on a different domain than your worker service +- **Local Development**: You're developing locally and your client runs on a different port than your actor service +- **Different Domain**: Your frontend application is hosted on a different domain than your actor service ## Example @@ -18,7 +18,7 @@ import { setup } from "rivetkit"; import counter from "./counter"; const registry = setup({ - workers: { counter }, + actors: { counter }, // Change this to match your frontend's origin cors: { origin: "https://yourdomain.com" } }); diff --git a/docs/concepts/edge.mdx b/docs/concepts/edge.mdx index 52d52b7ce..b6879433f 100644 --- a/docs/concepts/edge.mdx +++ b/docs/concepts/edge.mdx @@ -1,6 +1,6 @@ --- title: Edge Networking -description: Workers run near your users on your provider's global network (if supported). +description: Actors run near your users on your provider's global network (if supported). icon: globe --- @@ -8,7 +8,7 @@ icon: globe ### Automatic region selection -By default, workers will choose the optimal region based on the client's location. +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 @@ -17,7 +17,7 @@ By default, workers will choose the optimal region based on the client's locatio ### Manual region selection -The region a worker is created in can be overridden using region options: +The region a actor is created in can be overridden using region options: ```typescript client.ts import { createClient } from "rivetkit/client"; @@ -25,8 +25,8 @@ import type { App } from "./src/index"; const client = createClient("http://localhost:6420"); -// Create worker in a specific region -const worker = await client.example.get({ +// Create actor in a specific region +const actor = await client.example.get({ options: { create: { region: "atl" @@ -35,7 +35,7 @@ const worker = await client.example.get({ }); ``` -See [Create & Manage Workers](/docs/manage) for more information. +See [Create & Manage Actors](/docs/manage) for more information. ## Available regions @@ -45,6 +45,6 @@ See available regions [here](/docs/regions). 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/worker/regions/list). +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. diff --git a/docs/concepts/external-sql.mdx b/docs/concepts/external-sql.mdx index d0204b6cf..d30e88848 100644 --- a/docs/concepts/external-sql.mdx +++ b/docs/concepts/external-sql.mdx @@ -3,9 +3,9 @@ title: External SQL Database icon: database --- -While workers can serve as a complete database solution, they can also complement your existing databases. For example, you might use workers to handle frequently-changing data that needs real-time access, while keeping less frequently accessed data in your traditional 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. -Workers can be used with common SQL databases, such as PostgreSQL and MySQL. +Actors can be used with common SQL databases, such as PostgreSQL and MySQL. ## Libraries @@ -35,8 +35,8 @@ There are several options for places to host your SQL database: Here's a basic example of how you might set up a connection to a PostgreSQL database using the `pg` library: -```typescript worker.ts -import { worker } from "rivetkit"; +```typescript actor.ts +import { actor } from "rivetkit"; import { Pool } from "pg"; // Create a database connection pool @@ -48,8 +48,8 @@ const pool = new Pool({ port: 5432, }); -// Create the worker -const databaseWorker = worker({ +// Create the actor +const databaseActor = actor({ state: { // Local state if needed lastQueryTime: 0 @@ -57,7 +57,7 @@ const databaseWorker = worker({ // Initialize any resources onStart: (c) => { - console.log("Database worker started"); + console.log("Database actor started"); }, // Clean up resources if needed @@ -98,15 +98,15 @@ const databaseWorker = worker({ } }); -export default databaseWorker; +export default databaseActor; ``` ## With Drizzle ORM Here's an example using Drizzle ORM for more type-safe database operations: -```typescript worker.ts -import { worker } from "rivetkit"; +```typescript actor.ts +import { actor } from "rivetkit"; import { drizzle } from "drizzle-orm/node-postgres"; import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core"; import { Pool } from "pg"; @@ -127,10 +127,10 @@ const pool = new Pool({ // Initialize Drizzle with the pool const db = drizzle(pool); -// Create the worker -const userWorker = worker({ +// Create the actor +const userActor = actor({ state: { - // Worker state (frequently accessed data can be cached here) + // Actor state (frequently accessed data can be cached here) userCache: {} }, @@ -169,5 +169,5 @@ const userWorker = worker({ } }); -export default userWorker; +export default userActor; ``` diff --git a/docs/concepts/logging.mdx b/docs/concepts/logging.mdx index 92d5be2a1..ce10feb15 100644 --- a/docs/concepts/logging.mdx +++ b/docs/concepts/logging.mdx @@ -3,11 +3,11 @@ title: Logging icon: list-ul --- -Workers provide a built-in way to log complex data to the console. +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 the context's log object (`c.log`) allows you to log complex data using structured logging. -Using the worker logging API is completely optional. +Using the actor logging API is completely optional. ## Log levels @@ -46,9 +46,9 @@ Consider this example: ```typescript structured_logging.ts -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const counter = worker({ +const counter = actor({ state: { count: 0 }, actions: { @@ -64,9 +64,9 @@ const counter = worker({ ``` ```typescript unstructured_logging.ts -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const counter = worker({ +const counter = actor({ state: { count: 0 }, actions: { @@ -92,13 +92,13 @@ Additionally, structured logs can be parsed and queried at scale using tools lik The logger is available in all lifecycle hooks: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const loggingExample = worker({ +const loggingExample = actor({ state: { events: [] }, onStart: (c) => { - c.log.info('worker_started', { timestamp: Date.now() }); + c.log.info('actor_started', { timestamp: Date.now() }); }, onBeforeConnect: (c, { params }) => { @@ -137,7 +137,7 @@ const loggingExample = worker({ }, actions: { - // Worker actions... + // Actor actions... } }); ``` diff --git a/docs/concepts/overview.mdx b/docs/concepts/overview.mdx index b15637a1b..82101aefb 100644 --- a/docs/concepts/overview.mdx +++ b/docs/concepts/overview.mdx @@ -3,26 +3,26 @@ title: Introduction icon: square-info --- -import CreateWorkerCli from "/snippets/create-worker-cli.mdx"; +import CreateActorCli from "/snippets/create-actor-cli.mdx"; -Workers combine compute and storage into unified entities for simplified architecture. Workers seamlessly integrate with your existing infrastructure or can serve as a complete standalone solution. +Actors combine compute and storage into unified entities for simplified architecture. Actors seamlessly integrate with your existing infrastructure or can serve as a complete standalone solution. ## Quickstart Run this to get started: - + ## Code Example -Here's a complete chat room worker that maintains state and handles messages. We'll explore each component in depth throughout this document: +Here's a complete chat room actor that maintains state and handles messages. We'll explore each component in depth throughout this document: ```typescript chat_room.ts -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -// Define a chat room worker -const chatRoom = worker({ - // Initialize state when the worker is first created +// Define a chat room actor +const chatRoom = actor({ + // Initialize state when the actor is first created createState: () => ({ messages: [] }), @@ -50,7 +50,7 @@ export default chatRoom; ## Using the App -To start using your worker, create an app and serve it: +To start using your actor, create an app and serve it: ```typescript app.ts import { setup, serve } from "rivetkit"; @@ -58,7 +58,7 @@ import chatRoom from "./chat_room"; // Create the application const registry = setup({ - workers: { chatRoom } + actors: { chatRoom } }); // Start serving on default port @@ -68,17 +68,17 @@ serve(registry); export type Registry = typeof registry; ``` -## Key Worker Components +## Key Actor Components ### State -Workers maintain state that's stored in memory and automatically persisted. State is defined either as a constant or via a `createState` function: +Actors maintain state that's stored in memory and automatically persisted. State is defined either as a constant or via a `createState` function: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; // Method 1: State constant -const counter1 = worker({ +const counter1 = actor({ state: { count: 0 }, actions: { // ... @@ -86,7 +86,7 @@ const counter1 = worker({ }); // Method 2: CreateState function -const counter2 = worker({ +const counter2 = actor({ createState: () => ({ count: 0 }), actions: { // ... @@ -97,9 +97,9 @@ const counter2 = worker({ Update state by modifying `c.state` in your actions: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const counter = worker({ +const counter = actor({ state: { count: 0 }, actions: { // Example of state update in an action @@ -117,12 +117,12 @@ Learn more about [state management](/concepts/state). ### Actions -Actions are functions defined in your worker configuration that clients & other workers can call: +Actions are functions defined in your actor configuration that clients & other actors can call: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const mathUtils = worker({ +const mathUtils = actor({ state: {}, actions: { multiplyByTwo: (c, x) => { @@ -138,12 +138,12 @@ Learn more about [actions](/concepts/actions). ### Events -Workers can broadcast events to connected clients: +Actors can broadcast events to connected clients: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const inventory = worker({ +const inventory = actor({ createState: () => ({ items: [] }), @@ -163,9 +163,9 @@ const inventory = worker({ You can also send events to specific clients: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const messageService = worker({ +const messageService = actor({ state: {}, actions: { sendPrivateMessage: (c, userId, text) => { @@ -181,12 +181,12 @@ const messageService = worker({ Learn more about [events](/concepts/events). -## Worker Tags +## Actor Tags -Tags are key-value pairs attached to workers that serve two purposes: +Tags are key-value pairs attached to actors that serve two purposes: -1. **Worker Discovery**: Find specific workers using `client.get(tags)` -2. **Organization**: Group related workers for management purposes +1. **Actor Discovery**: Find specific actors using `client.get(tags)` +2. **Organization**: Group related actors for management purposes For example, you can query chat rooms by tag like: @@ -215,16 +215,16 @@ const document = await client.document.get({ }); ``` -## Worker Lifecycle +## Actor Lifecycle -Workers are created automatically when needed and persist until explicitly shutdown. +Actors are created automatically when needed and persist until explicitly shutdown. -To shut down a worker, use `c.shutdown()` from within an action: +To shut down a actor, use `c.shutdown()` from within an action: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ createState: () => ({ messages: [] }), @@ -233,26 +233,26 @@ const chatRoom = worker({ // Do any cleanup needed c.broadcast("roomClosed"); - // Shutdown the worker + // Shutdown the actor c.shutdown(); } } }); ``` -Learn more about the [worker lifecycle](/concepts/lifecycle). +Learn more about the [actor lifecycle](/concepts/lifecycle). ## Next Steps - - Learn how to connect to workers from clients + + Learn how to connect to actors from clients - Deep dive into worker state management + Deep dive into actor state management - Learn more about worker actions + Learn more about actor actions Learn more about realtime events diff --git a/docs/concepts/scaling.mdx b/docs/concepts/scaling.mdx index 13c890c07..9922803d1 100644 --- a/docs/concepts/scaling.mdx +++ b/docs/concepts/scaling.mdx @@ -3,49 +3,49 @@ title: Scaling & Concurrency icon: maximize --- -This document covers how workers are able to scale better than traditional applications & provides tips on architecting your workers. +This document covers how actors are able to scale better than traditional applications & provides tips on architecting your actors. -## How workers scale +## How actors scale -Workers scale by design through these key properties: +Actors scale by design through these key properties: | Property | Description | | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Independent State** | Each worker manages its own private data separately from other workers, so they never conflict with each other when running at the same time (i.e. using locking mechanisms). | -| **Action- & Event-Based Communication** | Workers communicate through asynchronous [actions](/concepts/actions) or [events](/concepts/events), making it easy to distribute them across different machines. | -| **Location Transparency** | Unlike traditional servers, workers don't need to know which machine other workers are running on in order to communicate with each other. They can run on the same machine, across a network, and across the world. Workers handle the network routing for you under the hood. | -| **Horizontal Scaling** | Workers distribute workload by splitting responsibilities into small, focused units. Since each worker handles a limited scope (like a single user, document, or chat room), the system automatically spreads load across many independent workers rather than concentrating it in a single place. | +| **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). | +| **Action- & Event-Based Communication** | Actors communicate through asynchronous [actions](/concepts/actions) 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 workers for scale +## Tips for architecting actors for scale -Here are key principles for architecting your worker system: +Here are key principles for architecting your actor system: **Single Responsibility** -- Each worker should represent one specific entity or concept from your application (e.g., `User`, `Document`, `ChatRoom`). -- This makes your system scale better, since workers have small scopes and do not conflict with each other. +- 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 worker owns and manages only its own state -- Use [actions](/concepts/actions) to request data from other workers -- Keep state minimal and relevant to the worker's core responsibility +- Each actor owns and manages only its own state +- Use [actions](/concepts/actions) to request data from other actors +- Keep state minimal and relevant to the actor's core responsibility **Granularity Guidelines** -- Too coarse: Workers handling too many responsibilities become bottlenecks -- Too fine: Excessive workers create unnecessary communication overhead -- Aim for workers that can operate independently with minimal cross-worker communication +- 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 worker boundaries** +**Good actor boundaries** - `User`: Manages user profile, preferences, and authentication - `Document`: Handles document content, metadata, and versioning - `ChatRoom`: Manages participants and message history -**Poor worker boundaries** +**Poor actor boundaries** - `Application`: Too broad, handles everything -- `DocumentWordCount`: Too granular, should be part of DocumentWorker +- `DocumentWordCount`: Too granular, should be part of DocumentActor diff --git a/docs/concepts/testing.mdx b/docs/concepts/testing.mdx index 7b0a37ee5..9b8730952 100644 --- a/docs/concepts/testing.mdx +++ b/docs/concepts/testing.mdx @@ -3,7 +3,7 @@ title: Testing icon: vial-circle-check --- -RivetKit provides a straightforward testing framework to build reliable and maintainable applications. This guide covers how to write effective tests for your worker-based services. +RivetKit provides a straightforward testing framework to build reliable and maintainable applications. This guide covers how to write effective tests for your actor-based services. ## Setup @@ -19,33 +19,33 @@ npm test ## Basic Testing Setup -RivetKit includes a test helper called `setupTest` that configures a test environment with in-memory drivers for your workers. This allows for fast, isolated tests without external dependencies. +RivetKit includes a test helper called `setupTest` that configures a test environment with in-memory drivers for your actors. This allows for fast, isolated tests without external dependencies. -```ts tests/my-worker.test.ts +```ts tests/my-actor.test.ts import { test, expect } from "vitest"; import { setupTest } from "rivetkit/test"; import { app } from "../src/index"; -test("my worker test", async (test) => { +test("my actor test", async (test) => { const { client } = await setupTest(test, app); - // Now you can interact with your worker through the client - const myWorker = await client.myWorker.get(); + // Now you can interact with your actor through the client + const myActor = await client.myActor.get(); - // Test your worker's functionality - await myWorker.someAction(); + // Test your actor's functionality + await myActor.someAction(); // Make assertions - const result = await myWorker.getState(); + const result = await myActor.getState(); expect(result).toEqual("updated"); }); ``` ```ts src/index.ts -import { worker, setup } from "rivetkit"; +import { actor, setup } from "rivetkit"; -const myWorker = worker({ +const myActor = actor({ state: { value: "initial" }, actions: { someAction: (c) => { @@ -59,16 +59,16 @@ const myWorker = worker({ }); export const registry = setup({ - workers: { myWorker } + actors: { myActor } }); export type Registry = typeof registry; ``` -## Testing Worker State +## Testing Actor State -The test framework uses in-memory drivers that persist state within each test, allowing you to verify that your worker correctly maintains state between operations. +The test framework uses in-memory drivers that persist state within each test, allowing you to verify that your actor correctly maintains state between operations. ```ts tests/counter.test.ts @@ -76,7 +76,7 @@ import { test, expect } from "vitest"; import { setupTest } from "rivetkit/test"; import { app } from "../src/index"; -test("worker should persist state", async (test) => { +test("actor should persist state", async (test) => { const { client } = await setupTest(test, app); const counter = await client.counter.get(); @@ -94,7 +94,7 @@ test("worker should persist state", async (test) => { ```ts src/index.ts import { setup } from "rivetkit"; -const counter = worker({ +const counter = actor({ state: { count: 0 }, actions: { increment: (c) => { @@ -109,7 +109,7 @@ const counter = worker({ }); export const registry = setup({ - workers: { counter } + actors: { counter } }); export type Registry = typeof registry; @@ -118,7 +118,7 @@ export type Registry = typeof registry; ## Testing Events -For workers that emit events, you can verify events are correctly triggered by subscribing to them: +For actors that emit events, you can verify events are correctly triggered by subscribing to them: ```ts tests/chat-room.test.ts @@ -126,7 +126,7 @@ import { test, expect, vi } from "vitest"; import { setupTest } from "rivetkit/test"; import { app } from "../src/index"; -test("worker should emit events", async (test) => { +test("actor should emit events", async (test) => { const { client } = await setupTest(test, app); const chatRoom = await client.chatRoom.get(); @@ -145,9 +145,9 @@ test("worker should emit events", async (test) => { ``` ```ts src/index.ts -import { worker, setup } from "rivetkit"; +import { actor, setup } from "rivetkit"; -export const chatRoom = worker({ +export const chatRoom = actor({ state: { messages: [] }, @@ -164,7 +164,7 @@ export const chatRoom = worker({ // Create and export the app export const registry = setup({ - workers: { chatRoom } + actors: { chatRoom } }); // Export type for client type checking @@ -199,9 +199,9 @@ test("scheduled tasks should execute", async (test) => { ``` ```ts src/index.ts -import { worker, setup } from "rivetkit"; +import { actor, setup } from "rivetkit"; -const scheduler = worker({ +const scheduler = actor({ state: { tasks: [], completedTasks: [] @@ -225,7 +225,7 @@ const scheduler = worker({ }); export const registry = setup({ - workers: { scheduler } + actors: { scheduler } }); export type Registry = typeof registry; @@ -237,7 +237,7 @@ The `setupTest` function automatically calls `vi.useFakeTimers()`, allowing you ## Best Practices 1. **Isolate tests**: Each test should run independently, avoiding shared state. -2. **Test edge cases**: Verify how your worker handles invalid inputs, concurrent operations, and error conditions. +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. diff --git a/docs/concepts/topology.mdx b/docs/concepts/topology.mdx index 855c2e1a5..95ce856b7 100644 --- a/docs/concepts/topology.mdx +++ b/docs/concepts/topology.mdx @@ -3,7 +3,7 @@ title: Topologies icon: list-tree --- -RivetKit supports three topologies that define how workers are distributed and scale. +RivetKit supports three topologies that define how actors are distributed and scale. Each platform configures a default topology appropriate for that environment. In most cases, you can rely on these defaults unless you have specific distribution needs. @@ -21,21 +21,21 @@ const config = { ### Standalone -- **How it works**: Runs all workers in a single process +- **How it works**: Runs all actors in a single process - **When to use**: Development, testing, simple apps with low traffic - **Limitations**: No horizontal scaling, single point of failure - **Default on**: Node.js, Bun ### Partition -- **How it works**: Each worker has its own isolated process. Clients connect directly to the worker for optimal performance. +- **How it works**: Each actor has its own isolated process. Clients connect directly to the actor for optimal performance. - **When to use**: Production environments needing horizontal scaling - **Limitations**: Minimal - balanced performance and availability for most use cases - **Default on**: Rivet, Cloudflare Workers ### Coordinate -- **How it works**: Creates a peer-to-peer network between multiple servers with leader election with multiple workers running on each server. Clients connect to any server and data is transmitted to the leader over a pubsub server. +- **How it works**: Creates a peer-to-peer network between multiple servers with leader election with multiple actors running on each server. Clients connect to any server and data is transmitted to the leader over a pubsub server. - **When to use**: High-availability scenarios needing redundancy and failover - **Limitations**: Added complexity, performance overhead, requires external data source - **Default on**: _None_ diff --git a/docs/docs.json b/docs/docs.json index c4bb8e139..362ac3e6d 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -46,33 +46,33 @@ ] }, { - "group": "Workers", + "group": "Actors", "pages": [ -"workers/overview", +"actors/overview", { "group": "Quickstart", "icon": "forward", "pages": [ -"workers/quickstart", -"workers/quickstart-frontend" +"actors/quickstart", +"actors/quickstart-frontend" ] }, -"workers/state", -"workers/actions", -"workers/events", +"actors/state", +"actors/actions", +"actors/events", { "group": "More", "pages": [ -"workers/schedule", +"actors/schedule", -"workers/lifecycle", -"workers/connections", -"workers/authentication", +"actors/lifecycle", +"actors/connections", +"actors/authentication", { "group": "Advanced", "pages": [ -"workers/metadata", -"workers/types" +"actors/metadata", +"actors/types" ] } @@ -137,7 +137,7 @@ "pages": [ "concepts/cors", "concepts/external-sql", - "concepts/interacting-with-workers", + "concepts/interacting-with-actors", "concepts/logging", "concepts/overview", "concepts/scaling", diff --git a/docs/drivers/build.mdx b/docs/drivers/build.mdx index 2892f7217..5f325217f 100644 --- a/docs/drivers/build.mdx +++ b/docs/drivers/build.mdx @@ -4,17 +4,17 @@ title: Building Drivers Each driver implements common interfaces defined by RivetKit, including: -- **WorkerDriver**: Manages worker state, lifecycle, and persistence -- **ManagerDriver**: Handles worker discovery, routing, and scaling -- **CoordinateDriver**: Facilitates peer-to-peer communication between worker instances (only relevant for the coordinated topology) +- **ActorDriver**: Manages actor state, lifecycle, and persistence +- **ManagerDriver**: Handles actor discovery, routing, and scaling +- **CoordinateDriver**: Facilitates peer-to-peer communication between actor instances (only relevant for the coordinated topology) ## Source Code Locations Get started by looking at source code for the driver interfaces and existing drivers: - **Driver Interfaces** - - **WorkerDriver*** `packages/rivetkit/src/worker/runtime/driver.ts` - - **ManagerDriver*** `packages/rivetkit/src/worker/runtime/driver.ts` + - **ActorDriver*** `packages/rivetkit/src/actor/runtime/driver.ts` + - **ManagerDriver*** `packages/rivetkit/src/actor/runtime/driver.ts` - **CoordinateDriver**: `packages/rivetkit/src/topologies/coordinate/driver.ts` - **Driver Implementations** - **Memory driver**: `packages/drivers/memory/` diff --git a/docs/drivers/cloudflare-workers.mdx b/docs/drivers/cloudflare-workers.mdx index dd69b49d2..c1e522fd8 100644 --- a/docs/drivers/cloudflare-workers.mdx +++ b/docs/drivers/cloudflare-workers.mdx @@ -5,7 +5,7 @@ sidebarTitle: Durable Objects import DriverNote from '/snippets/driver-note.mdx'; -The Cloudflare Workers Driver is an implementation that uses Cloudflare's Durable Objects for worker state persistence and coordination. It leverages Cloudflare's global network for low-latency, distributed worker systems that can scale automatically. +The Cloudflare Workers Driver is an implementation that uses Cloudflare's Durable Objects for actor state persistence and coordination. It leverages Cloudflare's global network for low-latency, distributed actor systems that can scale automatically. diff --git a/docs/drivers/file-system.mdx b/docs/drivers/file-system.mdx index d2e35b4a9..cd2143994 100644 --- a/docs/drivers/file-system.mdx +++ b/docs/drivers/file-system.mdx @@ -4,7 +4,7 @@ title: File System import DriverNote from '/snippets/driver-note.mdx'; -The File System Driver is a simple file-based implementation designed for development and testing environments. It stores all worker state in local files, providing persistence between application restarts. +The File System Driver is a simple file-based implementation designed for development and testing environments. It stores all actor state in local files, providing persistence between application restarts. @@ -56,14 +56,14 @@ The File System Driver is a simple file-based implementation designed for develo ```typescript src/index.ts import { serve } from "@rivetkit/nodejs" - import { FileSystemManagerDriver, FileSystemWorkerDriver, FileSystemGlobalState } from "@rivetkit/file-system"; + import { FileSystemManagerDriver, FileSystemActorDriver, FileSystemGlobalState } from "@rivetkit/file-system"; const fsState = new FileSystemGlobalState(); serve(app, { topology: "standalone", drivers: { manager: new FileSystemManagerDriver(app, fsState), - worker: new FileSystemWorkerDriver(fsState), + actor: new FileSystemActorDriver(fsState), }, }); ``` @@ -101,7 +101,7 @@ The File System Driver is a simple file-based implementation designed for develo The File System driver provides several benefits for development: -- **Persistence**: Worker state is stored in files and persists between application restarts +- **Persistence**: Actor state is stored in files and persists between application restarts - **Durability**: Data is written to disk, providing protection against process crashes - **Visibility**: State files can be inspected for debugging purposes - **No External Dependencies**: Doesn't require additional services like Redis diff --git a/docs/drivers/memory.mdx b/docs/drivers/memory.mdx index e6ec08583..7d15b7a80 100644 --- a/docs/drivers/memory.mdx +++ b/docs/drivers/memory.mdx @@ -4,7 +4,7 @@ title: Memory import DriverNote from '/snippets/driver-note.mdx'; -The Memory Driver is a simple in-memory implementation designed for development and testing environments. It stores all worker state in memory, which means data is not persisted between application restarts. +The Memory Driver is a simple in-memory implementation designed for development and testing environments. It stores all actor state in memory, which means data is not persisted between application restarts. @@ -56,14 +56,14 @@ The Memory Driver is a simple in-memory implementation designed for development ```typescript src/index.ts import { serve } from "@rivetkit/nodejs" - import { MemoryManagerDriver, MemoryWorkerDriver, MemoryGlobalState } from "@rivetkit/memory"; + import { MemoryManagerDriver, MemoryActorDriver, MemoryGlobalState } from "@rivetkit/memory"; const memoryState = new MemoryGlobalState(); serve(app, { topology: "standalone", drivers: { manager: new MemoryManagerDriver(app, memoryState), - worker: new MemoryWorkerDriver(memoryState), + actor: new MemoryActorDriver(memoryState), }, }); ``` @@ -104,7 +104,7 @@ The Memory driver has several limitations to be aware of: - **No Persistence**: All data is stored in memory and lost when the application restarts - **Single Process**: Only works within a single process - not suitable for distributed environments - **Scalability**: Cannot scale beyond a single instance -- **Coordination**: Limited support for coordinated topology, as workers can only communicate within the same process +- **Coordination**: Limited support for coordinated topology, as actors can only communicate within the same process For production environments or applications requiring persistence and distributed capabilities, consider using the [Rivet](/platforms/rivet) or [Cloudflare Workers](/platforms/cloudflare-workers) instead. diff --git a/docs/drivers/overview.mdx b/docs/drivers/overview.mdx index 2dc7e29da..622da8e42 100644 --- a/docs/drivers/overview.mdx +++ b/docs/drivers/overview.mdx @@ -5,7 +5,7 @@ sidebarTitle: Overview import DriverNote from '/snippets/driver-note.mdx'; -Drivers in RivetKit the infrastructure layer between your worker code and the underlying systems. +Drivers in RivetKit the infrastructure layer between your actor code and the underlying systems. @@ -19,7 +19,7 @@ Choose a driver based on your deployment needs - [Memory](/drivers/memory) for d - Local file system driver for development and testing with persistent storage. Stores worker state in files for durability between restarts. + Local file system driver for development and testing with persistent storage. Stores actor state in files for durability between restarts. In-memory driver for development and testing. Simple and lightweight with no external dependencies. diff --git a/docs/drivers/redis.mdx b/docs/drivers/redis.mdx index 648ef0c19..9d191d95b 100644 --- a/docs/drivers/redis.mdx +++ b/docs/drivers/redis.mdx @@ -4,7 +4,7 @@ title: Redis import DriverNote from '/snippets/driver-note.mdx'; -The Redis Driver is a production-ready implementation that uses Redis for worker state persistence, coordination, and communication. It supports distributed worker systems and enables horizontal scaling across multiple instances. +The Redis Driver is a production-ready implementation that uses Redis for actor state persistence, coordination, and communication. It supports distributed actor systems and enables horizontal scaling across multiple instances. @@ -57,7 +57,7 @@ The Redis Driver is a production-ready implementation that uses Redis for worker ```typescript src/index.ts import { serve } from "@rivetkit/nodejs" import { RedisManagerDriver } from "@rivetkit/redis/manager"; - import { RedisWorkerDriver } from "@rivetkit/redis/worker"; + import { RedisActorDriver } from "@rivetkit/redis/actor"; import { RedisCoordinateDriver } from "@rivetkit/redis/coordinate"; import Redis from "ioredis"; @@ -68,7 +68,7 @@ The Redis Driver is a production-ready implementation that uses Redis for worker topology: "coordinate", // Can be "standalone" or "coordinate" drivers: { manager: new RedisManagerDriver(redis), - worker: new RedisWorkerDriver(redis), + actor: new RedisActorDriver(redis), coordinate: new RedisCoordinateDriver(redis), }, }); @@ -110,7 +110,7 @@ The Redis driver requires an [ioredis](https://github.com/redis/ioredis) connect ```typescript import Redis from "ioredis"; import { RedisManagerDriver } from "@rivetkit/redis/manager"; -import { RedisWorkerDriver } from "@rivetkit/redis/worker"; +import { RedisActorDriver } from "@rivetkit/redis/actor"; import { RedisCoordinateDriver } from "@rivetkit/redis/coordinate"; // Create a Redis connection @@ -122,7 +122,7 @@ const redis = new Redis({ // Create the Redis drivers const managerDriver = new RedisManagerDriver(redis); -const workerDriver = new RedisWorkerDriver(redis); +const actorDriver = new RedisActorDriver(redis); const coordinateDriver = new RedisCoordinateDriver(redis); ``` diff --git a/docs/drivers/rivet.mdx b/docs/drivers/rivet.mdx index 862cf1f09..84542ff77 100644 --- a/docs/drivers/rivet.mdx +++ b/docs/drivers/rivet.mdx @@ -4,7 +4,7 @@ title: Rivet import DriverNote from '/snippets/driver-note.mdx'; -The Rivet Driver is a production-ready implementation that uses Rivet's managed infrastructure for worker state persistence, coordination, and communication. It provides a fully managed environment for RivetKit with built-in scaling, monitoring, and global distribution. +The Rivet Driver is a production-ready implementation that uses Rivet's managed infrastructure for actor state persistence, coordination, and communication. It provides a fully managed environment for RivetKit with built-in scaling, monitoring, and global distribution. @@ -28,7 +28,7 @@ See the [Rivet Platform](/platforms/rivet) documentation for complete setup inst The Rivet driver offers several advantages: - **Fully Managed**: No infrastructure to provision or maintain -- **Global Distribution**: Deploy workers globally with automatic region selection +- **Global Distribution**: Deploy actors globally with automatic region selection - **Monitoring**: Built-in observability and performance metrics - **Cost Efficiency**: Pay only for what you use with no upfront costs - **Partition Topology Support**: Optimized for the Partition topology for efficient scaling diff --git a/docs/frameworks/react.mdx b/docs/frameworks/react.mdx index 8e21b0d6a..dbb148ec9 100644 --- a/docs/frameworks/react.mdx +++ b/docs/frameworks/react.mdx @@ -4,12 +4,12 @@ icon: react --- import MvpWarning from "/snippets/mvp-warning.mdx"; -import StepDefineWorker from "/snippets/step-define-worker.mdx"; +import StepDefineActor from "/snippets/step-define-actor.mdx"; import StepRunStudio from "/snippets/step-run-studio.mdx"; import StepDeploy from "/snippets/step-deploy.mdx"; import SetupNextSteps from "/snippets/setup-next-steps.mdx"; -Learn how to create realtime, stateful React applications with RivetKit's worker model. +Learn how to create realtime, stateful React applications with RivetKit's actor model. @@ -64,7 +64,7 @@ Learn how to create realtime, stateful React applications with RivetKit's worker - + Now modify your `src/App.tsx` file to connect to your RivetKit backend: @@ -72,20 +72,20 @@ Learn how to create realtime, stateful React applications with RivetKit's worker ```tsx src/App.tsx import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; - import type { App } from "../workers/app"; + import type { App } from "../actors/app"; import React, { useState } from "react"; // Replace with your endpoint URL after deployment const client = createClient("http://localhost:6420"); - const { useWorker, useWorkerEvent } = createReactRivetKit(client); + const { useActor, useActorEvent } = createReactRivetKit(client); function App() { - // Connect to counter worker - const [{ worker }] = useWorker("counter"); + // Connect to counter actor + const [{ actor }] = useActor("counter"); const [count, setCount] = useState(0); // Listen to count updates - useWorkerEvent({ worker, event: "newCount" }, (newCount) => { + useActorEvent({ actor, event: "newCount" }, (newCount) => { setCount(newCount); }); @@ -93,8 +93,8 @@ Learn how to create realtime, stateful React applications with RivetKit's worker

Count: {count}

@@ -150,7 +150,7 @@ The React integration leverages React's hooks system to provide an idiomatic way The main function that creates React hooks for interacting with RivetKit. It takes a client instance and returns hook functions. ```tsx -const { useWorker, useWorkerEvent } = createReactRivetKit(client); +const { useActor, useActorEvent } = createReactRivetKit(client); ``` #### Parameters @@ -160,46 +160,46 @@ const { useWorker, useWorkerEvent } = createReactRivetKit(client); #### Returns An object containing React hooks: -- `useWorker`: Hook for connecting to workers -- `useWorkerEvent`: Hook for subscribing to worker events +- `useActor`: Hook for connecting to actors +- `useActorEvent`: Hook for subscribing to actor events -### `useWorker` +### `useActor` -Hook that connects to a worker, creating it if necessary. It manages the worker connection and returns the worker handle. +Hook that connects to a actor, creating it if necessary. It manages the actor connection and returns the actor handle. ```tsx -const [{ worker, error, isLoading, state }] = useWorker(workerName, options); +const [{ actor, error, isLoading, state }] = useActor(actorName, options); ``` #### Parameters -- `workerName`: The name of the worker to connect to (string). -- `options`: Optional connection options (same options as `client.workerName.get()`). - - `id`: String identifier for the worker instance. - - `tags`: Key-value pairs for worker identification. +- `actorName`: The name of the actor to connect to (string). +- `options`: Optional connection options (same options as `client.actorName.get()`). + - `id`: String identifier for the actor instance. + - `tags`: Key-value pairs for actor identification. - `params`: Parameters to pass during connection. - - `noCreate`: Boolean to prevent worker creation if it doesn't exist. + - `noCreate`: Boolean to prevent actor creation if it doesn't exist. #### Returns Returns an array with a single object containing: -- `worker`: The worker handle if connected, or `undefined` if still connecting. +- `actor`: The actor handle if connected, or `undefined` if still connecting. - `error`: Any error that occurred during connection. - `isLoading`: Boolean indicating if the connection is in progress. - `state`: String representing the internal connection state ("init", "creating", "created", or "error"). -### `useWorkerEvent` +### `useActorEvent` -Hook that subscribes to events from a worker. +Hook that subscribes to events from a actor. ```tsx -useWorkerEvent({ worker, event }, cb); +useActorEvent({ actor, event }, cb); ``` #### Parameters - `opts`: Object containing: - - `worker`: The worker handle from `useWorker`, or undefined. + - `actor`: The actor handle from `useActor`, or undefined. - `event`: The name of the event to subscribe to. - `cb`: Function called when the event is fired. The arguments passed to this function depend on the event type. @@ -214,25 +214,25 @@ This hook doesn't return a value. The subscription is automatically managed by t ```tsx import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; -import type { App } from "../workers/app"; +import type { App } from "../actors/app"; import { useState } from "react"; // Connect to RivetKit const client = createClient("http://localhost:6420"); -const { useWorker, useWorkerEvent } = createReactRivetKit(client); +const { useActor, useActorEvent } = createReactRivetKit(client); function Counter() { - // Get worker and track count - const [{ worker }] = useWorker("counter"); + // Get actor and track count + const [{ actor }] = useActor("counter"); const [count, setCount] = useState(0); // Listen for count updates - useWorkerEvent({ worker, event: "newCount" }, setCount); + useActorEvent({ actor, event: "newCount" }, setCount); return (

Count: {count}

-
diff --git a/docs/integrations/better-auth.mdx b/docs/integrations/better-auth.mdx index c9711fedc..716cbdacf 100644 --- a/docs/integrations/better-auth.mdx +++ b/docs/integrations/better-auth.mdx @@ -66,10 +66,10 @@ _If you want to run without export fetch (i.e. standalone Node.js), see below._ -```ts Worker -import { worker, setup } from "@rivetkit/worker"; +```ts Actor +import { actor, setup } from "@rivetkit/actor"; -export const counter = worker({ +export const counter = actor({ onAuth: () => { ... }, state: { count: 0 }, actions: { @@ -81,7 +81,7 @@ export const counter = worker({ }); export const registry = setup({ - workers: { counter }, + actors: { counter }, }); ``` @@ -199,7 +199,7 @@ const { client, hono } = registry.run({ ## Configuration Options -### Connect your frontend to the Rivet Worker +### Connect your frontend to the Rivet Actor TODO: Quick summary of why you would want to connect your frontend @@ -208,27 +208,27 @@ Connect your frontend: ```ts JavaScript -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import type { registry } from "./registry.js"; const client = createClient("http://localhost:8080/registry"); -const result = await client.myWorker.getOrCreate().myAction("Hello, world!"); +const result = await client.myActor.getOrCreate().myAction("Hello, world!"); ``` ```ts React import { useState } from "react"; -import { createClient, createRivetKit } from "@@rivetkit/worker/react"; +import { createClient, createRivetKit } from "@@rivetkit/actor/react"; import type { registry } from "./registry"; const client = createClient(`http://localhost:8080/registry`); -const { useWorker } = createRivetKit(client); +const { useActor } = createRivetKit(client); function App() { const [count, setCount] = useState(0); const [counterName, setCounterName] = useState("test-counter"); - const counter = useWorker({ + const counter = useActor({ name: "counter", key: [counterName], }); @@ -296,7 +296,7 @@ TODO - + diff --git a/docs/integrations/hono.mdx b/docs/integrations/hono.mdx index 9993b4179..543c91904 100644 --- a/docs/integrations/hono.mdx +++ b/docs/integrations/hono.mdx @@ -11,19 +11,19 @@ When mounting the RivetKit router at a custom path, you **must** specify the sam ```typescript // Setup the RivetKit app const registry = setup({ - workers: { counter }, + actors: { counter }, // IMPORTANT: Must specify the same basePath where your router is mounted basePath: "/my-path" }); // Create a router from the app -const { router: workerRouter } = createRouter(app); +const { router: actorRouter } = createRouter(app); // Mount at the same path specified in basePath -honoApp.route("/my-path", workerRouter); +honoApp.route("/my-path", actorRouter); ``` -This ensures that WebSocket connections and other functionality work correctly when accessing your workers through the custom path. +This ensures that WebSocket connections and other functionality work correctly when accessing your actors through the custom path. ## Platform-Specific Examples @@ -46,22 +46,22 @@ honoApp.get("/hello", (c) => c.text("Hello, world!")); // Setup the RivetKit app const registry = setup({ - workers: { counter }, + actors: { counter }, // IMPORTANT: Must specify the same basePath where your router is mounted basePath: "/my-path" }); // Create a router and handler from the app -const { router: workerRouter, WorkerHandler } = createRouter(app); +const { router: actorRouter, ActorHandler } = createRouter(app); // Mount the RivetKit router at /my-path -honoApp.route("/my-path", workerRouter); +honoApp.route("/my-path", actorRouter); // Export the app type for client usage export type Registry = typeof registry; -// IMPORTANT: Must export `WorkerHandler` as this exact name -export { honoApp as default, WorkerHandler }; +// IMPORTANT: Must export `ActorHandler` as this exact name +export { honoApp as default, ActorHandler }; ``` Make sure to update your client connection URL to include the custom path: @@ -71,7 +71,7 @@ Make sure to update your client connection URL to include the custom path: import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; -const client = createClient("https://your-worker.workers.dev/my-path"); +const client = createClient("https://your-actor.actors.dev/my-path"); ``` For this to work with Cloudflare Workers, your `wrangler.json` **must** include specific Durable Object and KV namespace bindings with the exact names expected by RivetKit: @@ -83,21 +83,21 @@ For this to work with Cloudflare Workers, your `wrangler.json` **must** include "compatibility_date": "2025-01-29", "migrations": [ { - "new_classes": ["WorkerHandler"], + "new_classes": ["ActorHandler"], "tag": "v1" } ], "durable_objects": { "bindings": [ { - "class_name": "WorkerHandler", // Must match exported class - "name": "WORKER_DO" // Must use this exact name + "class_name": "ActorHandler", // Must match exported class + "name": "ACTOR_DO" // Must use this exact name } ] }, "kv_namespaces": [ { - "binding": "WORKER_KV", // Must use this exact name + "binding": "ACTOR_KV", // Must use this exact name "id": "YOUR_KV_NAMESPACE_ID" // Replace with your KV ID } ] @@ -122,16 +122,16 @@ honoApp.get("/hello", (c) => c.text("Hello, world!")); // Setup the RivetKit app const registry = setup({ - workers: { counter }, + actors: { counter }, // IMPORTANT: Must specify the same basePath where your router is mounted basePath: "/my-path" }); // Create a router from the app -const { router: workerRouter, injectWebSocket } = createRouter(app); +const { router: actorRouter, injectWebSocket } = createRouter(app); // Mount the RivetKit router at /my-path -honoApp.route("/my-path", workerRouter); +honoApp.route("/my-path", actorRouter); // Export the app type for client usage export type Registry = typeof registry; @@ -174,16 +174,16 @@ honoApp.get("/hello", (c) => c.text("Hello, world!")); // Setup the RivetKit app const registry = setup({ - workers: { counter }, + actors: { counter }, // IMPORTANT: Must specify the same basePath where your router is mounted basePath: "/my-path" }); // Create a router from the app -const { router: workerRouter, webSocketHandler } = createRouter(app); +const { router: actorRouter, webSocketHandler } = createRouter(app); // Mount the RivetKit router at /my-path -honoApp.route("/my-path", workerRouter); +honoApp.route("/my-path", actorRouter); // Export the app type for client usage export type Registry = typeof registry; diff --git a/docs/integrations/resend.mdx b/docs/integrations/resend.mdx index 33276da5e..404ea6d05 100644 --- a/docs/integrations/resend.mdx +++ b/docs/integrations/resend.mdx @@ -19,14 +19,14 @@ npm install resend ``` - -```typescript workers.ts -import { worker, setup } from "rivetkit"; + +```typescript actors.ts +import { actor, setup } from "rivetkit"; import { Resend } from "resend"; const resend = new Resend(process.env.RESEND_API_KEY); -const user = worker({ +const user = actor({ state: { email: null as string | null, }, @@ -51,21 +51,21 @@ const user = worker({ }, }); -export const registry = setup({ workers: { user } }); +export const registry = setup({ actors: { user } }); export type Registry = typeof registry; ``` - + ```typescript client.ts import { createClient } from "rivetkit"; -import { App } from "./workers/app.ts"; +import { App } from "./actors/app.ts"; const client = createClient("http://localhost:8787"); -const userWorker = await client.user.get({ tags: { user: "user123" } }); +const userActor = await client.user.get({ tags: { user: "user123" } }); -await userWorker.register("user@example.com"); -await userWorker.sendExampleEmail(); +await userActor.register("user@example.com"); +await userActor.sendExampleEmail(); ``` @@ -77,8 +77,8 @@ await userWorker.sendExampleEmail(); RivetKit's scheduling capabilities with Resend make it easy to send emails at specific times: -```typescript workers.ts -const emailScheduler = worker({ +```typescript actors.ts +const emailScheduler = actor({ state: { email: null as string | null, }, @@ -115,8 +115,8 @@ await scheduler.scheduleEmail("user@example.com", 60000); // 1 minute Send daily reminders to users based on their activity: -```typescript workers.ts -const reminder = worker({ +```typescript actors.ts +const reminder = actor({ state: { email: null as string | null, lastActive: null as number | null, @@ -163,8 +163,8 @@ await userReminder.trackActivity("user@example.com"); Monitor your systems and send alerts when issues are detected: -```typescript workers.ts -const monitor = worker({ +```typescript actors.ts +const monitor = actor({ state: { alertEmail: null as string | null, isHealthy: true, @@ -212,12 +212,12 @@ await systemMonitor.configure("admin@example.com"); ## Testing -When testing workers that use Resend, you should mock the Resend API to avoid sending real emails during tests. RivetKit's testing utilities combined with Vitest make this straightforward: +When testing actors that use Resend, you should mock the Resend API to avoid sending real emails during tests. RivetKit's testing utilities combined with Vitest make this straightforward: ```typescript import { test, expect, vi, beforeEach } from "vitest"; import { setupTest } from "rivetkit/test"; -import { app } from "../workers/app"; +import { app } from "../actors/app"; // Create mock for send method const mockSendEmail = vi.fn().mockResolvedValue({ success: true }); @@ -242,10 +242,10 @@ beforeEach(() => { test("email is sent when action is called", async (t) => { const { client } = await setupTest(t, app); - const worker = await client.user.get(); + const actor = await client.user.get(); // Call the action that should send an email - await worker.someActionThatSendsEmail("user@example.com"); + await actor.someActionThatSendsEmail("user@example.com"); // Verify the email was sent with the right parameters expect(mockSendEmail).toHaveBeenCalledWith( diff --git a/docs/introduction.mdx b/docs/introduction.mdx index 286ff6df4..7a251d3bc 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -31,7 +31,7 @@ import FAQ from "/snippets/landing-faq.mdx";

{/*
- + Get Started window.copyCommand && window.copyCommand(e.currentTarget)}>
-
npx create-worker@latest
+
npx create-actor@latest
@@ -56,11 +56,11 @@ import FAQ from "/snippets/landing-faq.mdx";
*/}
- +
-

Workers

+

Actors

Long running tasks with state persistence, hibernation, and realtime

@@ -337,10 +337,10 @@ import FAQ from "/snippets/landing-faq.mdx";
-

Performance in every act - thanks to Rivet Workers.

+

Performance in every act - thanks to Rivet Actors.

- + Get Started diff --git a/docs/llm/claude.mdx b/docs/llm/claude.mdx index 05896be0c..7e80ed611 100644 --- a/docs/llm/claude.mdx +++ b/docs/llm/claude.mdx @@ -22,7 +22,7 @@ To set up an effective `CLAUDE.md` file for RivetKit: Paste the template into `CLAUDE.md` - Run Claude Code and ask a simple question like `What lifecycle hook is used when a worker is first created?` to confirm it's reading your CLAUDE.md file correctly + Run Claude Code and ask a simple question like `What lifecycle hook is used when a actor is first created?` to confirm it's reading your CLAUDE.md file correctly @@ -36,11 +36,11 @@ Here are some useful ways to leverage Claude Code with RivetKit: # Get an overview of RivetKit's architecture claude "explain the architecture of RivetKit and how the different topologies work" -# Understand how workers communicate -claude "explain how workers communicate with each other in the coordinate topology" +# Understand how actors communicate +claude "explain how actors communicate with each other in the coordinate topology" # Learn about specific concepts -claude "explain the lifecycle hooks for workers and when each one is called" +claude "explain the lifecycle hooks for actors and when each one is called" ``` ### Find Relevant Code @@ -52,34 +52,34 @@ claude "find the files that implement the Redis driver" # Locate examples of specific patterns claude "show me examples of RPC methods in the codebase" -# Understand worker state management -claude "explain how worker state is persisted between restarts" +# Understand actor state management +claude "explain how actor state is persisted between restarts" ``` ### Add New Features ```bash -# Create a new worker implementation -claude "help me create a new worker for managing user sessions" +# Create a new actor implementation +claude "help me create a new actor for managing user sessions" -# Add authentication to a worker -claude "show me how to add authentication to my worker's _onBeforeConnect method" +# Add authentication to a actor +claude "show me how to add authentication to my actor's _onBeforeConnect method" # Implement error handling -claude "help me implement proper error handling for my worker's RPC methods" +claude "help me implement proper error handling for my actor's RPC methods" ``` ### Debug Issues ```bash # Diagnose connection problems -claude "my worker connections are dropping, help me debug why" +claude "my actor connections are dropping, help me debug why" # Fix state persistence issues -claude "my worker state isn't persisting between restarts, what could be wrong?" +claude "my actor state isn't persisting between restarts, what could be wrong?" # Address scaling problems -claude "my workers are using too much memory, how can I optimize them?" +claude "my actors are using too much memory, how can I optimize them?" ``` ## Additional Resources diff --git a/docs/llm/cursor.mdx b/docs/llm/cursor.mdx index e6ca440e9..7779627ea 100644 --- a/docs/llm/cursor.mdx +++ b/docs/llm/cursor.mdx @@ -52,11 +52,11 @@ Here are some useful prompts to try with Cursor when working with RivetKit: # Get an overview of RivetKit's architecture Explain the architecture of RivetKit and how the different topologies work -# Understand how workers communicate -Explain how workers communicate with each other in the coordinate topology +# Understand how actors communicate +Explain how actors communicate with each other in the coordinate topology # Learn about specific concepts -Explain the lifecycle hooks for workers and when each one is called +Explain the lifecycle hooks for actors and when each one is called ``` ### Find Relevant Code @@ -68,34 +68,34 @@ Find the files that implement the Redis driver # Locate examples of specific patterns Show me examples of RPC methods in the codebase -# Understand worker state management -Explain how worker state is persisted between restarts +# Understand actor state management +Explain how actor state is persisted between restarts ``` ### Add New Features ``` -# Create a new worker implementation -Help me create a new worker for managing user sessions +# Create a new actor implementation +Help me create a new actor for managing user sessions -# Add authentication to a worker -Show me how to add authentication to my worker's _onBeforeConnect method +# Add authentication to a actor +Show me how to add authentication to my actor's _onBeforeConnect method # Implement error handling -Help me implement proper error handling for my worker's RPC methods +Help me implement proper error handling for my actor's RPC methods ``` ### Debug Issues ``` # Diagnose connection problems -My worker connections are dropping, help me debug why +My actor connections are dropping, help me debug why # Fix state persistence issues -My worker state isn't persisting between restarts, what could be wrong? +My actor state isn't persisting between restarts, what could be wrong? # Address scaling problems -My workers are using too much memory, how can I optimize them? +My actors are using too much memory, how can I optimize them? ``` ## Additional Resources diff --git a/docs/llm/prompt.mdx b/docs/llm/prompt.mdx index 9aa306867..00e9f0ec9 100644 --- a/docs/llm/prompt.mdx +++ b/docs/llm/prompt.mdx @@ -36,10 +36,10 @@ This guide contains essential information for working with the RivetKit project. ### Core Concepts -- **Worker**: A stateful, long-lived entity that processes messages and maintains state -- **Manager**: Component responsible for creating, routing, and managing worker instances -- **Action**: Method for a worker to expose callable functions to clients -- **Event**: Asynchronous message sent from a worker to connected clients +- **Actor**: A stateful, long-lived entity that processes messages and maintains state +- **Manager**: Component responsible for creating, routing, and managing actor instances +- **Action**: Method for a actor to expose callable functions to clients +- **Event**: Asynchronous message sent from a actor to connected clients - **Alarm**: Scheduled callback that triggers at a specific time ## Build Commands @@ -56,7 +56,7 @@ Available driver implementations: - **Memory**: In-memory implementation for development and testing - **Redis**: Production-ready implementation using Redis for persistence and pub/sub -- **Cloudflare Workers**: Uses Durable Objects for worker state persistence +- **Cloudflare Workers**: Uses Durable Objects for actor state persistence - **Rivet**: Fully-managed cloud platform with built-in scaling and monitoring ## Platform Support @@ -98,7 +98,7 @@ When importing from workspace packages, always check the package's `package.json ## State Management -- Each worker owns and manages its own isolated state via `c.state` +- Each actor owns and manages its own isolated state via `c.state` - State is automatically persisted between action calls - State is initialized via `createState` function or `state` constant - Only JSON-serializable types can be stored in state @@ -115,43 +115,43 @@ When importing from workspace packages, always check the package's `package.json ## Actions and Events -- **Actions**: Used for clients to call worker functions -- **Events**: For workers to publish updates to clients -- Actions are defined in the `actions` object of the worker configuration +- **Actions**: Used for clients to call actor functions +- **Events**: For actors to publish updates to clients +- Actions are defined in the `actions` object of the actor configuration - Helper functions outside the `actions` object are not callable by clients - Broadcasting is done via `c.broadcast(name, data)` - Specific client messaging uses `conn.send(name, data)` -- Clients subscribe to events with `worker.on(eventName, callback)` +- Clients subscribe to events with `actor.on(eventName, callback)` ## Lifecycle Hooks -- `createState()`: Function that returns initial worker state -- `onStart(c)`: Called any time worker is started (after restart/upgrade) -- `onStateChange(c, newState)`: Called when worker state changes +- `createState()`: Function that returns initial actor state +- `onStart(c)`: Called any time actor is started (after restart/upgrade) +- `onStateChange(c, newState)`: Called when actor state changes - `onBeforeConnect(c)`: Called when new client connects - `onConnect(c)`: Executed after client connection succeeds - `onDisconnect(c)`: Called when client disconnects -## Worker Management +## Actor Management -- App is configured with workers using `setup({ workers: { workerName }})` followed by `serve(registry)` -- Workers are accessed by client using `client.workerName.get()` -- Workers can pass an ID parameter or object with `client.workerName.get(id)` or `client.workerName.get({key: value})` -- Workers can be shut down with `c.shutdown()` from within the worker +- App is configured with actors using `setup({ actors: { actorName }})` followed by `serve(registry)` +- Actors are accessed by client using `client.actorName.get()` +- Actors can pass an ID parameter or object with `client.actorName.get(id)` or `client.actorName.get({key: value})` +- Actors can be shut down with `c.shutdown()` from within the actor ## Scaling and Architecture Guidelines -- Each worker should have a single responsibility -- Keep state minimal and relevant to the worker's core function -- Use separate workers for different entity types (users, rooms, documents) -- Avoid too many cross-worker communications +- Each actor should have a single responsibility +- Keep state minimal and relevant to the actor's core function +- Use separate actors for different entity types (users, rooms, documents) +- Avoid too many cross-actor communications - Use appropriate topology based on your scaling needs ## Scheduling - Schedule future events with `c.after(duration, fn, ...args)` - Schedule events for specific time with `c.at(timestamp, fn, ...args)` -- Scheduled events persist across worker restarts +- Scheduled events persist across actor restarts ## CORS Configuration @@ -161,7 +161,7 @@ When importing from workspace packages, always check the package's `package.json ## Development Best Practices -- Prefer functional worker pattern with `worker({ ... })` syntax +- Prefer functional actor pattern with `actor({ ... })` syntax - Use zod for runtime type validation - Use `assertUnreachable(x: never)` for exhaustive type checking - Add proper JSDoc comments for public APIs diff --git a/docs/llm/windsurf.mdx b/docs/llm/windsurf.mdx index 48c65613d..fe234e624 100644 --- a/docs/llm/windsurf.mdx +++ b/docs/llm/windsurf.mdx @@ -50,11 +50,11 @@ Here are some useful prompts to try with Windsurf when working with RivetKit: # Get an overview of RivetKit's architecture Explain the architecture of RivetKit and how the different topologies work -# Understand how workers communicate -Explain how workers communicate with each other in the coordinate topology +# Understand how actors communicate +Explain how actors communicate with each other in the coordinate topology # Learn about specific concepts -Explain the lifecycle hooks for workers and when each one is called +Explain the lifecycle hooks for actors and when each one is called ``` ### Find Relevant Code @@ -66,34 +66,34 @@ Find the files that implement the Redis driver # Locate examples of specific patterns Show me examples of RPC methods in the codebase -# Understand worker state management -Explain how worker state is persisted between restarts +# Understand actor state management +Explain how actor state is persisted between restarts ``` ### Add New Features ``` -# Create a new worker implementation -Help me create a new worker for managing user sessions +# Create a new actor implementation +Help me create a new actor for managing user sessions -# Add authentication to a worker -Show me how to add authentication to my worker's _onBeforeConnect method +# Add authentication to a actor +Show me how to add authentication to my actor's _onBeforeConnect method # Implement error handling -Help me implement proper error handling for my worker's RPC methods +Help me implement proper error handling for my actor's RPC methods ``` ### Debug Issues ``` # Diagnose connection problems -My worker connections are dropping, help me debug why +My actor connections are dropping, help me debug why # Fix state persistence issues -My worker state isn't persisting between restarts, what could be wrong? +My actor state isn't persisting between restarts, what could be wrong? # Address scaling problems -My workers are using too much memory, how can I optimize them? +My actors are using too much memory, how can I optimize them? ``` ## Additional Resources diff --git a/docs/openapi.json b/docs/openapi.json index c7efdaf95..9e6fdcd3b 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -11,7 +11,7 @@ "properties": { "i": { "type": "string", - "example": "worker-123" + "example": "actor-123" } }, "required": [ @@ -25,7 +25,7 @@ "nullable": true, "example": { "getForId": { - "workerId": "worker-123" + "actorId": "actor-123" } } } @@ -41,7 +41,7 @@ "nullable": true, "example": { "getForId": { - "workerId": "worker-123" + "actorId": "actor-123" } } }, @@ -64,7 +64,7 @@ "nullable": true, "example": { "type": "message", - "content": "Hello, worker!" + "content": "Hello, actor!" } } } @@ -73,13 +73,13 @@ "parameters": {} }, "paths": { - "/workers/resolve": { + "/actors/resolve": { "post": { "parameters": [ { "schema": { "type": "string", - "description": "Worker query information" + "description": "Actor query information" }, "required": true, "name": "X-RivetKit-Query", @@ -115,7 +115,7 @@ } } }, - "/workers/connect/websocket": { + "/actors/connect/websocket": { "get": { "responses": { "101": { @@ -124,7 +124,7 @@ } } }, - "/workers/connect/sse": { + "/actors/connect/sse": { "get": { "parameters": [ { @@ -140,7 +140,7 @@ { "schema": { "type": "string", - "description": "Worker query information" + "description": "Actor query information" }, "required": true, "name": "X-RivetKit-Query", @@ -170,7 +170,7 @@ } } }, - "/workers/actions/{action}": { + "/actors/actions/{action}": { "post": { "parameters": [ { @@ -231,17 +231,17 @@ } } }, - "/workers/message": { + "/actors/message": { "post": { "parameters": [ { "schema": { "type": "string", - "description": "Worker ID (used in some endpoints)", - "example": "worker-123456" + "description": "Actor ID (used in some endpoints)", + "example": "actor-123456" }, "required": true, - "name": "X-RivetKit-Worker", + "name": "X-RivetKit-Actor", "in": "header" }, { diff --git a/docs/platforms/bun.mdx b/docs/platforms/bun.mdx index be9378d69..3f940ed8f 100644 --- a/docs/platforms/bun.mdx +++ b/docs/platforms/bun.mdx @@ -30,7 +30,7 @@ Bun provides a fast runtime environment for running RivetKit, with excellent per ```typescript src/index.ts import { serve } from "@rivetkit/bun"; - import { app } from "../workers/app"; + import { app } from "../actors/app"; // Start the server with file-system driver (default) serve(registry); diff --git a/docs/platforms/cloudflare-workers.mdx b/docs/platforms/cloudflare-workers.mdx index 4077975f6..760d4954a 100644 --- a/docs/platforms/cloudflare-workers.mdx +++ b/docs/platforms/cloudflare-workers.mdx @@ -45,21 +45,21 @@ The Cloudflare Workers platform with Durable Objects provides a robust environme ```typescript src/index.ts import { createHandler } from "@rivetkit/cloudflare-workers"; - import { app } from "../workers/app"; + import { app } from "../actors/app"; // Create handlers for Cloudflare Workers - const { handler, WorkerHandler } = createHandler(app); + const { handler, ActorHandler } = createHandler(app); // Export the handlers for Cloudflare - export { handler as default, WorkerHandler }; + export { handler as default, ActorHandler }; ``` - Create a KV namespace for your workers: + Create a KV namespace for your actors: ```sh - npx wrangler kv:namespace create WORKER_KV + npx wrangler kv:namespace create ACTOR_KV ``` Take note of the KV namespace ID from the output, as you'll need it in the next step. @@ -80,20 +80,20 @@ The Cloudflare Workers platform with Durable Objects provides a robust environme "migrations": [ { "tag": "v1", - "new_classes": ["WorkerHandler"] + "new_classes": ["ActorHandler"] } ], "durable_objects": { "bindings": [ { - "name": "WORKER_DO", - "class_name": "WorkerHandler" + "name": "ACTOR_DO", + "class_name": "ActorHandler" } ] }, "kv_namespaces": [ { - "binding": "WORKER_KV", + "binding": "ACTOR_KV", "id": "your-namespace-id-here" // Replace with your actual ID } ] @@ -118,12 +118,12 @@ The Cloudflare Workers platform with Durable Objects provides a robust environme ## Accessing Cloudflare Context And Bindings -You can access Cloudflare-specific features like the [DurableObjectState](https://developers.cloudflare.com/durable-objects/api/state/) and environment bindings from your worker: +You can access Cloudflare-specific features like the [DurableObjectState](https://developers.cloudflare.com/durable-objects/api/state/) and environment bindings from your actor: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const myWorker = worker({ +const myActor = actor({ // Load Cloudflare-specific variables createVars: (c, cloudflare) => ({ ctx: cloudflare.ctx, diff --git a/docs/platforms/nodejs.mdx b/docs/platforms/nodejs.mdx index 9e9b392fe..9dc10a784 100644 --- a/docs/platforms/nodejs.mdx +++ b/docs/platforms/nodejs.mdx @@ -44,7 +44,7 @@ Node.js provides a robust environment for running RivetKit, ideal for developmen ```typescript src/index.ts import { serve } from "@rivetkit/nodejs"; - import { app } from "../workers/app"; + import { app } from "../actors/app"; // Start the server with file-system driver (default) serve(registry); diff --git a/docs/platforms/rivet.mdx b/docs/platforms/rivet.mdx index 0f1519d52..47afaeaed 100644 --- a/docs/platforms/rivet.mdx +++ b/docs/platforms/rivet.mdx @@ -44,19 +44,19 @@ Rivet provides a fully managed cloud service for running RivetKit, with automati ```sh npm - npx rivetkit/cli@latest deploy rivet workers/app.ts + npx rivetkit/cli@latest deploy rivet actors/app.ts ``` ```sh pnpm - pnpm exec rivetkit/cli@latest deploy rivet workers/app.ts + pnpm exec rivetkit/cli@latest deploy rivet actors/app.ts ``` ```sh yarn - yarn rivetkit/cli@latest deploy rivet workers/app.ts + yarn rivetkit/cli@latest deploy rivet actors/app.ts ``` ```sh bun - bunx rivetkit/cli@latest deploy rivet workers/app.ts + bunx rivetkit/cli@latest deploy rivet actors/app.ts ``` @@ -76,19 +76,19 @@ Rivet provides a fully managed cloud service for running RivetKit, with automati ## Accessing Rivet Context -[Rivet's `WorkerContext`](https://rivet.gg/docs/javascript-runtime#the-worker-context-object) can be accessed from `createVars`. +[Rivet's `ActorContext`](https://rivet.gg/docs/javascript-runtime#the-actor-context-object) can be accessed from `createVars`. ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const myWorker = worker({ +const myActor = actor({ // Load Rivet-specific variables createVars: (c, rivet) => ({ rivet: rivet.ctx, }), actions: { foo: async (c) => { - // Access WorkerContext + // Access ActorContext c.log.info(`Region: ${c.vars.rivet.metadata.region.name}`); await c.vars.rivet.kv.get("foo"); }, @@ -98,7 +98,7 @@ const myWorker = worker({ ## Available Regions -Rivet supports deploying your workers to multiple regions automatically. You can specify region preferences in your Rivet project settings in the Rivet Hub. +Rivet supports deploying your actors to multiple regions automatically. You can specify region preferences in your Rivet project settings in the Rivet Hub. See available regions [here](https://rivet.gg/docs/regions). diff --git a/docs/snippets/cloudflare-deploy.mdx b/docs/snippets/cloudflare-deploy.mdx index 8bcadcf5d..04820035e 100644 --- a/docs/snippets/cloudflare-deploy.mdx +++ b/docs/snippets/cloudflare-deploy.mdx @@ -1,7 +1,7 @@ 1. Create a new KV namespace with: ```sh - npx wrangler kv namespace create WORKER_KV + 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: @@ -10,7 +10,7 @@ { "kv_namespaces": [ { - "binding": "WORKER_KV", + "binding": "ACTOR_KV", "id": "your-namespace-id-here" // Replace with your actual ID } ] @@ -30,7 +30,7 @@ import { createClient } from "rivetkit/client"; import type { App } from "../src/index"; - const client = createClient("https://your-worker-subdomain.workers.dev"); + const client = createClient("https://your-actor-subdomain.actors.dev"); ``` - Ensure you replace `your-worker-subdomain` with the actual subdomain assigned to your worker. + Ensure you replace `your-actor-subdomain` with the actual subdomain assigned to your actor. diff --git a/docs/snippets/create-actor-cli.mdx b/docs/snippets/create-actor-cli.mdx index 07fac8b22..65366ec96 100644 --- a/docs/snippets/create-actor-cli.mdx +++ b/docs/snippets/create-actor-cli.mdx @@ -1,21 +1,21 @@ ```sh npx -npx create-worker@latest +npx create-actor@latest ``` ```sh npm -npm create worker@latest +npm create actor@latest ``` ```sh pnpm -pnpm create worker@latest +pnpm create actor@latest ``` ```sh yarn -yarn create worker@latest +yarn create actor@latest ``` ```sh bun -bun create worker@latest +bun create actor@latest ``` diff --git a/docs/snippets/examples/ai-agent-js.mdx b/docs/snippets/examples/ai-agent-js.mdx index a93bafad4..22bb26ef5 100644 --- a/docs/snippets/examples/ai-agent-js.mdx +++ b/docs/snippets/examples/ai-agent-js.mdx @@ -1,12 +1,12 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { generateText, tool } from "ai"; import { openai } from "@ai-sdk/openai"; import { getWeather } from "./my-utils"; export type Message = { role: "user" | "assistant"; content: string; timestamp: number; } -const aiAgent = worker({ +const aiAgent = actor({ // State is automatically persisted state: { messages: [] as Message[] diff --git a/docs/snippets/examples/ai-agent-react.mdx b/docs/snippets/examples/ai-agent-react.mdx index 688bc0fe7..13d6e7e14 100644 --- a/docs/snippets/examples/ai-agent-react.mdx +++ b/docs/snippets/examples/ai-agent-react.mdx @@ -2,41 +2,41 @@ import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { App } from "../workers/app"; -import type { Message } from "./worker"; +import type { App } from "../actors/app"; +import type { Message } from "./actor"; const client = createClient("http://localhost:6420"); -const { useWorker, useWorkerEvent } = createReactRivetKit(client); +const { useActor, useActorEvent } = createReactRivetKit(client); export function AIAssistant() { - const [{ worker }] = useWorker("aiAgent", { tags: { conversationId: "default" } }); + const [{ actor }] = useActor("aiAgent", { tags: { conversationId: "default" } }); const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [isLoading, setIsLoading] = useState(false); // Load initial messages useEffect(() => { - if (worker) { - worker.getMessages().then(setMessages); + if (actor) { + actor.getMessages().then(setMessages); } - }, [worker]); + }, [actor]); // Listen for real-time messages - useWorkerEvent({ worker, event: "messageReceived" }, (message) => { + useActorEvent({ actor, event: "messageReceived" }, (message) => { setMessages(prev => [...prev, message as Message]); setIsLoading(false); }); const handleSendMessage = async () => { - if (worker && input.trim()) { + if (actor && input.trim()) { setIsLoading(true); // Add user message to UI immediately const userMessage = { role: "user", content: input } as Message; setMessages(prev => [...prev, userMessage]); - // Send to worker (AI response will come through the event) - await worker.sendMessage(input); + // Send to actor (AI response will come through the event) + await actor.sendMessage(input); setInput(""); } }; diff --git a/docs/snippets/examples/ai-agent-sqlite.mdx b/docs/snippets/examples/ai-agent-sqlite.mdx index eda07de22..f80c40132 100644 --- a/docs/snippets/examples/ai-agent-sqlite.mdx +++ b/docs/snippets/examples/ai-agent-sqlite.mdx @@ -1,5 +1,5 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { generateText, tool } from "ai"; import { openai } from "@ai-sdk/openai"; @@ -8,7 +8,7 @@ import { messages } from "./schema"; export type Message = { role: "user" | "assistant"; content: string; timestamp: number; } -const aiAgent = worker({ +const aiAgent = actor({ sql: drizzle(), actions: { @@ -28,7 +28,7 @@ const aiAgent = worker({ // Add user message to conversation const userMsg = { - conversationId: c.workerId, // Use the worker instance ID + conversationId: c.actorId, // Use the actor instance ID role: "user", content: userMessage, }; diff --git a/docs/snippets/examples/chat-room-js.mdx b/docs/snippets/examples/chat-room-js.mdx index e8db01eb3..a9b120a34 100644 --- a/docs/snippets/examples/chat-room-js.mdx +++ b/docs/snippets/examples/chat-room-js.mdx @@ -1,9 +1,9 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; export type Message = { sender: string; text: string; timestamp: number; } -const chatRoom = worker({ +const chatRoom = actor({ // State is automatically persisted state: { messages: [] as Message[] diff --git a/docs/snippets/examples/chat-room-react.mdx b/docs/snippets/examples/chat-room-react.mdx index 78d516c22..4c6391794 100644 --- a/docs/snippets/examples/chat-room-react.mdx +++ b/docs/snippets/examples/chat-room-react.mdx @@ -2,15 +2,15 @@ import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { App } from "../workers/app"; -import type { Message } from "./worker"; +import type { App } from "../actors/app"; +import type { Message } from "./actor"; const client = createClient("http://localhost:6420"); -const { useWorker, useWorkerEvent } = createReactRivetKit(client); +const { useActor, useActorEvent } = createReactRivetKit(client); export function ChatRoom({ roomId = "general" }) { // Connect to specific chat room using tags - const [{ worker }] = useWorker("chatRoom", { + const [{ actor }] = useActor("chatRoom", { tags: { roomId } }); @@ -19,20 +19,20 @@ export function ChatRoom({ roomId = "general" }) { // Load initial state useEffect(() => { - if (worker) { + if (actor) { // Load chat history - worker.getHistory().then(setMessages); + actor.getHistory().then(setMessages); } - }, [worker]); + }, [actor]); // Listen for real-time updates from the server - useWorkerEvent({ worker, event: "newMessage" }, (message) => { + useActorEvent({ actor, event: "newMessage" }, (message) => { setMessages(prev => [...prev, message]); }); const sendMessage = () => { - if (worker && input.trim()) { - worker.sendMessage("User", input); + if (actor && input.trim()) { + actor.sendMessage("User", input); setInput(""); } }; diff --git a/docs/snippets/examples/chat-room-sqlite.mdx b/docs/snippets/examples/chat-room-sqlite.mdx index 38c9c7c38..8c192ae7e 100644 --- a/docs/snippets/examples/chat-room-sqlite.mdx +++ b/docs/snippets/examples/chat-room-sqlite.mdx @@ -1,11 +1,11 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { messages } from "./schema"; export type Message = { sender: string; text: string; timestamp: number; } -const chatRoom = worker({ +const chatRoom = actor({ sql: drizzle(), actions: { diff --git a/docs/snippets/examples/crdt-js.mdx b/docs/snippets/examples/crdt-js.mdx index 6614d2d45..f8921de58 100644 --- a/docs/snippets/examples/crdt-js.mdx +++ b/docs/snippets/examples/crdt-js.mdx @@ -1,9 +1,9 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import * as Y from 'yjs'; import { encodeStateAsUpdate, applyUpdate } from 'yjs'; -const yjsDocument = worker({ +const yjsDocument = actor({ // State: just the serialized Yjs document data state: { docData: "", // Base64 encoded Yjs document @@ -15,7 +15,7 @@ const yjsDocument = worker({ doc: new Y.Doc() }), - // Initialize document from state when worker starts + // Initialize document from state when actor starts onStart: (c) => { if (c.state.docData) { const binary = atob(c.state.docData); diff --git a/docs/snippets/examples/crdt-react.mdx b/docs/snippets/examples/crdt-react.mdx index 4ff9677cd..e3275e185 100644 --- a/docs/snippets/examples/crdt-react.mdx +++ b/docs/snippets/examples/crdt-react.mdx @@ -4,14 +4,14 @@ import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect, useRef } from "react"; import * as Y from 'yjs'; import { applyUpdate, encodeStateAsUpdate } from 'yjs'; -import type { App } from "../workers/app"; +import type { App } from "../actors/app"; const client = createClient("http://localhost:6420"); -const { useWorker, useWorkerEvent } = createReactRivetKit(client); +const { useActor, useActorEvent } = createReactRivetKit(client); export function YjsEditor({ documentId = "shared-doc" }) { // Connect to specific document using tags - const { worker } = useWorker("yjsDocument", { + const { actor } = useActor("yjsDocument", { tags: { documentId } }); @@ -38,7 +38,7 @@ export function YjsEditor({ documentId = "shared-doc" }) { // Clean up Yjs document yDoc.destroy(); }; - }, [worker]); + }, [actor]); // Set up text observation useEffect(() => { @@ -55,14 +55,14 @@ export function YjsEditor({ documentId = "shared-doc" }) { // Update React state setText(yText.toString()); - if (worker && !updatingFromLocal.current) { + if (actor && !updatingFromLocal.current) { // Set flag to prevent loops updatingFromLocal.current = true; // Convert update to base64 and send to server const update = encodeStateAsUpdate(yDoc); const base64 = bufferToBase64(update); - worker.applyUpdate(base64).finally(() => { + actor.applyUpdate(base64).finally(() => { updatingFromLocal.current = false; }); } @@ -70,10 +70,10 @@ export function YjsEditor({ documentId = "shared-doc" }) { }); observationInitialized.current = true; - }, [worker]); + }, [actor]); // Handle initial state from server - useWorkerEvent({ worker, event: "initialState" }, ({ update }) => { + useActorEvent({ actor, event: "initialState" }, ({ update }) => { const yDoc = yDocRef.current; if (!yDoc) return; @@ -102,7 +102,7 @@ export function YjsEditor({ documentId = "shared-doc" }) { }); // Handle updates from other clients - useWorkerEvent({ worker, event: "update" }, ({ update }) => { + useActorEvent({ actor, event: "update" }, ({ update }) => { const yDoc = yDocRef.current; if (!yDoc) return; diff --git a/docs/snippets/examples/crdt-sqlite.mdx b/docs/snippets/examples/crdt-sqlite.mdx index 0e871bfba..73805c5a3 100644 --- a/docs/snippets/examples/crdt-sqlite.mdx +++ b/docs/snippets/examples/crdt-sqlite.mdx @@ -1,11 +1,11 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import * as Y from 'yjs'; import { encodeStateAsUpdate, applyUpdate } from 'yjs'; import { documents } from "./schema"; -const yjsDocument = worker({ +const yjsDocument = actor({ sql: drizzle(), // In-memory Yjs objects (not serialized) @@ -13,7 +13,7 @@ const yjsDocument = worker({ doc: new Y.Doc() }), - // Initialize document from state when worker starts + // Initialize document from state when actor starts onStart: async (c) => { // Get document data from database const documentData = await c.db diff --git a/docs/snippets/examples/database-js.mdx b/docs/snippets/examples/database-js.mdx index 445ee338c..a5c6a6610 100644 --- a/docs/snippets/examples/database-js.mdx +++ b/docs/snippets/examples/database-js.mdx @@ -1,11 +1,11 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { authenticate } from "./my-utils"; export type Note = { id: string; content: string; updatedAt: number }; -// User notes worker -const notes = worker({ +// User notes actor +const notes = actor({ state: { notes: [] as Note[] }, diff --git a/docs/snippets/examples/database-react.mdx b/docs/snippets/examples/database-react.mdx index da06385fc..7963d3898 100644 --- a/docs/snippets/examples/database-react.mdx +++ b/docs/snippets/examples/database-react.mdx @@ -4,51 +4,51 @@ import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; const client = createClient("http://localhost:6420"); -const { useWorker, useWorkerEvent } = createReactRivetKit(client); +const { useActor, useActorEvent } = createReactRivetKit(client); export function NotesApp({ userId }: { userId: string }) { const [notes, setNotes] = useState>([]); const [newNote, setNewNote] = useState(""); - // Connect to worker with auth token - const [{ worker }] = useWorker("notes", { + // Connect to actor with auth token + const [{ actor }] = useActor("notes", { params: { userId, token: "demo-token" } }); // Load initial notes useEffect(() => { - if (worker) { - worker.getNotes().then(setNotes); + if (actor) { + actor.getNotes().then(setNotes); } - }, [worker]); + }, [actor]); // Add a new note const addNote = async () => { - if (worker && newNote.trim()) { - await worker.updateNote({ id: `note-${Date.now()}`, content: newNote }); + if (actor && newNote.trim()) { + await actor.updateNote({ id: `note-${Date.now()}`, content: newNote }); setNewNote(""); } }; // Delete a note const deleteNote = (id: string) => { - if (worker) { - worker.deleteNote({ id }); + if (actor) { + actor.deleteNote({ id }); } }; // Listen for realtime updates - useWorkerEvent({ worker, event: "noteAdded" }, (note) => { + useActorEvent({ actor, event: "noteAdded" }, (note) => { setNotes(notes => [...notes, note]); }); - useWorkerEvent({ worker, event: "noteUpdated" }, (updatedNote) => { + useActorEvent({ actor, event: "noteUpdated" }, (updatedNote) => { setNotes(notes => notes.map(note => note.id === updatedNote.id ? updatedNote : note )); }); - useWorkerEvent({ worker, event: "noteDeleted" }, ({ id }) => { + useActorEvent({ actor, event: "noteDeleted" }, ({ id }) => { setNotes(notes => notes.filter(note => note.id !== id)); }); diff --git a/docs/snippets/examples/database-sqlite.mdx b/docs/snippets/examples/database-sqlite.mdx index 84d9ab000..83b594a79 100644 --- a/docs/snippets/examples/database-sqlite.mdx +++ b/docs/snippets/examples/database-sqlite.mdx @@ -1,13 +1,13 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { notes } from "./schema"; import { authenticate } from "./my-utils"; export type Note = { id: string; content: string; updatedAt: number }; -// User notes worker -const userNotes = worker({ +// User notes actor +const userNotes = actor({ sql: drizzle(), // Authenticate diff --git a/docs/snippets/examples/document-js.mdx b/docs/snippets/examples/document-js.mdx index 410601e27..546db5013 100644 --- a/docs/snippets/examples/document-js.mdx +++ b/docs/snippets/examples/document-js.mdx @@ -1,9 +1,9 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; export type Cursor = { x: number, y: number, userId: string }; -const document = worker({ +const document = actor({ state: { text: "", cursors: {} as Record, diff --git a/docs/snippets/examples/document-react.mdx b/docs/snippets/examples/document-react.mdx index b845b1868..42e862596 100644 --- a/docs/snippets/examples/document-react.mdx +++ b/docs/snippets/examples/document-react.mdx @@ -2,15 +2,15 @@ import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { App } from "../workers/app"; +import type { App } from "../actors/app"; const client = createClient("http://localhost:6420"); -const { useWorker, useWorkerEvent } = createReactRivetKit(client); +const { useActor, useActorEvent } = createReactRivetKit(client); export function DocumentEditor() { - // Connect to worker for this document ID from URL + // Connect to actor for this document ID from URL const documentId = new URLSearchParams(window.location.search).get('id') || 'default-doc'; - const [{ worker, connectionId }] = useWorker("document", { tags: { id: documentId } }); + const [{ actor, connectionId }] = useActor("document", { tags: { id: documentId } }); // Local state const [text, setText] = useState(""); @@ -19,18 +19,18 @@ export function DocumentEditor() { // Load initial document state useEffect(() => { - if (worker) { - worker.getText().then(setText); - worker.getCursors().then(setOtherCursors); + if (actor) { + actor.getText().then(setText); + actor.getCursors().then(setOtherCursors); } - }, [worker]); + }, [actor]); // Listen for updates from other users - useWorkerEvent({ worker, event: "textUpdated" }, ({ text: newText, userId: senderId }) => { + useActorEvent({ actor, event: "textUpdated" }, ({ text: newText, userId: senderId }) => { if (senderId !== connectionId) setText(newText); }); - useWorkerEvent({ worker, event: "cursorUpdated" }, ({ userId: cursorUserId, x, y }) => { + useActorEvent({ actor, event: "cursorUpdated" }, ({ userId: cursorUserId, x, y }) => { if (cursorUserId !== connectionId) { setOtherCursors(prev => ({ ...prev, @@ -41,14 +41,14 @@ export function DocumentEditor() { // Update cursor position const updateCursor = (e) => { - if (!worker) return; + if (!actor) return; const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; if (x !== cursorPos.x || y !== cursorPos.y) { setCursorPos({ x, y }); - worker.updateCursor(x, y); + actor.updateCursor(x, y); } }; @@ -62,7 +62,7 @@ export function DocumentEditor() { onChange={(e) => { const newText = e.target.value; setText(newText); - worker?.setText(newText); + actor?.setText(newText); }} placeholder="Start typing..." /> diff --git a/docs/snippets/examples/document-sqlite.mdx b/docs/snippets/examples/document-sqlite.mdx index 3ff14b120..15891bbdc 100644 --- a/docs/snippets/examples/document-sqlite.mdx +++ b/docs/snippets/examples/document-sqlite.mdx @@ -1,11 +1,11 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { documents, cursors } from "./schema"; export type Cursor = { x: number, y: number, userId: string }; -const document = worker({ +const document = actor({ sql: drizzle(), actions: { diff --git a/docs/snippets/examples/game-js.mdx b/docs/snippets/examples/game-js.mdx index 73887a79b..47a413f8d 100644 --- a/docs/snippets/examples/game-js.mdx +++ b/docs/snippets/examples/game-js.mdx @@ -1,11 +1,11 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; export type Position = { x: number; y: number }; export type Input = { x: number; y: number }; export type Player = { id: string; position: Position; input: Input }; -const gameRoom = worker({ +const gameRoom = actor({ state: { players: {} as Record, mapSize: 800 diff --git a/docs/snippets/examples/game-react.mdx b/docs/snippets/examples/game-react.mdx index cbbc97329..6767869ec 100644 --- a/docs/snippets/examples/game-react.mdx +++ b/docs/snippets/examples/game-react.mdx @@ -2,20 +2,20 @@ import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect, useRef } from "react"; -import type { Player } from "./worker"; +import type { Player } from "./actor"; const client = createClient("http://localhost:6420"); -const { useWorker, useWorkerEvent } = createReactRivetKit(client); +const { useActor, useActorEvent } = createReactRivetKit(client); export function MultiplayerGame() { - const [{ worker, connectionId }] = useWorker("gameRoom"); + const [{ actor, connectionId }] = useActor("gameRoom"); const [players, setPlayers] = useState([]); const canvasRef = useRef(null); const keysPressed = useRef>({}); // Set up game useEffect(() => { - if (!worker) return; + if (!actor) return; // Set up keyboard handlers const handleKeyDown = (e: KeyboardEvent) => { @@ -38,7 +38,7 @@ export function MultiplayerGame() { if (keysPressed.current["a"] || keysPressed.current["arrowleft"]) input.x = -1; if (keysPressed.current["d"] || keysPressed.current["arrowright"]) input.x = 1; - worker.setInput(input); + actor.setInput(input); }, 50); // Rendering loop @@ -71,10 +71,10 @@ export function MultiplayerGame() { clearInterval(inputInterval); cancelAnimationFrame(animationId); }; - }, [worker, connectionId, players]); + }, [actor, connectionId, players]); // Listen for world updates - useWorkerEvent({ worker, event: "worldUpdate" }, ({ players: updatedPlayers }) => { + useActorEvent({ actor, event: "worldUpdate" }, ({ players: updatedPlayers }) => { setPlayers(updatedPlayers); }); diff --git a/docs/snippets/examples/game-sqlite.mdx b/docs/snippets/examples/game-sqlite.mdx index c1f77b3f8..4b556e322 100644 --- a/docs/snippets/examples/game-sqlite.mdx +++ b/docs/snippets/examples/game-sqlite.mdx @@ -1,5 +1,5 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { players, gameSettings } from "./schema"; @@ -7,7 +7,7 @@ export type Position = { x: number; y: number }; export type Input = { x: number; y: number }; export type Player = { id: string; position: Position; input: Input }; -const gameRoom = worker({ +const gameRoom = actor({ sql: drizzle(), // Store game settings and player inputs in memory for performance diff --git a/docs/snippets/examples/rate-js.mdx b/docs/snippets/examples/rate-js.mdx index 906cea57f..ac2bd368d 100644 --- a/docs/snippets/examples/rate-js.mdx +++ b/docs/snippets/examples/rate-js.mdx @@ -1,8 +1,8 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; // Simple rate limiter - allows 5 requests per minute -const rateLimiter = worker({ +const rateLimiter = actor({ state: { count: 0, resetAt: 0 diff --git a/docs/snippets/examples/rate-react.mdx b/docs/snippets/examples/rate-react.mdx index 3a097c4b6..29d9af3c5 100644 --- a/docs/snippets/examples/rate-react.mdx +++ b/docs/snippets/examples/rate-react.mdx @@ -2,14 +2,14 @@ import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState } from "react"; -import type { App } from "../workers/app"; +import type { App } from "../actors/app"; const client = createClient("http://localhost:6420"); -const { useWorker } = createReactRivetKit(client); +const { useActor } = createReactRivetKit(client); export function RateLimiter() { // Connect to API rate limiter for user-123 - const [{ worker }] = useWorker("rateLimiter", { tags: { userId: "user-123" } }); + const [{ actor }] = useActor("rateLimiter", { tags: { userId: "user-123" } }); const [result, setResult] = useState<{ allowed: boolean; remaining: number; @@ -18,9 +18,9 @@ export function RateLimiter() { // Make a request const makeRequest = async () => { - if (!worker) return; + if (!actor) return; - const response = await worker.checkLimit(); + const response = await actor.checkLimit(); setResult(response); }; diff --git a/docs/snippets/examples/rate-sqlite.mdx b/docs/snippets/examples/rate-sqlite.mdx index 501a45e74..86af51aa7 100644 --- a/docs/snippets/examples/rate-sqlite.mdx +++ b/docs/snippets/examples/rate-sqlite.mdx @@ -1,10 +1,10 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { limiters } from "./schema"; // Simple rate limiter - allows 5 requests per minute -const rateLimiter = worker({ +const rateLimiter = actor({ sql: drizzle(), actions: { diff --git a/docs/snippets/examples/stream-js.mdx b/docs/snippets/examples/stream-js.mdx index a2ec818ca..8d773a3ec 100644 --- a/docs/snippets/examples/stream-js.mdx +++ b/docs/snippets/examples/stream-js.mdx @@ -1,12 +1,12 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; export type StreamState = { topValues: number[]; }; // Simple top-K stream processor example -const streamProcessor = worker({ +const streamProcessor = actor({ state: { topValues: [] as number[] }, diff --git a/docs/snippets/examples/stream-react.mdx b/docs/snippets/examples/stream-react.mdx index c2a1daedd..d2d860a5c 100644 --- a/docs/snippets/examples/stream-react.mdx +++ b/docs/snippets/examples/stream-react.mdx @@ -2,33 +2,33 @@ import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { App } from "../workers/app"; -import type { StreamState } from "./worker"; // Import shared types from worker +import type { App } from "../actors/app"; +import type { StreamState } from "./actor"; // Import shared types from actor const client = createClient("http://localhost:6420"); -const { useWorker, useWorkerEvent } = createReactRivetKit(client); +const { useActor, useActorEvent } = createReactRivetKit(client); export function StreamExample() { - const [{ worker }] = useWorker("streamProcessor"); + const [{ actor }] = useActor("streamProcessor"); const [topValues, setTopValues] = useState([]); const [newValue, setNewValue] = useState(0); // Load initial values useEffect(() => { - if (worker) { - worker.getTopValues().then(setTopValues); + if (actor) { + actor.getTopValues().then(setTopValues); } - }, [worker]); + }, [actor]); // Listen for updates from other clients - useWorkerEvent({ worker, event: "updated" }, ({ topValues }) => { + useActorEvent({ actor, event: "updated" }, ({ topValues }) => { setTopValues(topValues); }); // Add a new value to the stream const handleAddValue = () => { - if (worker) { - worker.addValue(newValue).then(setTopValues); + if (actor) { + actor.addValue(newValue).then(setTopValues); setNewValue(0); } }; diff --git a/docs/snippets/examples/stream-sqlite.mdx b/docs/snippets/examples/stream-sqlite.mdx index 699913ad4..5aa22c9e0 100644 --- a/docs/snippets/examples/stream-sqlite.mdx +++ b/docs/snippets/examples/stream-sqlite.mdx @@ -1,12 +1,12 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { streams, streamValues } from "./schema"; export type StreamState = { topValues: number[]; }; // Simple top-K stream processor example -const streamProcessor = worker({ +const streamProcessor = actor({ sql: drizzle(), actions: { diff --git a/docs/snippets/examples/sync-js.mdx b/docs/snippets/examples/sync-js.mdx index e785111d5..9dee02def 100644 --- a/docs/snippets/examples/sync-js.mdx +++ b/docs/snippets/examples/sync-js.mdx @@ -1,9 +1,9 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; export type Contact = { id: string; name: string; email: string; phone: string; updatedAt: number; } -const contacts = worker({ +const contacts = actor({ // State is automatically persisted state: { contacts: {} diff --git a/docs/snippets/examples/sync-react.mdx b/docs/snippets/examples/sync-react.mdx index 33f521e8b..8859d0c50 100644 --- a/docs/snippets/examples/sync-react.mdx +++ b/docs/snippets/examples/sync-react.mdx @@ -2,13 +2,13 @@ import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect, useRef } from "react"; -import type { Contact } from "./worker"; +import type { Contact } from "./actor"; const client = createClient("http://localhost:6420"); -const { useWorker, useWorkerEvent } = createReactRivetKit(client); +const { useActor, useActorEvent } = createReactRivetKit(client); export function ContactsApp() { - const { worker } = useWorker("contacts"); + const { actor } = useActor("contacts"); const [contacts, setContacts] = useState([]); const [name, setName] = useState(""); const [email, setEmail] = useState(""); @@ -19,17 +19,17 @@ export function ContactsApp() { // Load initial contacts useEffect(() => { - if (!worker) return; + if (!actor) return; - worker.getChanges(0).then(data => { + actor.getChanges(0).then(data => { setContacts(data.changes); lastSyncTime.current = data.timestamp; setSyncStatus("Synced"); }); - }, [worker]); + }, [actor]); // Handle contact events - useWorkerEvent({ worker, event: "contactsChanged" }, ({ contacts: updatedContacts }) => { + useActorEvent({ actor, event: "contactsChanged" }, ({ contacts: updatedContacts }) => { setContacts(prev => { const contactMap = new Map(prev.map(c => [c.id, c])); @@ -46,14 +46,14 @@ export function ContactsApp() { // Sync periodically useEffect(() => { - if (!worker) return; + if (!actor) return; const sync = async () => { setSyncStatus("Syncing..."); try { // Get remote changes - const changes = await worker.getChanges(lastSyncTime.current); + const changes = await actor.getChanges(lastSyncTime.current); // Apply remote changes if (changes.changes.length > 0) { @@ -74,7 +74,7 @@ export function ContactsApp() { // Push local changes const localChanges = contacts.filter(c => c.updatedAt > lastSyncTime.current); if (localChanges.length > 0) { - await worker.pushChanges(localChanges); + await actor.pushChanges(localChanges); } lastSyncTime.current = changes.timestamp; @@ -87,7 +87,7 @@ export function ContactsApp() { const intervalId = setInterval(sync, 5000); return () => clearInterval(intervalId); - }, [worker, contacts]); + }, [actor, contacts]); // Add new contact (local first) const addContact = () => { @@ -103,8 +103,8 @@ export function ContactsApp() { setContacts(prev => [...prev, newContact]); - if (worker) { - worker.pushChanges([newContact]); + if (actor) { + actor.pushChanges([newContact]); } setName(""); @@ -121,10 +121,10 @@ export function ContactsApp() { : c ); - if (worker) { + if (actor) { const deleted = updatedContacts.find(c => c.id === id); if (deleted) { - worker.pushChanges([deleted]); + actor.pushChanges([deleted]); } } @@ -134,16 +134,16 @@ export function ContactsApp() { // Manual sync const handleSync = async () => { - if (!worker) return; + if (!actor) return; setSyncStatus("Syncing..."); try { // Push all contacts - await worker.pushChanges(contacts); + await actor.pushChanges(contacts); // Get all changes - const changes = await worker.getChanges(0); + const changes = await actor.getChanges(0); setContacts(changes.changes); lastSyncTime.current = changes.timestamp; diff --git a/docs/snippets/examples/sync-sqlite.mdx b/docs/snippets/examples/sync-sqlite.mdx index 17fd0a5b4..a8dca3d88 100644 --- a/docs/snippets/examples/sync-sqlite.mdx +++ b/docs/snippets/examples/sync-sqlite.mdx @@ -1,11 +1,11 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { contacts } from "./schema"; export type Contact = { id: string; name: string; email: string; phone: string; updatedAt: number; } -const contactSync = worker({ +const contactSync = actor({ sql: drizzle(), actions: { diff --git a/docs/snippets/examples/tenant-js.mdx b/docs/snippets/examples/tenant-js.mdx index 15036fcfc..0e2aa32ba 100644 --- a/docs/snippets/examples/tenant-js.mdx +++ b/docs/snippets/examples/tenant-js.mdx @@ -1,9 +1,9 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { authenticate } from "./my-utils"; -// Simple tenant organization worker -const tenant = worker({ +// Simple tenant organization actor +const tenant = actor({ // Example initial state state: { members: [ diff --git a/docs/snippets/examples/tenant-react.mdx b/docs/snippets/examples/tenant-react.mdx index 7446fb67f..a2b09d33d 100644 --- a/docs/snippets/examples/tenant-react.mdx +++ b/docs/snippets/examples/tenant-react.mdx @@ -2,11 +2,11 @@ import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { App } from "../workers/app"; +import type { App } from "../actors/app"; // Create client and hooks const client = createClient("http://localhost:6420"); -const { useWorker } = createReactRivetKit(client); +const { useActor } = createReactRivetKit(client); export function OrgDashboard({ orgId }: { orgId: string }) { // State for data @@ -26,25 +26,25 @@ export function OrgDashboard({ orgId }: { orgId: string }) { // Authentication token const [token, setToken] = useState(""); - // Connect to tenant worker with authentication token - const [{ worker }] = useWorker("tenant", { + // Connect to tenant actor with authentication token + const [{ actor }] = useActor("tenant", { params: { token }, tags: { orgId } }); - // Load data when worker is available + // Load data when actor is available useEffect(() => { - if (!worker || !token) return; + if (!actor || !token) return; const loadData = async () => { try { // Get members (available to all users) - const membersList = await worker.getMembers(); + const membersList = await actor.getMembers(); setMembers(membersList); // Try to get invoices (only available to admins) try { - const invoicesList = await worker.getInvoices(); + const invoicesList = await actor.getInvoices(); setInvoices(invoicesList); setError(""); } catch (err: any) { @@ -56,7 +56,7 @@ export function OrgDashboard({ orgId }: { orgId: string }) { }; loadData(); - }, [worker, token]); + }, [actor, token]); // Login screen when not authenticated if (!token) { diff --git a/docs/snippets/examples/tenant-sqlite.mdx b/docs/snippets/examples/tenant-sqlite.mdx index 7bc563ad4..d4151cce5 100644 --- a/docs/snippets/examples/tenant-sqlite.mdx +++ b/docs/snippets/examples/tenant-sqlite.mdx @@ -1,11 +1,11 @@ ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { members, invoices } from "./schema"; import { authenticate } from "./my-utils"; -// Simple tenant organization worker -const tenant = worker({ +// Simple tenant organization actor +const tenant = actor({ sql: drizzle(), // Authentication diff --git a/docs/snippets/landing-comparison-table.mdx b/docs/snippets/landing-comparison-table.mdx index 686785c6f..4cac8baf8 100644 --- a/docs/snippets/landing-comparison-table.mdx +++ b/docs/snippets/landing-comparison-table.mdx @@ -204,7 +204,7 @@
- Define and call functions that interact with your workers + Define and call functions that interact with your actors
diff --git a/docs/snippets/landing-faq.mdx b/docs/snippets/landing-faq.mdx index e23b7314d..4d4131b33 100644 --- a/docs/snippets/landing-faq.mdx +++ b/docs/snippets/landing-faq.mdx @@ -9,7 +9,7 @@ import { Icon } from "@/components/Icon";
@@ -21,11 +21,11 @@ import { Icon } from "@/components/Icon";
-

Stateful serverless is very similar to workers: it's essentially workers with persistence, and usually doesn't have as rigid constraints on message handling. This makes it more flexible while maintaining the core benefits of the worker model.

+

Stateful serverless is very similar to actors: it's essentially actors with persistence, and usually doesn't have as rigid constraints on message handling. This makes it more flexible while maintaining the core benefits of the actor model.

@@ -78,7 +78,7 @@ import { Icon } from "@/components/Icon";
-

Yes, but only as much as storing data in a single database row does. We're working on building out read replicas to allow you to perform read-only actions on workers.

+

Yes, but only as much as storing data in a single database row does. We're working on building out read replicas to allow you to perform read-only actions on actors.

diff --git a/docs/snippets/landing-manifesto.mdx b/docs/snippets/landing-manifesto.mdx index d8f185826..68ba9d51b 100644 --- a/docs/snippets/landing-manifesto.mdx +++ b/docs/snippets/landing-manifesto.mdx @@ -4,7 +4,7 @@ Stateful serverless is the future of how applications will be architected.

- Startups increasingly build on stateful serverless to ship faster, achieve better performance, and outscale databases like Postgres. The worker model – closely related to stateful serverless – has an established history in frameworks like Elixir, Orleans, and Akka, though these typically involve steep learning curves and complex infrastructure. Cloudflare demonstrates the power of this approach, having built their entire infrastructure – including R2, Workflows, and Queues – on their stateful serverless engine called Durable Objects. + Startups increasingly build on stateful serverless to ship faster, achieve better performance, and outscale databases like Postgres. The actor model – closely related to stateful serverless – has an established history in frameworks like Elixir, Orleans, and Akka, though these typically involve steep learning curves and complex infrastructure. Cloudflare demonstrates the power of this approach, having built their entire infrastructure – including R2, Workflows, and Queues – on their stateful serverless engine called Durable Objects.

With years of experience in gaming infrastructure, we've seen firsthand how the stateful serverless model excels. After building numerous systems like matchmaking, chat, presence, and social networks using stateful serverless, we're convinced it's hands down the best way to build applications. However, the ecosystem lacks accessibility and resources. diff --git a/docs/snippets/landing-snippets.mdx b/docs/snippets/landing-snippets.mdx index f3ce0b059..c8066535a 100644 --- a/docs/snippets/landing-snippets.mdx +++ b/docs/snippets/landing-snippets.mdx @@ -102,7 +102,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";

- worker.ts + actor.ts Runs on the server
@@ -126,7 +126,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -151,7 +151,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -175,7 +175,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -200,7 +200,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -224,7 +224,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -249,7 +249,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -273,7 +273,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -298,7 +298,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -322,7 +322,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -347,7 +347,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -371,7 +371,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -396,7 +396,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -420,7 +420,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -445,7 +445,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -469,7 +469,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -494,7 +494,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -518,7 +518,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -543,7 +543,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
@@ -567,7 +567,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- worker.ts + actor.ts Runs on the server
diff --git a/docs/snippets/platform-extra-notes.mdx b/docs/snippets/platform-extra-notes.mdx index 7b2938e54..b88267b20 100644 --- a/docs/snippets/platform-extra-notes.mdx +++ b/docs/snippets/platform-extra-notes.mdx @@ -2,7 +2,7 @@ For security reasons, you should configure proper CORS settings in production. In the example above, we used `cors: { origin: "*" }` which allows requests from any domain. -For production deployments, specify the exact domains that should be allowed to connect to your workers. Learn more in the [CORS documentation](/concepts/cors). +For production deployments, specify the exact domains that should be allowed to connect to your actors. Learn more in the [CORS documentation](/concepts/cors). ## Integration with Existing Projects diff --git a/docs/snippets/setup-actor.mdx b/docs/snippets/setup-actor.mdx index e78ba7863..616e0755b 100644 --- a/docs/snippets/setup-actor.mdx +++ b/docs/snippets/setup-actor.mdx @@ -1,8 +1,8 @@ ```typescript src/app.ts -import { worker, setup } from "rivetkit"; +import { actor, setup } from "rivetkit"; -// Create worker -const counter = worker({ +// Create actor +const counter = actor({ state: { count: 0 }, actions: { increment: (c, x: number) => { @@ -15,7 +15,7 @@ const counter = worker({ // Create the application export const registry = setup({ - workers: { counter }, + actors: { counter }, cors: { origin: "http://localhost:8080" } }); diff --git a/docs/snippets/setup-next-steps.mdx b/docs/snippets/setup-next-steps.mdx index 1003f3cb7..03229781a 100644 --- a/docs/snippets/setup-next-steps.mdx +++ b/docs/snippets/setup-next-steps.mdx @@ -2,7 +2,7 @@ - + diff --git a/docs/snippets/step-define-actor.mdx b/docs/snippets/step-define-actor.mdx index 4241d157d..150dd8012 100644 --- a/docs/snippets/step-define-actor.mdx +++ b/docs/snippets/step-define-actor.mdx @@ -1,11 +1,11 @@ - - Create a file `workers/app.ts` in your project with your worker definition: + + Create a file `actors/app.ts` in your project with your actor definition: - ```typescript workers/app.ts - import { worker, setup } from "rivetkit"; + ```typescript actors/app.ts + import { actor, setup } from "rivetkit"; - // Create worker - const counter = worker({ + // Create actor + const counter = actor({ state: { count: 0 }, actions: { increment: (c, x: number) => { @@ -18,7 +18,7 @@ // Create the application export const registry = setup({ - workers: { counter }, + actors: { counter }, cors: { origin: "*" } // Configure CORS for your production domains in production }); diff --git a/docs/snippets/step-run-studio.mdx b/docs/snippets/step-run-studio.mdx index c06fc690f..38efd0d78 100644 --- a/docs/snippets/step-run-studio.mdx +++ b/docs/snippets/step-run-studio.mdx @@ -3,19 +3,19 @@ ```sh npm - npx rivetkit/cli@latest dev workers/app.ts + npx rivetkit/cli@latest dev actors/app.ts ``` ```sh pnpm - pnpm exec rivetkit/cli@latest dev workers/app.ts + pnpm exec rivetkit/cli@latest dev actors/app.ts ``` ```sh yarn - yarn rivetkit/cli@latest dev workers/app.ts + yarn rivetkit/cli@latest dev actors/app.ts ``` ```sh bun - bunx rivetkit/cli@latest dev workers/app.ts + bunx rivetkit/cli@latest dev actors/app.ts ``` diff --git a/docs/snippets/step-update-client.mdx b/docs/snippets/step-update-client.mdx index fb2e09db8..091c5463b 100644 --- a/docs/snippets/step-update-client.mdx +++ b/docs/snippets/step-update-client.mdx @@ -12,7 +12,7 @@ ``` ```python Python - client = WorkerClient( + client = ActorClient( # FILL ME IN ) ``` diff --git a/docs/styles/cta.js b/docs/styles/cta.js index 6c4dbcd58..b6bf9f7a3 100644 --- a/docs/styles/cta.js +++ b/docs/styles/cta.js @@ -1,22 +1,22 @@ // CTA titles array const CTA_TITLES = [ - "Performance in every act - thanks to Rivet Workers.", - "Scale without drama - only with Rivet Workers.", - "It's time your backend took center-stage - with Rivet Workers.", - "SQLite the spotlight on performance - with Rivet Workers.", - "Backend scalability: the SQL - starring Rivet Workers.", - "Take your state to the edge - Rivet Workers makes it easy.", - "No state fright - just scalability with Rivet Workers.", - "Act now, deploy at the edge - with Rivet Workers.", - "Lights, camera, serverless - powered by Rivet Workers.", - "Your backend deserves a standing ovation - Rivet Workers delivers.", - "Cue your backend's best performance - enter Rivet Workers.", - "Backend performance worth applauding - only with Rivet Workers.", - "Put your backend center-stage - with Rivet Workers.", - "Make your backend the main worker - with Rivet Workers.", - "Give your backend its big break - use Rivet Workers.", - "Serverless, with no intermissions - powered by Rivet Workers.", - "Set the stage for serverless success - with Rivet Workers." + "Performance in every act - thanks to Rivet Actors.", + "Scale without drama - only with Rivet Actors.", + "It's time your backend took center-stage - with Rivet Actors.", + "SQLite the spotlight on performance - with Rivet Actors.", + "Backend scalability: the SQL - starring Rivet Actors.", + "Take your state to the edge - Rivet Actors makes it easy.", + "No state fright - just scalability with Rivet Actors.", + "Act now, deploy at the edge - with Rivet Actors.", + "Lights, camera, serverless - powered by Rivet Actors.", + "Your backend deserves a standing ovation - Rivet Actors delivers.", + "Cue your backend's best performance - enter Rivet Actors.", + "Backend performance worth applauding - only with Rivet Actors.", + "Put your backend center-stage - with Rivet Actors.", + "Make your backend the main actor - with Rivet Actors.", + "Give your backend its big break - use Rivet Actors.", + "Serverless, with no intermissions - powered by Rivet Actors.", + "Set the stage for serverless success - with Rivet Actors." ]; function initializeAllCTAs() { diff --git a/docs/styles/particles.js b/docs/styles/particles.js index a0c1a9810..edf9a925b 100644 --- a/docs/styles/particles.js +++ b/docs/styles/particles.js @@ -190,7 +190,7 @@ // // Size and opacity // this.size = Particle.PARTICLE_RADIUS * 2; // this.opacity = 1; -// this.opacityFworker = Math.random(); +// this.opacityFactor = Math.random(); // this.opacityBase = Math.min(0, Math.random() - 0.5); // } // @@ -220,7 +220,7 @@ // // Fade out between 600px and 1200px from center // const minDistance = 600; // const maxDistance = 1200; -// this.opacity = this.opacityBase + Math.max(0, Math.min(1, 1 - (distance - minDistance) / (maxDistance - minDistance))) * this.opacityFworker; +// this.opacity = this.opacityBase + Math.max(0, Math.min(1, 1 - (distance - minDistance) / (maxDistance - minDistance))) * this.opacityFactor; // // // Calculate velocity magnitude directly - simpler and more accurate // const velocityMagnitude = Math.sqrt(this.vx * this.vx + this.vy * this.vy); @@ -454,8 +454,8 @@ // // Normalize mouse velocity to 0-1 range // const normalizedSpeed = Math.min(mouseSpeed / PARTICLE_CONFIG.MAX_VELOCITY, 1); // -// // Calculate base force fworker with smoother distance falloff -// const forceFworker = Math.pow(1 - distance / FORCE_RADIUS, 1.5) * +// // Calculate base force factor with smoother distance falloff +// const forceFactor = Math.pow(1 - distance / FORCE_RADIUS, 1.5) * // normalizedSpeed * // PARTICLE_CONFIG.BASE_PUSH_FORCE * // PARTICLE_CONFIG.MOVEMENT_FORCE_MULTIPLIER * @@ -477,8 +477,8 @@ // const moveForce = mouseSpeed * PARTICLE_CONFIG.MOVEMENT_FORCE_RATIO; // // // More emphasis on movement direction, less on repulsion -// const fx = (repelDirX * repelForce * 0.5 + mvx * moveForce) * forceFworker * movementInfluence; -// const fy = (repelDirY * repelForce * 0.5 + mvy * moveForce) * forceFworker * movementInfluence; +// const fx = (repelDirX * repelForce * 0.5 + mvx * moveForce) * forceFactor * movementInfluence; +// const fy = (repelDirY * repelForce * 0.5 + mvy * moveForce) * forceFactor * movementInfluence; // // particle.applyForce(fx, fy); // } diff --git a/docs/workers/actions.mdx b/docs/workers/actions.mdx index 04fd2c7b3..5c764b660 100644 --- a/docs/workers/actions.mdx +++ b/docs/workers/actions.mdx @@ -3,23 +3,23 @@ title: Actions icon: bolt --- -Actions are how clients & other workers communicate with workers. Actions are defined as functions in the worker configuration and can be called from clients. +Actions are how clients & other actors communicate with actors. Actions are defined as functions in the actor configuration and can be called from clients. **Performance** Actions are very lightweight. They can be called hundreds of times per second to send realtime data to the -worker. +actor. ## Writing Actions -Actions are defined in the `actions` object when creating a worker: +Actions are defined in the `actions` object when creating a actor: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const mathUtils = worker({ +const mathUtils = actor({ state: {}, actions: { // This is an action @@ -37,14 +37,14 @@ Each action receives a context object (commonly named `c`) as its first paramete You can define helper functions outside the actions object to keep your code organized. These functions cannot be called directly by clients: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; // Private helper function - not callable by clients const calculateFee = (amount) => { return amount * 0.05; }; -const paymentProcessor = worker({ +const paymentProcessor = actor({ state: { transactions: [] }, @@ -84,7 +84,7 @@ Calling actions from the client are async and require an `await`, even if the ac ### Type Safety -The worker client includes type safety out of the box. When you use `createClient()`, TypeScript automatically infers action parameter and return types: +The actor client includes type safety out of the box. When you use `createClient()`, TypeScript automatically infers action parameter and return types: @@ -92,7 +92,7 @@ The worker client includes type safety out of the box. When you use `createClien import { setup } from "rivetkit"; // Create simple counter -const counter = worker({ +const counter = actor({ state: { count: 0 }, actions: { increment: (c, count: number) => { @@ -104,7 +104,7 @@ const counter = worker({ // Create and export the app const registry = setup({ - workers: { counter } + actors: { counter } }); export type Registry = typeof registry; @@ -127,7 +127,7 @@ await counter.nonexistentMethod(123); // TypeScript error ## Error Handling -Workers provide robust error handling out of the box for actions. +Actors provide robust error handling out of the box for actions. ### User Errors @@ -141,10 +141,10 @@ For example: -```typescript worker.ts -import { worker, UserError } from "rivetkit"; +```typescript actor.ts +import { actor, UserError } from "rivetkit"; -const user = worker({ +const user = actor({ state: { users: [] }, actions: { registerUser: (c, username) => { @@ -167,7 +167,7 @@ const user = worker({ ```typescript client.ts try { - await userWorker.registerUser("extremely_long_username_that_exceeds_limit"); + await userActor.registerUser("extremely_long_username_that_exceeds_limit"); } catch (error) { console.log("Message", error.message); // "Invalid username" console.log("Code", error.code); // "invalid_username" @@ -177,7 +177,7 @@ try { -{/* Read the documentation for `UserError` [here](https://jsr.io/@rivet-gg/worker/doc/~/UserError). */} +{/* Read the documentation for `UserError` [here](https://jsr.io/@rivet-gg/actor/doc/~/UserError). */} ### Internal Errors @@ -190,7 +190,7 @@ Data schemas are not validated by default. For production applications, use a li For example, to validate action parameters: ```typescript -import { worker, UserError } from "rivetkit"; +import { actor, UserError } from "rivetkit"; import { z } from "zod"; // Define schema for action parameters @@ -198,7 +198,7 @@ const IncrementSchema = z.object({ count: z.number().int().positive() }); -const counter = worker({ +const counter = actor({ state: { count: 0 }, actions: { increment: (c, params) => { @@ -224,7 +224,7 @@ const counter = worker({ ## Authentication -By default, clients can call all actions on a worker without restriction. Make sure to implement authentication if needed. Documentation on authentication is available [here](/concepts/authentication). +By default, clients can call all actions on a actor without restriction. Make sure to implement authentication if needed. Documentation on authentication is available [here](/concepts/authentication). ## Using `ActionContext` Type Externally @@ -233,9 +233,9 @@ When writing complex logic for actions, you may want to extract parts of your im RivetKit provides the `ActionContextOf` utility type for exactly this purpose: ```typescript -import { worker, ActionContextOf } from "rivetkit"; +import { actor, ActionContextOf } from "rivetkit"; -const counter = worker({ +const counter = actor({ state: { count: 0 }, actions: { diff --git a/docs/workers/authentication.mdx b/docs/workers/authentication.mdx index 5481ddeff..83927b854 100644 --- a/docs/workers/authentication.mdx +++ b/docs/workers/authentication.mdx @@ -3,22 +3,22 @@ title: Authentication icon: fingerprint --- -Authentication can be handled through the `onBeforeConnect` or `createConnState` lifecycle hook, which acts as middleware before allowing clients to interact with your worker. +Authentication can be handled through the `onBeforeConnect` or `createConnState` lifecycle hook, which acts as middleware before allowing clients to interact with your actor. ## Using `onBeforeConnect` or `createConnState` -The `onBeforeConnect` and `createConnState` hook is called whenever a new client attempts to connect to your worker. It receives a context object that contains the client's connection parameters. `createConnState` should return an object that will become the connection state. +The `onBeforeConnect` and `createConnState` hook is called whenever a new client attempts to connect to your actor. It receives a context object that contains the client's connection parameters. `createConnState` should return an object that will become the connection state. Throwing an error in `onBeforeConnect` or `createConnState` will abort the connection. Here's a basic example: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const exampleWorker = worker({ +const exampleActor = actor({ state: { - // Worker state... + // Actor state... }, createConnState: async (c, { params }) => { @@ -36,7 +36,7 @@ const exampleWorker = worker({ }, actions: { - // Worker actions... + // Actor actions... } }); ``` @@ -46,11 +46,11 @@ const exampleWorker = worker({ After authentication, you can access the connection state in any action through the context object: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const authenticatedWorker = worker({ +const authenticatedActor = actor({ state: { - // Worker state... + // Actor state... }, createConnState: (c) => { @@ -80,11 +80,11 @@ const authenticatedWorker = worker({ ### With API Server Authentication ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const apiAuthenticatedWorker = worker({ +const apiAuthenticatedActor = actor({ state: { - // Worker state... + // Actor state... }, createConnState: async (c, { params }) => { @@ -108,7 +108,7 @@ const apiAuthenticatedWorker = worker({ }, actions: { - // Worker actions... + // Actor actions... } }); ``` @@ -118,14 +118,14 @@ When authentication fails, throwing an error in `createConnState` will prevent t ### With JWT Authentication ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import jwt from "jsonwebtoken"; const JWT_SECRET = process.env.JWT_SECRET; -const jwtAuthenticatedWorker = worker({ +const jwtAuthenticatedActor = actor({ state: { - // Worker state... + // Actor state... }, createConnState: (c, { params }) => { diff --git a/docs/workers/connections.mdx b/docs/workers/connections.mdx index 17cfc539c..92cd30b84 100644 --- a/docs/workers/connections.mdx +++ b/docs/workers/connections.mdx @@ -3,20 +3,20 @@ title: Connections icon: network-wired --- -Connections represent client connections to your worker. They provide a way to handle client authentication, manage connection-specific data, and control the connection lifecycle. +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 a worker, they can pass connection parameters that are handled during the connection process. +When clients connect to a actor, they can pass connection parameters that are handled during the connection process. For example: -```typescript worker.ts -import { worker } from "rivetkit"; +```typescript actor.ts +import { actor } from "rivetkit"; -const gameRoom = worker({ +const gameRoom = actor({ state: {}, // Handle connection setup @@ -52,14 +52,14 @@ const gameRoom = await client.gameRoom.get({ ## Connection State -There are two ways to define a worker's connection state: +There are two ways to define a actor's connection state: ### Method 1: `ConnState` constant ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ state: { messages: [] }, // Define default connection state as a constant @@ -84,9 +84,9 @@ const chatRoom = worker({ The data returned from `createConnState` is used as the initial state of the connection. The connection state can be accessed through `conn.state`. ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ state: { messages: [] }, // Create connection state dynamically @@ -120,7 +120,7 @@ The connection lifecycle has several hooks: - `onConnect`: Called when a client successfully connects - `onDisconnect`: Called when a client disconnects -See the documentation on [Worker Lifecycle](/concepts/lifecycle) for more details. +See the documentation on [Actor Lifecycle](/concepts/lifecycle) for more details. ## Connection List @@ -131,9 +131,9 @@ This is frequently used with `conn.send(name, event)` to send messages directly For example: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ state: { users: {} }, actions: { @@ -158,9 +158,9 @@ const chatRoom = worker({ Connections can be disconnected from within an action: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const secureRoom = worker({ +const secureRoom = actor({ state: {}, actions: { @@ -189,4 +189,4 @@ This ensures the underlying network connections close cleanly before continuing. ## Offline & Auto-Reconnection -See [Interacting with Workers](/concepts/interacting-with-workers#offline-and-auto-reconnection) for details on reconnection behavior. +See [Interacting with Actors](/concepts/interacting-with-actors#offline-and-auto-reconnection) for details on reconnection behavior. diff --git a/docs/workers/events.mdx b/docs/workers/events.mdx index bb1f65339..6a9572001 100644 --- a/docs/workers/events.mdx +++ b/docs/workers/events.mdx @@ -3,24 +3,24 @@ title: Events icon: tower-broadcast --- -Events are used for clients to receive realtime data from workers. +Events are used for clients to receive realtime data from actors. -Events are used for workers to publish updates to clients. Clients call actions to communicate with the worker. +Events are used for actors to publish updates to clients. Clients call actions to communicate with the actor. -## Publishing from workers +## Publishing from actors -Workers can publish events to clients using `c.broadcast` and `conn.send`. +Actors can publish events to clients using `c.broadcast` and `conn.send`. ### Broadcasting events -Workers can publish events to all connected clients with `c.broadcast(name, data)`. For example: +Actors can publish events to all connected clients with `c.broadcast(name, data)`. For example: ```typescript chat_room.ts -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ state: {}, actions: { sendMessage: (c, message) => { @@ -43,14 +43,14 @@ await chatRoom.sendMessage('Hello, world!'); ### Sending events to specific connections -Workers can send messages to specific client connections. All connections are available through the context object. For example: +Actors can send messages to specific client connections. All connections are available through the context object. For example: ```typescript chat_room.ts -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ state: {}, actions: { sendPrivateMessage: (c, connId, message) => { @@ -76,13 +76,13 @@ await chatRoom.sendPrivateMessage(123, 'Hello, world!'); ## Subscribing from clients -Clients can subscribe to events from workers using `on` and `once`. +Clients can subscribe to events from actors using `on` and `once`. ### `on(eventName, callback)` -{/* [Documentation](https://jsr.io/@rivet-gg/worker-client/doc/~/WorkerHandleRaw.prototype.on.html) */} +{/* [Documentation](https://jsr.io/@rivet-gg/actor-client/doc/~/ActorHandleRaw.prototype.on.html) */} -Clients can subscribe to events that will happen repeatedly using `worker.on(name, callback)`. For example: +Clients can subscribe to events that will happen repeatedly using `actor.on(name, callback)`. For example: @@ -99,9 +99,9 @@ chatRoom.on('newMessage', ({ message }) => { ``` ```typescript chat_room.ts -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ state: {}, actions: { sendMessage: (c, message) => { @@ -115,9 +115,9 @@ const chatRoom = worker({ ### `once(eventName, callback)` -{/* [Documentation](https://jsr.io/@rivet-gg/worker-client/doc/~/WorkerHandleRaw.prototype.once.html) */} +{/* [Documentation](https://jsr.io/@rivet-gg/actor-client/doc/~/ActorHandleRaw.prototype.once.html) */} -Clients can listen for an event only one time with `worker.once(name, callback)`. For example: +Clients can listen for an event only one time with `actor.once(name, callback)`. For example: @@ -137,9 +137,9 @@ await chatRoom.requestJoin(); ``` ```typescript chat_room.ts -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ state: { pendingJoinRequests: [] }, @@ -161,6 +161,6 @@ const chatRoom = worker({ ## Connections -Connections are used to communicate with clients from the worker. +Connections are used to communicate with clients from the actor. Read more about connections [here](/concepts/connections). diff --git a/docs/workers/lifecycle.mdx b/docs/workers/lifecycle.mdx index 9b20d9171..6ae357535 100644 --- a/docs/workers/lifecycle.mdx +++ b/docs/workers/lifecycle.mdx @@ -5,32 +5,32 @@ icon: rotate ## Lifecycle Hooks -Worker lifecycle hooks are defined as functions in the worker configuration. +Actor lifecycle hooks are defined as functions in the actor configuration. ### `createState` and `state` -{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._onInitialize) */} +{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onInitialize) */} -The `createState` function or `state` constant defines the initial state of the worker (see [state documentation](/concepts/state)). The `createState` function is called only once when the worker is first created. +The `createState` function or `state` constant defines the initial state of the actor (see [state documentation](/concepts/state)). The `createState` function is called only once when the actor is first created. ### `createVars` and `vars` -The `createVars` function or `vars` constant defines ephemeral variables for the worker (see [state documentation](/concepts/state)). These variables are not persisted and are useful for storing runtime-only objects or temporary data. +The `createVars` function or `vars` constant defines ephemeral variables for the actor (see [state documentation](/concepts/state)). These variables are not persisted and are useful for storing runtime-only objects or temporary data. The `createVars` function can also receive driver-specific context as its second parameter, allowing access to driver capabilities like Rivet KV or Cloudflare Durable Object storage. ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; // Using vars constant -const counter1 = worker({ +const counter1 = actor({ state: { count: 0 }, vars: { lastAccessTime: 0 }, actions: { /* ... */ } }); // Using createVars function -const counter2 = worker({ +const counter2 = actor({ state: { count: 0 }, createVars: () => { // Initialize with non-serializable objects @@ -43,7 +43,7 @@ const counter2 = worker({ }); // Access driver-specific context -const exampleWorker = worker({ +const exampleActor = actor({ state: { count: 0 }, // Access driver context in createVars createVars: (c, rivet) => ({ @@ -63,16 +63,16 @@ const exampleWorker = worker({ The `onCreate` hook is called at the same time as `createState`, but unlike `createState`, it doesn't return any value. Use this hook for initialization logic that doesn't affect the initial state. ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; // Using state constant -const counter1 = worker({ +const counter1 = actor({ state: { count: 0 }, actions: { /* ... */ } }); // Using createState function -const counter2 = worker({ +const counter2 = actor({ createState: () => { // Initialize with a count of 0 return { count: 0 }; @@ -81,12 +81,12 @@ const counter2 = worker({ }); // Using onCreate -const counter3 = worker({ +const counter3 = actor({ state: { count: 0 }, // Run initialization logic (logging, external service setup, etc.) onCreate: (c) => { - console.log("Counter worker initialized"); + console.log("Counter actor initialized"); // Can perform async operations or setup // No need to return anything }, @@ -97,22 +97,22 @@ const counter3 = worker({ ### `onStart` -{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._onStart) */} +{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onStart) */} -This hook is called any time the worker is started (e.g. after restarting, upgrading code, or crashing). +This hook is called any time the actor is started (e.g. after restarting, upgrading code, or crashing). -This is called after the worker has been initialized but before any connections are accepted. +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 -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const counter = worker({ +const counter = actor({ state: { count: 0 }, onStart: (c) => { - console.log('Worker started with count:', c.state.count); + console.log('Actor started with count:', c.state.count); // Set up interval for automatic counting const intervalId = setInterval(() => { @@ -130,14 +130,14 @@ const counter = worker({ ### `onStateChange` -{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._onStateChange) */} +{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onStateChange) */} -Called whenever the worker's state changes. This is often used to broadcast state updates. +Called whenever the actor's state changes. This is often used to broadcast state updates. ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const counter = worker({ +const counter = actor({ state: { count: 0 }, onStateChange: (c, newState) => { @@ -158,7 +158,7 @@ const counter = worker({ ### `createConnState` and `connState` -{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._createConnState) */} +{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._createConnState) */} 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 @@ -166,16 +166,16 @@ There are two ways to define the initial state for connections: ### `onBeforeConnect` -{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._onBeforeConnect) */} +{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onBeforeConnect) */} -The `onBeforeConnect` hook is called whenever a new client connects to the worker. 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 is called whenever a new client connects to the actor. Clients can pass parameters when connecting, accessible via `params`. This hook is used for connection validation and can throw errors to reject connections. The `onBeforeConnect` hook does NOT return connection state - it's used solely for validation. ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ state: { messages: [] }, // Method 1: Use a static default connection state @@ -209,18 +209,18 @@ const chatRoom = worker({ }); ``` -Connections cannot interact with the worker until this method completes successfully. Throwing an error will abort the connection. This can be used for authentication - see [Authentication](/concepts/authentication) for details. +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` -{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._onConnect) */} +{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onConnect) */} Executed after the client has successfully connected. ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ state: { users: {}, messages: [] }, onConnect: (c) => { @@ -241,18 +241,18 @@ const chatRoom = worker({ }); ``` -Messages will not be processed for this worker until this hook succeeds. Errors thrown from this hook will cause the client to disconnect. +Messages will not be processed for this actor until this hook succeeds. Errors thrown from this hook will cause the client to disconnect. ### `onDisconnect` -{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._onDisconnect) */} +{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onDisconnect) */} -Called when a client disconnects from the worker. Use this to clean up any connection-specific resources. +Called when a client disconnects from the actor. Use this to clean up any connection-specific resources. ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ state: { users: {}, messages: [] }, onDisconnect: (c) => { @@ -273,14 +273,14 @@ const chatRoom = worker({ }); ``` -## Destroying Workers +## Destroying Actors -Workers can be shut down gracefully with `c.shutdown()`. Clients will be gracefully disconnected. +Actors can be shut down gracefully with `c.shutdown()`. Clients will be gracefully disconnected. ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const temporaryRoom = worker({ +const temporaryRoom = actor({ state: { createdAt: 0, expiresAfterMs: 3600000 // 1 hour @@ -314,7 +314,7 @@ const temporaryRoom = worker({ // Notify all clients c.broadcast("roomClosed", { reason: "Admin closed the room" }); - // Shutdown the worker + // Shutdown the actor c.shutdown(); } } @@ -323,42 +323,42 @@ const temporaryRoom = worker({ This action is permanent and cannot be reverted. -## Using `WorkerContext` Type Externally +## 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. RivetKit provides helper types that make it easy to extract and pass these context types to external functions. ```typescript -import { worker, WorkerContextOf } from "rivetkit"; +import { actor, ActorContextOf } from "rivetkit"; -const myWorker = worker({ +const myActor = actor({ state: { count: 0 }, // Use external function in lifecycle hook - onStart: (c) => logWorkerStarted(c) + onStart: (c) => logActorStarted(c) }); // Simple external function with typed context -function logWorkerStarted(c: WorkerContextOf) { - console.log(`Worker started with count: ${c.state.count}`); +function logActorStarted(c: ActorContextOf) { + console.log(`Actor started with count: ${c.state.count}`); } ``` -See [Helper Types](/concepts/types) for more details on using `WorkerContextOf`. +See [Helper Types](/concepts/types) for more details on using `ActorContextOf`. ## Full Example ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const counter = worker({ +const counter = actor({ // Initialize state createState: () => ({ count: 0 }), - // Initialize worker (run setup that doesn't affect initial state) + // Initialize actor (run setup that doesn't affect initial state) onCreate: (c) => { - console.log('Counter worker initialized'); + console.log('Counter actor initialized'); // Set up external resources, etc. }, diff --git a/docs/workers/metadata.mdx b/docs/workers/metadata.mdx index 4df0ecfe0..fb5e37a01 100644 --- a/docs/workers/metadata.mdx +++ b/docs/workers/metadata.mdx @@ -3,7 +3,7 @@ title: Metadata icon: tag --- -Metadata provides information about the currently running worker. +Metadata provides information about the currently running actor. ## Region @@ -20,9 +20,9 @@ For example: ```typescript chat_room.ts -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ state: { messages: [] }, @@ -74,12 +74,12 @@ const teamChannel = await client.chatRoom.get({ -## Worker Name +## Actor Name -You can access the worker name with: +You can access the actor name with: ```typescript -const workerName = c.name; +const actorName = c.name; ``` -This is useful when you need to know which worker type is running, especially if you have generic utility functions that are shared between different worker implementations. +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. diff --git a/docs/workers/overview.mdx b/docs/workers/overview.mdx index 50b0a61c5..8c88b6015 100644 --- a/docs/workers/overview.mdx +++ b/docs/workers/overview.mdx @@ -1,13 +1,13 @@ --- -title: Rivet Workers +title: Rivet Actors icon: square-info sidebarTitle: "Overview" description: A library for building stateful, scalable, realtime backend applications. --- -import CreateWorkerCli from "/snippets/create-worker-cli.mdx"; +import CreateActorCli from "/snippets/create-actor-cli.mdx"; -Workers combine compute and storage into unified entities for simplified architecture. Workers seamlessly integrate with your existing infrastructure or can serve as a complete standalone solution. +Actors combine compute and storage into unified entities for simplified architecture. Actors seamlessly integrate with your existing infrastructure or can serve as a complete standalone solution. ## Quickstart @@ -18,31 +18,31 @@ Workers combine compute and storage into unified entities for simplified archite ## Concepts -The core concepts that power Rivet Worker applications: +The core concepts that power Rivet Actor applications: - **State Is Automatically Persisted**: State automatically persists between restarts, upgrades, & crashes - **State Is Stored In-Memory**: State is stored in memory for high-performance reads/writes while also automatically persisted -- **Isolated State Ownership**: Workers only manage their own state, which can only be modified by the worker itself -- **Communicates via Actions**: How clients and other workers interact with a worker +- **Isolated State Ownership**: Actors only manage their own state, which can only be modified by the actor itself +- **Communicates via Actions**: How clients and other actors interact with a actor - **Actions Are Low-Latency**: Actions provide WebSocket-like performance for time-sensitive operations -- **Broadcast Updates With Events**: Workers can publish real-time updates to connected clients +- **Broadcast Updates With Events**: Actors can publish real-time updates to connected clients ## Quickstart Run this to get started: - + ## Code Example -Here's a complete chat room worker that maintains state and handles messages. We'll explore each component in depth throughout this document: +Here's a complete chat room actor that maintains state and handles messages. We'll explore each component in depth throughout this document: ```typescript chat_room.ts -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -// Define a chat room worker -const chatRoom = worker({ - // Initialize state when the worker is first created +// Define a chat room actor +const chatRoom = actor({ + // Initialize state when the actor is first created createState: () => ({ messages: [] }), @@ -70,7 +70,7 @@ export default chatRoom; ## Using the App -To start using your worker, create an app and serve it: +To start using your actor, create an app and serve it: ```typescript app.ts import { setup, serve } from "rivetkit"; @@ -78,7 +78,7 @@ import chatRoom from "./chat_room"; // Create the application const registry = setup({ - workers: { chatRoom } + actors: { chatRoom } }); // Start serving on default port @@ -88,17 +88,17 @@ serve(registry); export type Registry = typeof registry; ``` -## Key Worker Components +## Key Actor Components ### State -Workers maintain state that's stored in memory and automatically persisted. State is defined either as a constant or via a `createState` function: +Actors maintain state that's stored in memory and automatically persisted. State is defined either as a constant or via a `createState` function: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; // Method 1: State constant -const counter1 = worker({ +const counter1 = actor({ state: { count: 0 }, actions: { // ... @@ -106,7 +106,7 @@ const counter1 = worker({ }); // Method 2: CreateState function -const counter2 = worker({ +const counter2 = actor({ createState: () => ({ count: 0 }), actions: { // ... @@ -117,9 +117,9 @@ const counter2 = worker({ Update state by modifying `c.state` in your actions: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const counter = worker({ +const counter = actor({ state: { count: 0 }, actions: { // Example of state update in an action @@ -133,16 +133,16 @@ const counter = worker({ These changes are durable and are automatically persisted across updates, restarts, and crashes. -Learn more about [state management](/worker/state). +Learn more about [state management](/actor/state). ### Actions -Actions are functions defined in your worker configuration that clients & other workers can call: +Actions are functions defined in your actor configuration that clients & other actors can call: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const mathUtils = worker({ +const mathUtils = actor({ state: {}, actions: { multiplyByTwo: (c, x) => { @@ -154,16 +154,16 @@ const mathUtils = worker({ Each action receives a context object (commonly named `c`) as its first parameter, which provides access to state, connections, and other utilities. -Learn more about [actions](/worker/actions). +Learn more about [actions](/actor/actions). ### Events -Workers can broadcast events to connected clients: +Actors can broadcast events to connected clients: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const inventory = worker({ +const inventory = actor({ createState: () => ({ items: [] }), @@ -183,9 +183,9 @@ const inventory = worker({ You can also send events to specific clients: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const messageService = worker({ +const messageService = actor({ state: {}, actions: { sendPrivateMessage: (c, userId, text) => { @@ -199,14 +199,14 @@ const messageService = worker({ }); ``` -Learn more about [events](/worker/events). +Learn more about [events](/actor/events). -## Worker Tags +## Actor Tags -Tags are key-value pairs attached to workers that serve two purposes: +Tags are key-value pairs attached to actors that serve two purposes: -1. **Worker Discovery**: Find specific workers using `client.get(tags)` -2. **Organization**: Group related workers for management purposes +1. **Actor Discovery**: Find specific actors using `client.get(tags)` +2. **Organization**: Group related actors for management purposes For example, you can query chat rooms by tag like: @@ -235,16 +235,16 @@ const document = await client.document.get({ }); ``` -## Worker Lifecycle +## Actor Lifecycle -Workers are created automatically when needed and persist until explicitly shutdown. +Actors are created automatically when needed and persist until explicitly shutdown. -To shut down a worker, use `c.shutdown()` from within an action: +To shut down a actor, use `c.shutdown()` from within an action: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ createState: () => ({ messages: [] }), @@ -253,36 +253,36 @@ const chatRoom = worker({ // Do any cleanup needed c.broadcast("roomClosed"); - // Shutdown the worker + // Shutdown the actor c.shutdown(); } } }); ``` -Learn more about the [worker lifecycle](/worker/lifecycle). +Learn more about the [actor lifecycle](/actor/lifecycle). ## Documentation -Learn more about Rivet Workers: +Learn more about Rivet Actors: - - Get started with Rivet Workers in minutes + + Get started with Rivet Actors in minutes - - Understand how worker state is managed, persisted, and accessed. + + Understand how actor state is managed, persisted, and accessed. - - Define and implement worker actions (RPCs) for client interaction. + + Define and implement actor actions (RPCs) for client interaction. - + Real-time communication with events and broadcasts. - - Managing the creation, execution, and termination of workers. + + Managing the creation, execution, and termination of actors. - - Schedule tasks and alarms with workers for time-based operations. + + Schedule tasks and alarms with actors for time-based operations. diff --git a/docs/workers/quickstart-frontend.mdx b/docs/workers/quickstart-frontend.mdx index f0e98d32b..e174780b2 100644 --- a/docs/workers/quickstart-frontend.mdx +++ b/docs/workers/quickstart-frontend.mdx @@ -11,12 +11,12 @@ npm install rivetkit ``` - + ```ts registry.ts -import { worker, setup } from "rivetkit"; +import { actor, setup } from "rivetkit"; -export const counter = worker({ +export const counter = actor({ state: { count: 0 }, actions: { increment: (c, x: number) => { @@ -27,7 +27,7 @@ export const counter = worker({ }); export const registry = setup({ - workers: { counter }, + actors: { counter }, }); ``` @@ -53,13 +53,13 @@ import { createClient, createRivetKit } from "@rivetkit/react"; import type { Registry } from "../backend/registry"; const client = createClient(`http://localhost:6420/registry`); -const { useWorker } = createRivetKit(client); +const { useActor } = createRivetKit(client); function App() { const [count, setCount] = useState(0); const [counterName, setCounterName] = useState("test-counter"); - const counter = useWorker({ + const counter = useActor({ name: "counter", key: [counterName], }); @@ -133,7 +133,7 @@ TODO: See backend - + diff --git a/docs/workers/quickstart.mdx b/docs/workers/quickstart.mdx index 04be06d7c..84394b223 100644 --- a/docs/workers/quickstart.mdx +++ b/docs/workers/quickstart.mdx @@ -7,16 +7,16 @@ icon: node-js ```sh -npm install @rivetkit/worker +npm install @rivetkit/actor ``` - + ```ts registry.ts -import { worker, setup } from "@rivetkit/worker"; +import { actor, setup } from "@rivetkit/actor"; -export const counter = worker({ +export const counter = actor({ state: { count: 0 }, actions: { increment: (c, x: number) => { @@ -27,7 +27,7 @@ export const counter = worker({ }); export const registry = setup({ - workers: { counter }, + actors: { counter }, }); ``` @@ -239,7 +239,7 @@ const { client, hono } = registry.run({ ## Configuration Options -### Connect your frontend to the Rivet Worker +### Connect your frontend to the Rivet Actor TODO: Quick summary of why you would want to connect your frontend @@ -248,27 +248,27 @@ Connect your frontend: ```ts JavaScript -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import type { registry } from "./registry.js"; const client = createClient("http://localhost:8080/registry"); -const result = await client.myWorker.getOrCreate().myAction("Hello, world!"); +const result = await client.myActor.getOrCreate().myAction("Hello, world!"); ``` ```ts React import { useState } from "react"; -import { createClient, createRivetKit } from "@@rivetkit/worker/react"; +import { createClient, createRivetKit } from "@@rivetkit/actor/react"; import type { registry } from "./registry"; const client = createClient(`http://localhost:8080/registry`); -const { useWorker } = createRivetKit(client); +const { useActor } = createRivetKit(client); function App() { const [count, setCount] = useState(0); const [counterName, setCounterName] = useState("test-counter"); - const counter = useWorker({ + const counter = useActor({ name: "counter", key: [counterName], }); @@ -336,7 +336,7 @@ TODO - + diff --git a/docs/workers/schedule.mdx b/docs/workers/schedule.mdx index 79c0b7a63..2f8c5ad60 100644 --- a/docs/workers/schedule.mdx +++ b/docs/workers/schedule.mdx @@ -3,7 +3,7 @@ title: Schedule icon: clock --- -Scheduling is used to trigger events in the future. The worker scheduler is like `setTimeout`, except the timeout will persist even if the worker restarts, upgrades, or crashes. +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 @@ -13,7 +13,7 @@ Scheduling is helpful for long-running timeouts like month-long billing periods ### `c.schedule.after(duration, fn, ...args)` -Schedules a function to be executed after a specified duration. This function persists across worker restarts, upgrades, or crashes. +Schedules a function to be executed after a specified duration. This function persists across actor restarts, upgrades, or crashes. Parameters: @@ -23,7 +23,7 @@ Parameters: ### `c.schedule.at(timestamp, fn, ...args)` -Schedules a function to be executed at a specific timestamp. This function persists across worker restarts, upgrades, or crashes. +Schedules a function to be executed at a specific timestamp. This function persists across actor restarts, upgrades, or crashes. Parameters: @@ -38,9 +38,9 @@ Currently, scheduling can only trigger public actions. If the scheduled action i ## Full Example ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const reminderService = worker({ +const reminderService = actor({ state: { reminders: {} }, diff --git a/docs/workers/state.mdx b/docs/workers/state.mdx index 5f644fdf7..7462c4764 100644 --- a/docs/workers/state.mdx +++ b/docs/workers/state.mdx @@ -3,26 +3,26 @@ title: State icon: floppy-disk --- -Worker 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. +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** -Workers can also be used with external SQL databases. This can be useful to integrate workers with existing +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). ## Initializing State -There are two ways to define a worker's initial state: +There are two ways to define a actor's initial state: **Method 1: Static Initial State** ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; // Simple state with a constant -const counter = worker({ +const counter = actor({ // Define state as a constant state: { count: 0 }, @@ -35,10 +35,10 @@ const counter = worker({ **Method 2: Dynamic Initial State** ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; // State with initialization logic -const counter = worker({ +const counter = actor({ // Define state using a creation function createState: () => { return { count: 0 }; @@ -50,16 +50,16 @@ const counter = worker({ }); ``` -The `createState` function is called once when the worker is first created. See [Lifecycle](/concepts/lifecycle) for more details. +The `createState` function is called once when the actor is first created. See [Lifecycle](/concepts/lifecycle) for more details. ## Modifying State To update state, modify the `state` property on the context object (`c.state`) in your actions: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const counter = worker({ +const counter = actor({ state: { count: 0 }, actions: { @@ -84,14 +84,14 @@ Only state stored in the `state` object will be persisted. Any other variables o ## State Saves -Workers automatically handle persisting state transparently. This happens at the end of every action if the state has changed. +Actors automatically handle persisting state transparently. This happens at the end of every action if the state has changed. In the rare occasion 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 -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; -const criticalProcess = worker({ +const criticalProcess = actor({ state: { steps: [], currentStep: 0 @@ -120,34 +120,34 @@ const criticalProcess = worker({ ## State Isolation -Each worker's state is completely isolated, meaning it cannot be accessed directly by other workers or clients. This allows workers to maintain a high level of security and data integrity, ensuring that state changes are controlled and predictable. +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 a worker's state, you must use [Actions](/concepts/actions). Actions provide a controlled way to read from and write to the state. +To interact with a actor's state, you must use [Actions](/concepts/actions). Actions provide a controlled way to read from and write to the state. -## Sharing State Between Workers +## Sharing State Between Actors -If you need a shared state between multiple workers, you have two options: +If you need a shared state between multiple actors, you have two options: -1. Create a worker that holds the shared state that other workers can make action calls to +1. Create a actor that holds the shared state that other actors can make action calls to 2. Use an external database, see [External SQL Databases](/concepts/external-sql) ## Ephemeral Variables -In addition to persisted state, RivetKit provides a way to store ephemeral data that is not saved to permanent storage using `vars`. This is useful for temporary data that only needs to exist while the worker is running or data that cannot be serialized. +In addition to persisted state, RivetKit provides a way to store ephemeral data that is not saved to permanent storage using `vars`. This is useful for temporary data that only needs to exist while the actor is running or data that cannot be serialized. -`vars` is designed to complement `state`, not replace it. Most workers should use both: `state` for critical business data and `vars` for ephemeral or non-serializable data. +`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 a worker's initial vars: +There are two ways to define a actor's initial vars: **Method 1: Static Initial Variables** ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; // Define vars as a constant -const counter = worker({ +const counter = actor({ state: { count: 0 }, // Define ephemeral variables @@ -169,10 +169,10 @@ When using static `vars`, all values must be compatible with `structuredClone()` **Method 2: Dynamic Initial Variables** ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; // Define vars with initialization logic -const counter = worker({ +const counter = actor({ state: { count: 0 }, // Define vars using a creation function @@ -194,10 +194,10 @@ const counter = worker({ Vars can be accessed and modified through the context object with `c.vars`: ```typescript -import { worker } from "rivetkit"; +import { actor } from "rivetkit"; import { createNanoEvents } from "nanoevents"; -const counter = worker({ +const counter = actor({ // Persistent state - saved to storage state: { count: 0 }, @@ -230,7 +230,7 @@ const counter = worker({ ### When to Use `vars` vs `state` -In practice, most workers will use both: `state` for critical business data and `vars` for ephemeral, non-serializable, or performance-sensitive data. +In practice, most actors will use both: `state` for critical business data and `vars` for ephemeral, non-serializable, or performance-sensitive data. Use `vars` when: @@ -239,8 +239,8 @@ Use `vars` when: Use `state` when: -- The data must be preserved across worker sleeps, restarts, updates, or crashes -- The information is essential to the worker's core functionality and business logic +- 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 ## Limitations diff --git a/docs/workers/types.mdx b/docs/workers/types.mdx index 44a079705..95793a691 100644 --- a/docs/workers/types.mdx +++ b/docs/workers/types.mdx @@ -2,20 +2,20 @@ title: Helper Types --- -RivetKit provides several TypeScript helper types to make it easier to work with workers in a type-safe way. +RivetKit provides several TypeScript helper types to make it easier to work with actors in a type-safe way. ## `Context` Types -When working with workers, you often need to access the context object. RivetKit provides helper types to extract the context types from worker definitions. +When working with actors, you often need to access the context object. RivetKit provides helper types to extract the context types from actor definitions. -### `WorkerContextOf` +### `ActorContextOf` -Extracts the full worker context type from a worker definition. This is the type of the context object (`c`) available in lifecycle hooks such as `onCreate`, `onStart`, etc. +Extracts the full actor context type from a actor definition. This is the type of the context object (`c`) available in lifecycle hooks such as `onCreate`, `onStart`, etc. ```typescript -import { worker, WorkerContextOf } from "rivetkit"; +import { actor, ActorContextOf } from "rivetkit"; -const chatRoom = worker({ +const chatRoom = actor({ state: { messages: [] }, actions: { sendMessage: (c, message) => { @@ -25,7 +25,7 @@ const chatRoom = worker({ }); // Extract the chat room context type -type ChatRoomContext = WorkerContextOf; +type ChatRoomContext = ActorContextOf; // Now you can use this type elsewhere function processChatRoomContext(context: ChatRoomContext) { @@ -35,14 +35,14 @@ function processChatRoomContext(context: ChatRoomContext) { } ``` -### `ActionContextOf` +### `ActionContextOf` -Extracts the action context type from a worker definition. This is the type of the context object (`c`) available in action handlers. +Extracts the action context type from a actor definition. This is the type of the context object (`c`) available in action handlers. ```typescript -import { worker, ActionContextOf } from "rivetkit"; +import { actor, ActionContextOf } from "rivetkit"; -const counter = worker({ +const counter = actor({ state: { count: 0 }, actions: { increment: (c) => { diff --git a/examples/better-auth/README.md b/examples/better-auth/README.md index 7f2594758..d4f52af4f 100644 --- a/examples/better-auth/README.md +++ b/examples/better-auth/README.md @@ -32,21 +32,21 @@ Open your browser to `http://localhost:5173` to see the frontend and the backend ## Features - **Authentication**: Email/password authentication using Better Auth -- **Protected Workers**: RivetKit workers with authentication via `onAuth` hook +- **Protected Actors**: RivetKit actors with authentication via `onAuth` hook - **Real-time Chat**: Authenticated chat room with real-time messaging - **SQLite Database**: Persistent user data and session storage ## How It Works 1. **Better Auth Setup**: Configured with SQLite adapter for user storage -2. **Protected Worker**: The `chatRoom` worker uses the `onAuth` hook to verify user sessions +2. **Protected Actor**: The `chatRoom` actor uses the `onAuth` hook to verify user sessions 3. **Frontend Integration**: React components handle authentication flow and chat interface 4. **Session Management**: Better Auth handles session creation, validation, and cleanup ## Key Files - `src/backend/auth.ts` - Better Auth configuration with SQLite -- `src/backend/registry.ts` - RivetKit worker with authentication +- `src/backend/registry.ts` - RivetKit actor with authentication - `src/frontend/components/AuthForm.tsx` - Login/signup form - `src/frontend/components/ChatRoom.tsx` - Authenticated chat interface diff --git a/examples/better-auth/package.json b/examples/better-auth/package.json index 600b9b8b4..108fd8437 100644 --- a/examples/better-auth/package.json +++ b/examples/better-auth/package.json @@ -17,7 +17,7 @@ "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.2.0", "concurrently": "^8.2.2", - "@rivetkit/worker": "workspace:*", + "@rivetkit/actor": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.5.2", "vite": "^5.0.0", diff --git a/examples/better-auth/src/backend/registry.ts b/examples/better-auth/src/backend/registry.ts index daceb8bbd..14abfe73d 100644 --- a/examples/better-auth/src/backend/registry.ts +++ b/examples/better-auth/src/backend/registry.ts @@ -1,7 +1,7 @@ -// import { worker, setup } from "@rivetkit/worker"; +// import { actor, setup } from "@rivetkit/actor"; // import { auth, type Session, type User } from "./auth"; // -// export const chatRoom = worker({ +// export const chatRoom = actor({ // onAuth: async (c) => { // const authResult = await auth.api.getSession({ // headers: c.req.headers, @@ -42,7 +42,7 @@ // }); // // export const registry = setup({ -// workers: { chatRoom }, +// actors: { chatRoom }, // }); // // export type Registry = typeof registry; diff --git a/examples/better-auth/src/frontend/components/ChatRoom.tsx b/examples/better-auth/src/frontend/components/ChatRoom.tsx index dcd0f86dc..e6d551a03 100644 --- a/examples/better-auth/src/frontend/components/ChatRoom.tsx +++ b/examples/better-auth/src/frontend/components/ChatRoom.tsx @@ -7,7 +7,7 @@ const client = createClient("http://localhost:6420/registry", { transport: "sse", }); -const { useWorker } = createRivetKit(client); +const { useActor } = createRivetKit(client); interface ChatRoomProps { user: { id: string; email: string }; @@ -25,7 +25,7 @@ export function ChatRoom({ user, onSignOut }: ChatRoomProps) { }>>([]); const [roomId] = useState("general"); - const chatRoom = useWorker({ + const chatRoom = useActor({ name: "chatRoom", key: [roomId], }); diff --git a/examples/chat-room/README.md b/examples/chat-room/README.md index e7f7476d3..900d55045 100644 --- a/examples/chat-room/README.md +++ b/examples/chat-room/README.md @@ -1,6 +1,6 @@ # Chat Room for RivetKit -Example project demonstrating real-time messaging and worker state management with [RivetKit](https://rivetkit.org). +Example project demonstrating real-time messaging and actor state management with [RivetKit](https://rivetkit.org). [Learn More →](https://github.com/rivet-gg/rivetkit) diff --git a/examples/chat-room/package.json b/examples/chat-room/package.json index 1924156ff..5e28cb919 100644 --- a/examples/chat-room/package.json +++ b/examples/chat-room/package.json @@ -12,7 +12,7 @@ "@types/node": "^22.13.9", "@types/prompts": "^2", "prompts": "^2.4.2", - "@rivetkit/worker": "workspace:*", + "@rivetkit/actor": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.5.2", "vitest": "^3.1.1" diff --git a/examples/chat-room/scripts/cli.ts b/examples/chat-room/scripts/cli.ts index e22b876f1..f90d23c13 100644 --- a/examples/chat-room/scripts/cli.ts +++ b/examples/chat-room/scripts/cli.ts @@ -1,5 +1,5 @@ -import { createClient } from "@rivetkit/worker/client"; -import type { Registry } from "../workers/registry"; +import { createClient } from "@rivetkit/actor/client"; +import type { Registry } from "../actors/registry"; import prompts from "prompts"; async function main() { diff --git a/examples/chat-room/scripts/connect.ts b/examples/chat-room/scripts/connect.ts index 0fdd3e4f5..1ee9496c9 100644 --- a/examples/chat-room/scripts/connect.ts +++ b/examples/chat-room/scripts/connect.ts @@ -1,6 +1,6 @@ /// -import { createClient } from "@rivetkit/worker/client"; -import type { Registry } from "../workers/registry"; +import { createClient } from "@rivetkit/actor/client"; +import type { Registry } from "../actors/registry"; async function main() { // Create type-aware client diff --git a/examples/chat-room/src/server.ts b/examples/chat-room/src/server.ts index d3235e055..a8ad753e1 100644 --- a/examples/chat-room/src/server.ts +++ b/examples/chat-room/src/server.ts @@ -1,4 +1,4 @@ // import { serve } from "@rivetkit/nodejs"; -// import { registry } from "./workers/registry"; +// import { registry } from "./actors/registry"; // // serve(registry); diff --git a/examples/chat-room/src/workers/registry.ts b/examples/chat-room/src/workers/registry.ts index 634c3c0a9..16169e990 100644 --- a/examples/chat-room/src/workers/registry.ts +++ b/examples/chat-room/src/workers/registry.ts @@ -1,11 +1,11 @@ -import { worker, setup } from "@rivetkit/worker"; +import { actor, setup } from "@rivetkit/actor"; // state managed by the actor export interface State { messages: { username: string; message: string }[]; } -export const chatRoom = worker({ +export const chatRoom = actor({ // initialize state state: { messages: [] } as State, @@ -28,7 +28,7 @@ export const chatRoom = worker({ // Create and export the app export const registry = setup({ - workers: { chatRoom }, + actors: { chatRoom }, }); // Export type for client type checking diff --git a/examples/chat-room/tests/chat-room.test.ts b/examples/chat-room/tests/chat-room.test.ts index b14ea9592..9f6b2729f 100644 --- a/examples/chat-room/tests/chat-room.test.ts +++ b/examples/chat-room/tests/chat-room.test.ts @@ -1,6 +1,6 @@ import { test, expect } from "vitest"; -import { setupTest } from "@rivetkit/worker/test"; -import { registry } from "../src/workers/registry"; +import { setupTest } from "@rivetkit/actor/test"; +import { registry } from "../src/actors/registry"; test("chat room should handle messages", async (test) => { const { client } = await setupTest(test, registry); diff --git a/examples/cloudflare-workers/README.md b/examples/cloudflare-workers/README.md index d05f1f95d..bf6cb3c63 100644 --- a/examples/cloudflare-workers/README.md +++ b/examples/cloudflare-workers/README.md @@ -11,7 +11,7 @@ Example project demonstrating Cloudflare Workers deployment with [RivetKit](http ### Prerequisites - Node.js -- Cloudflare account with Workers enabled +- Cloudflare account with Actors enabled - Wrangler CLI installed globally (`npm install -g wrangler`) ### Installation @@ -32,7 +32,7 @@ This will start the Cloudflare Workers development server locally at http://loca ### Testing the Client -In a separate terminal, run the client script to interact with your workers: +In a separate terminal, run the client script to interact with your actors: ```sh npm run client @@ -54,4 +54,4 @@ npm run deploy ## License -Apache 2.0 \ No newline at end of file +Apache 2.0 diff --git a/examples/cloudflare-workers/package.json b/examples/cloudflare-workers/package.json index 0ab187a3c..cf9eb867a 100644 --- a/examples/cloudflare-workers/package.json +++ b/examples/cloudflare-workers/package.json @@ -12,7 +12,7 @@ "devDependencies": { "@cloudflare/workers-types": "^4.20250129.0", "@types/node": "^22.13.9", - "@rivetkit/worker": "workspace:*", + "@rivetkit/actor": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.5.2", "wrangler": "^3.0.0" diff --git a/examples/cloudflare-workers/scripts/client.ts b/examples/cloudflare-workers/scripts/client.ts index ef15618e2..8169bb478 100644 --- a/examples/cloudflare-workers/scripts/client.ts +++ b/examples/cloudflare-workers/scripts/client.ts @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import type { Registry } from "../src/registry.js"; // Create RivetKit client diff --git a/examples/cloudflare-workers/src/index.ts b/examples/cloudflare-workers/src/index.ts index 341b3fff9..134284ea2 100644 --- a/examples/cloudflare-workers/src/index.ts +++ b/examples/cloudflare-workers/src/index.ts @@ -1,6 +1,6 @@ -import { createHandler } from "@rivetkit/cloudflare-workers"; -import { registry } from "./registry"; - -const { handler, WorkerHandler } = createHandler(registry); - -export { handler as default, WorkerHandler }; +// import { createHandler } from "@rivetkit/cloudflare-workers"; +// import { registry } from "./registry"; +// +// const { handler, ActorHandler } = createHandler(registry); +// +// export { handler as default, ActorHandler }; diff --git a/examples/cloudflare-workers/src/registry.ts b/examples/cloudflare-workers/src/registry.ts index 84f7d059b..4c0f83a0f 100644 --- a/examples/cloudflare-workers/src/registry.ts +++ b/examples/cloudflare-workers/src/registry.ts @@ -1,6 +1,6 @@ -import { worker, setup } from "@rivetkit/worker"; +import { actor, setup } from "@rivetkit/actor"; -export const counter = worker({ +export const counter = actor({ onAuth: () => { // Configure auth here }, @@ -14,7 +14,7 @@ export const counter = worker({ }); export const registry = setup({ - workers: { counter }, + actors: { counter }, }); export type Registry = typeof registry; diff --git a/examples/cloudflare-workers/tsconfig.json b/examples/cloudflare-workers/tsconfig.json index 681844dd9..b8a0a7676 100644 --- a/examples/cloudflare-workers/tsconfig.json +++ b/examples/cloudflare-workers/tsconfig.json @@ -40,4 +40,4 @@ "skipLibCheck": true }, "include": ["src/**/*"] -} \ No newline at end of file +} diff --git a/examples/cloudflare-workers/wrangler.json b/examples/cloudflare-workers/wrangler.json index 8791c7de9..29b055cf3 100644 --- a/examples/cloudflare-workers/wrangler.json +++ b/examples/cloudflare-workers/wrangler.json @@ -6,20 +6,20 @@ "migrations": [ { "tag": "v1", - "new_classes": ["WorkerHandler"] + "new_classes": ["ActorHandler"] } ], "durable_objects": { "bindings": [ { - "name": "WORKER_DO", - "class_name": "WorkerHandler" + "name": "ACTOR_DO", + "class_name": "ActorHandler" } ] }, "kv_namespaces": [ { - "binding": "WORKER_KV", + "binding": "ACTOR_KV", "id": "example_namespace", "preview_id": "example_namespace_preview" } @@ -27,4 +27,4 @@ "observability": { "enabled": true } -} \ No newline at end of file +} diff --git a/examples/counter/README.md b/examples/counter/README.md index bcc6a476b..cac72debf 100644 --- a/examples/counter/README.md +++ b/examples/counter/README.md @@ -1,6 +1,6 @@ # Counter for RivetKit -Example project demonstrating basic worker state management and RPC calls with [RivetKit](https://rivetkit.org). +Example project demonstrating basic actor state management and RPC calls with [RivetKit](https://rivetkit.org). [Learn More →](https://github.com/rivet-gg/rivetkit) diff --git a/examples/counter/package.json b/examples/counter/package.json index d1b6bf3f2..301b0aa6e 100644 --- a/examples/counter/package.json +++ b/examples/counter/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@types/node": "^22.13.9", - "@rivetkit/worker": "workspace:*", + "@rivetkit/actor": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.7.3", "vitest": "^3.1.1" diff --git a/examples/counter/scripts/connect.ts b/examples/counter/scripts/connect.ts index b8abc6cd7..0c85864c4 100644 --- a/examples/counter/scripts/connect.ts +++ b/examples/counter/scripts/connect.ts @@ -1,6 +1,6 @@ /// -import { createClient } from "@rivetkit/worker/client"; -import type { Registry } from "../src/workers/registry"; +import { createClient } from "@rivetkit/actor/client"; +import type { Registry } from "../src/actors/registry"; async function main() { const client = createClient( diff --git a/examples/counter/src/server.ts b/examples/counter/src/server.ts index d3235e055..a8ad753e1 100644 --- a/examples/counter/src/server.ts +++ b/examples/counter/src/server.ts @@ -1,4 +1,4 @@ // import { serve } from "@rivetkit/nodejs"; -// import { registry } from "./workers/registry"; +// import { registry } from "./actors/registry"; // // serve(registry); diff --git a/examples/counter/src/workers/registry.ts b/examples/counter/src/workers/registry.ts index 13f56c12b..e662d7e65 100644 --- a/examples/counter/src/workers/registry.ts +++ b/examples/counter/src/workers/registry.ts @@ -1,6 +1,6 @@ -import { worker, setup } from "@rivetkit/worker"; +import { actor, setup } from "@rivetkit/actor"; -const counter = worker({ +const counter = actor({ state: { count: 0 }, actions: { increment: (c, x: number) => { @@ -15,7 +15,7 @@ const counter = worker({ }); export const registry = setup({ - workers: { counter }, + actors: { counter }, }); export type Registry = typeof registry; diff --git a/examples/counter/tests/counter.test.ts b/examples/counter/tests/counter.test.ts index 618096300..47988c8fe 100644 --- a/examples/counter/tests/counter.test.ts +++ b/examples/counter/tests/counter.test.ts @@ -1,35 +1,35 @@ -import { test, expect } from "vitest"; -import { setupTest } from "@rivetkit/worker/test"; -import { registry } from "../src/workers/registry"; - -test("it should count", async (test) => { - const { client } = await setupTest(test, registry); - const counter = client.counter.getOrCreate().connect(); - - // Test initial count - expect(await counter.getCount()).toBe(0); - - // Test event emission - let eventCount = -1; - counter.on("newCount", (count: number) => { - eventCount = count; - }); - - // Test increment - const incrementAmount = 5; - const result = await counter.increment(incrementAmount); - expect(result).toBe(incrementAmount); - - // Verify event was emitted with correct count - expect(eventCount).toBe(incrementAmount); - - // Test multiple increments - for (let i = 1; i <= 3; i++) { - const newCount = await counter.increment(incrementAmount); - expect(newCount).toBe(incrementAmount * (i + 1)); - expect(eventCount).toBe(incrementAmount * (i + 1)); - } - - // Verify final count - expect(await counter.getCount()).toBe(incrementAmount * 4); -}); +// import { test, expect } from "vitest"; +// import { setupTest } from "@rivetkit/actor/test"; +// import { registry } from "../src/actors/registry"; +// +// test("it should count", async (test) => { +// const { client } = await setupTest(test, registry); +// const counter = client.counter.getOrCreate().connect(); +// +// // Test initial count +// expect(await counter.getCount()).toBe(0); +// +// // Test event emission +// let eventCount = -1; +// counter.on("newCount", (count: number) => { +// eventCount = count; +// }); +// +// // Test increment +// const incrementAmount = 5; +// const result = await counter.increment(incrementAmount); +// expect(result).toBe(incrementAmount); +// +// // Verify event was emitted with correct count +// expect(eventCount).toBe(incrementAmount); +// +// // Test multiple increments +// for (let i = 1; i <= 3; i++) { +// const newCount = await counter.increment(incrementAmount); +// expect(newCount).toBe(incrementAmount * (i + 1)); +// expect(eventCount).toBe(incrementAmount * (i + 1)); +// } +// +// // Verify final count +// expect(await counter.getCount()).toBe(incrementAmount * 4); +// }); diff --git a/examples/drizzle/src/registry.ts b/examples/drizzle/src/registry.ts index b7896048f..0398452d4 100644 --- a/examples/drizzle/src/registry.ts +++ b/examples/drizzle/src/registry.ts @@ -1,9 +1,9 @@ -// import { worker, setup } from "rivetkit"; +// import { actor, setup } from "rivetkit"; // import { db } from "@rivetkit/db/drizzle"; // import * as schema from "./db/schema"; // import migrations from "../drizzle/migrations"; -// export const counter = worker({ +// export const counter = actor({ // db: db({ schema, migrations }), // state: { // count: 0, @@ -21,7 +21,7 @@ // }); // export const registry = setup({ -// workers: { counter }, +// actors: { counter }, // }); // export type Registry = typeof registry; diff --git a/examples/elysia/package.json b/examples/elysia/package.json index 4d96c884f..54f657136 100644 --- a/examples/elysia/package.json +++ b/examples/elysia/package.json @@ -9,7 +9,7 @@ }, "devDependencies": { "@types/node": "^22.13.9", - "@rivetkit/worker": "workspace:*", + "@rivetkit/actor": "workspace:*", "typescript": "^5.5.2" }, "dependencies": { diff --git a/examples/elysia/src/registry.ts b/examples/elysia/src/registry.ts index 84f7d059b..4c0f83a0f 100644 --- a/examples/elysia/src/registry.ts +++ b/examples/elysia/src/registry.ts @@ -1,6 +1,6 @@ -import { worker, setup } from "@rivetkit/worker"; +import { actor, setup } from "@rivetkit/actor"; -export const counter = worker({ +export const counter = actor({ onAuth: () => { // Configure auth here }, @@ -14,7 +14,7 @@ export const counter = worker({ }); export const registry = setup({ - workers: { counter }, + actors: { counter }, }); export type Registry = typeof registry; diff --git a/examples/express/package.json b/examples/express/package.json index ae98f4809..5447bcd12 100644 --- a/examples/express/package.json +++ b/examples/express/package.json @@ -10,7 +10,7 @@ "devDependencies": { "@types/express": "^4.17.21", "@types/node": "^22.13.9", - "@rivetkit/worker": "workspace:*", + "@rivetkit/actor": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.5.2" }, diff --git a/examples/express/src/registry.ts b/examples/express/src/registry.ts index 84f7d059b..4c0f83a0f 100644 --- a/examples/express/src/registry.ts +++ b/examples/express/src/registry.ts @@ -1,6 +1,6 @@ -import { worker, setup } from "@rivetkit/worker"; +import { actor, setup } from "@rivetkit/actor"; -export const counter = worker({ +export const counter = actor({ onAuth: () => { // Configure auth here }, @@ -14,7 +14,7 @@ export const counter = worker({ }); export const registry = setup({ - workers: { counter }, + actors: { counter }, }); export type Registry = typeof registry; diff --git a/examples/hono-react/README.md b/examples/hono-react/README.md index 7833b6532..2accebca7 100644 --- a/examples/hono-react/README.md +++ b/examples/hono-react/README.md @@ -26,7 +26,7 @@ npm install npm run dev ``` -This will start both the Hono backend server and Vite React frontend. Open your browser to http://localhost:5173 to see the React app connected to RivetKit workers. +This will start both the Hono backend server and Vite React frontend. Open your browser to http://localhost:5173 to see the React app connected to RivetKit actors. ## License diff --git a/examples/hono-react/package.json b/examples/hono-react/package.json index aaa832e77..bf437ec27 100644 --- a/examples/hono-react/package.json +++ b/examples/hono-react/package.json @@ -17,7 +17,7 @@ "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.2.0", "concurrently": "^8.2.2", - "@rivetkit/worker": "workspace:*", + "@rivetkit/actor": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.5.2", "vite": "^5.0.0", diff --git a/examples/hono-react/src/backend/registry.ts b/examples/hono-react/src/backend/registry.ts index de9d21a60..854e1bb74 100644 --- a/examples/hono-react/src/backend/registry.ts +++ b/examples/hono-react/src/backend/registry.ts @@ -1,6 +1,6 @@ -import { worker, setup } from "@rivetkit/worker"; +import { actor, setup } from "@rivetkit/actor"; -export const counter = worker({ +export const counter = actor({ onAuth: () => { // Configure auth here }, @@ -15,7 +15,7 @@ export const counter = worker({ }); export const registry = setup({ - workers: { counter }, + actors: { counter }, }); export type Registry = typeof registry; diff --git a/examples/hono-react/src/frontend/App.tsx b/examples/hono-react/src/frontend/App.tsx index d5aebce6a..fe27125ff 100644 --- a/examples/hono-react/src/frontend/App.tsx +++ b/examples/hono-react/src/frontend/App.tsx @@ -5,13 +5,13 @@ // const client = createClient("http://localhost:6420/registry", { // transport: "sse", // }); -// const { useWorker } = createRivetKit(client); +// const { useActor } = createRivetKit(client); // // function App() { // const [count, setCount] = useState(0); // const [counterName, setCounterName] = useState("test-counter"); // -// const counter = useWorker({ +// const counter = useActor({ // name: "counter", // key: [counterName], // }); diff --git a/examples/hono/package.json b/examples/hono/package.json index 63c7d873a..ac4f77661 100644 --- a/examples/hono/package.json +++ b/examples/hono/package.json @@ -9,7 +9,7 @@ }, "devDependencies": { "@types/node": "^22.13.9", - "@rivetkit/worker": "workspace:*", + "@rivetkit/actor": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.5.2" }, diff --git a/examples/hono/src/registry.ts b/examples/hono/src/registry.ts index 84f7d059b..4c0f83a0f 100644 --- a/examples/hono/src/registry.ts +++ b/examples/hono/src/registry.ts @@ -1,6 +1,6 @@ -import { worker, setup } from "@rivetkit/worker"; +import { actor, setup } from "@rivetkit/actor"; -export const counter = worker({ +export const counter = actor({ onAuth: () => { // Configure auth here }, @@ -14,7 +14,7 @@ export const counter = worker({ }); export const registry = setup({ - workers: { counter }, + actors: { counter }, }); export type Registry = typeof registry; diff --git a/examples/react/README.md b/examples/react/README.md index 7833b6532..2accebca7 100644 --- a/examples/react/README.md +++ b/examples/react/README.md @@ -26,7 +26,7 @@ npm install npm run dev ``` -This will start both the Hono backend server and Vite React frontend. Open your browser to http://localhost:5173 to see the React app connected to RivetKit workers. +This will start both the Hono backend server and Vite React frontend. Open your browser to http://localhost:5173 to see the React app connected to RivetKit actors. ## License diff --git a/examples/react/package.json b/examples/react/package.json index a37a14a74..97ea05fe1 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -17,7 +17,7 @@ "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.2.0", "concurrently": "^8.2.2", - "@rivetkit/worker": "workspace:*", + "@rivetkit/actor": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.5.2", "vite": "^5.0.0", diff --git a/examples/react/src/backend/registry.ts b/examples/react/src/backend/registry.ts index de9d21a60..854e1bb74 100644 --- a/examples/react/src/backend/registry.ts +++ b/examples/react/src/backend/registry.ts @@ -1,6 +1,6 @@ -import { worker, setup } from "@rivetkit/worker"; +import { actor, setup } from "@rivetkit/actor"; -export const counter = worker({ +export const counter = actor({ onAuth: () => { // Configure auth here }, @@ -15,7 +15,7 @@ export const counter = worker({ }); export const registry = setup({ - workers: { counter }, + actors: { counter }, }); export type Registry = typeof registry; diff --git a/examples/react/src/frontend/App.tsx b/examples/react/src/frontend/App.tsx index a65a3cabf..97a1c0eef 100644 --- a/examples/react/src/frontend/App.tsx +++ b/examples/react/src/frontend/App.tsx @@ -3,13 +3,13 @@ // import type { Registry } from "../backend/registry"; // // const client = createClient(`http://localhost:6420/registry`); -// const { useWorker } = createRivetKit(client); +// const { useActor } = createRivetKit(client); // // function App() { // const [count, setCount] = useState(0); // const [counterName, setCounterName] = useState("test-counter"); // -// const counter = useWorker({ +// const counter = useActor({ // name: "counter", // key: [counterName], // }); diff --git a/examples/rivet/README.md b/examples/rivet/README.md index 62a27648f..5b0bcd942 100644 --- a/examples/rivet/README.md +++ b/examples/rivet/README.md @@ -43,7 +43,7 @@ This will start the RivetKit server locally at http://localhost:6420. ### Testing the Client -In a separate terminal, run the client script to interact with your workers: +In a separate terminal, run the client script to interact with your actors: ```sh npm run client @@ -57,7 +57,7 @@ Deploy to Rivet Cloud: rivet deploy ``` -Your RivetKit workers will be deployed as Rivet actors with automatic scaling and management. +Your RivetKit actors will be deployed as Rivet actors with automatic scaling and management. ## License diff --git a/examples/rivet/package.json b/examples/rivet/package.json index 9999a8d03..01b508ddb 100644 --- a/examples/rivet/package.json +++ b/examples/rivet/package.json @@ -14,7 +14,7 @@ "typescript": "^5.5.2" }, "dependencies": { - "@rivetkit/worker": "https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/worker@38d8fca" + "@rivetkit/actor": "workspace:*" }, "stableVersion": "0.8.0" } diff --git a/examples/rivet/scripts/client.ts b/examples/rivet/scripts/client.ts index 860099971..9472eb28c 100644 --- a/examples/rivet/scripts/client.ts +++ b/examples/rivet/scripts/client.ts @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import { execSync } from "node:child_process"; import type { registry } from "../src/registry.js"; diff --git a/examples/rivet/src/registry.ts b/examples/rivet/src/registry.ts index bcb929459..4d0a85af7 100644 --- a/examples/rivet/src/registry.ts +++ b/examples/rivet/src/registry.ts @@ -1,6 +1,6 @@ -import { worker, setup } from "@rivetkit/worker"; +import { actor, setup } from "@rivetkit/actor"; -export const counter = worker({ +export const counter = actor({ onAuth: () => { // Configure auth here }, @@ -14,6 +14,6 @@ export const counter = worker({ }); export const registry = setup({ - workers: { counter }, + actors: { counter }, }); diff --git a/examples/snippets/ai-agent/App.tsx b/examples/snippets/ai-agent/App.tsx index 39a31ef88..db1d67473 100644 --- a/examples/snippets/ai-agent/App.tsx +++ b/examples/snippets/ai-agent/App.tsx @@ -1,7 +1,7 @@ -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { Registry } from "../workers/registry"; +import type { Registry } from "../actors/registry"; import type { Message } from "./actor"; const client = createClient("http://localhost:6420"); diff --git a/examples/snippets/ai-agent/actor-json.ts b/examples/snippets/ai-agent/actor-json.ts index 5d5ede675..2588cb84e 100644 --- a/examples/snippets/ai-agent/actor-json.ts +++ b/examples/snippets/ai-agent/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { generateText, tool } from "ai"; import { openai } from "@ai-sdk/openai"; import { getWeather } from "./my-utils"; diff --git a/examples/snippets/ai-agent/actor-sqlite.ts b/examples/snippets/ai-agent/actor-sqlite.ts index 61ed3ce46..2877282cf 100644 --- a/examples/snippets/ai-agent/actor-sqlite.ts +++ b/examples/snippets/ai-agent/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { drizzle } from "@rivetkit/drizzle"; import { generateText, tool } from "ai"; import { openai } from "@ai-sdk/openai"; diff --git a/examples/snippets/chat-room/App.tsx b/examples/snippets/chat-room/App.tsx index 8735e5a96..8371e4589 100644 --- a/examples/snippets/chat-room/App.tsx +++ b/examples/snippets/chat-room/App.tsx @@ -1,7 +1,7 @@ -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { Registry } from "../workers/registry"; +import type { Registry } from "../actors/registry"; import type { Message } from "./actor"; const client = createClient("http://localhost:6420"); diff --git a/examples/snippets/chat-room/actor-json.ts b/examples/snippets/chat-room/actor-json.ts index b6c9e0b02..8b279f4f4 100644 --- a/examples/snippets/chat-room/actor-json.ts +++ b/examples/snippets/chat-room/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; export type Message = { sender: string; text: string; timestamp: number; } diff --git a/examples/snippets/chat-room/actor-sqlite.ts b/examples/snippets/chat-room/actor-sqlite.ts index ad922290d..de9f9281a 100644 --- a/examples/snippets/chat-room/actor-sqlite.ts +++ b/examples/snippets/chat-room/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { drizzle } from "@rivetkit/drizzle"; import { messages } from "./schema"; diff --git a/examples/snippets/crdt/App.tsx b/examples/snippets/crdt/App.tsx index e37bc7b6d..a00dea4b4 100644 --- a/examples/snippets/crdt/App.tsx +++ b/examples/snippets/crdt/App.tsx @@ -1,9 +1,9 @@ -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect, useRef } from "react"; import * as Y from 'yjs'; import { applyUpdate, encodeStateAsUpdate } from 'yjs'; -import type { Registry } from "../workers/registry"; +import type { Registry } from "../actors/registry"; const client = createClient("http://localhost:6420"); const { useActor, useActorEvent } = createReactRivetKit(client); diff --git a/examples/snippets/crdt/actor-json.ts b/examples/snippets/crdt/actor-json.ts index a50f34d6c..0ec15a6aa 100644 --- a/examples/snippets/crdt/actor-json.ts +++ b/examples/snippets/crdt/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import * as Y from 'yjs'; import { encodeStateAsUpdate, applyUpdate } from 'yjs'; diff --git a/examples/snippets/crdt/actor-sqlite.ts b/examples/snippets/crdt/actor-sqlite.ts index a99acde4c..0a6c20463 100644 --- a/examples/snippets/crdt/actor-sqlite.ts +++ b/examples/snippets/crdt/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { drizzle } from "@rivetkit/drizzle"; import * as Y from 'yjs'; import { encodeStateAsUpdate, applyUpdate } from 'yjs'; diff --git a/examples/snippets/database/App.tsx b/examples/snippets/database/App.tsx index 637445f81..299e2344e 100644 --- a/examples/snippets/database/App.tsx +++ b/examples/snippets/database/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; diff --git a/examples/snippets/database/actor-json.ts b/examples/snippets/database/actor-json.ts index 9761bdf35..3fe717717 100644 --- a/examples/snippets/database/actor-json.ts +++ b/examples/snippets/database/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { authenticate } from "./my-utils"; export type Note = { id: string; content: string; updatedAt: number }; diff --git a/examples/snippets/database/actor-sqlite.ts b/examples/snippets/database/actor-sqlite.ts index 41f1f974b..d316f2bfa 100644 --- a/examples/snippets/database/actor-sqlite.ts +++ b/examples/snippets/database/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { drizzle } from "@rivetkit/drizzle"; import { notes } from "./schema"; import { authenticate } from "./my-utils"; diff --git a/examples/snippets/document/App.tsx b/examples/snippets/document/App.tsx index 03f00c7ca..cb0031e7a 100644 --- a/examples/snippets/document/App.tsx +++ b/examples/snippets/document/App.tsx @@ -1,7 +1,7 @@ -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { Registry } from "../workers/registry"; +import type { Registry } from "../actors/registry"; const client = createClient("http://localhost:6420"); const { useActor, useActorEvent } = createReactRivetKit(client); diff --git a/examples/snippets/document/actor-json.ts b/examples/snippets/document/actor-json.ts index 9fd4c668e..928835d46 100644 --- a/examples/snippets/document/actor-json.ts +++ b/examples/snippets/document/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; export type Cursor = { x: number, y: number, userId: string }; diff --git a/examples/snippets/document/actor-sqlite.ts b/examples/snippets/document/actor-sqlite.ts index a65d8a0a2..2c34de513 100644 --- a/examples/snippets/document/actor-sqlite.ts +++ b/examples/snippets/document/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { drizzle } from "@rivetkit/drizzle"; import { documents, cursors } from "./schema"; diff --git a/examples/snippets/game/App.tsx b/examples/snippets/game/App.tsx index 4122f81bc..43eea65bb 100644 --- a/examples/snippets/game/App.tsx +++ b/examples/snippets/game/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect, useRef } from "react"; import type { Player } from "./actor"; diff --git a/examples/snippets/game/actor-json.ts b/examples/snippets/game/actor-json.ts index 739de59dd..088f20c42 100644 --- a/examples/snippets/game/actor-json.ts +++ b/examples/snippets/game/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; export type Position = { x: number; y: number }; export type Input = { x: number; y: number }; diff --git a/examples/snippets/game/actor-sqlite.ts b/examples/snippets/game/actor-sqlite.ts index 22e93635e..b58a4f88d 100644 --- a/examples/snippets/game/actor-sqlite.ts +++ b/examples/snippets/game/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { drizzle } from "@rivetkit/drizzle"; import { players, gameSettings } from "./schema"; diff --git a/examples/snippets/rate/App.tsx b/examples/snippets/rate/App.tsx index b54a99047..a21624c06 100644 --- a/examples/snippets/rate/App.tsx +++ b/examples/snippets/rate/App.tsx @@ -1,7 +1,7 @@ -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState } from "react"; -import type { Registry } from "../workers/registry"; +import type { Registry } from "../actors/registry"; const client = createClient("http://localhost:6420"); const { useActor } = createReactRivetKit(client); diff --git a/examples/snippets/rate/actor-json.ts b/examples/snippets/rate/actor-json.ts index b06432d19..41570cfb8 100644 --- a/examples/snippets/rate/actor-json.ts +++ b/examples/snippets/rate/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; // Simple rate limiter - allows 5 requests per minute const rateLimiter = actor({ diff --git a/examples/snippets/rate/actor-sqlite.ts b/examples/snippets/rate/actor-sqlite.ts index ace413872..374571216 100644 --- a/examples/snippets/rate/actor-sqlite.ts +++ b/examples/snippets/rate/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { drizzle } from "@rivetkit/drizzle"; import { limiters } from "./schema"; diff --git a/examples/snippets/stream/App.tsx b/examples/snippets/stream/App.tsx index 21f4de62f..ebd3a37b5 100644 --- a/examples/snippets/stream/App.tsx +++ b/examples/snippets/stream/App.tsx @@ -1,7 +1,7 @@ -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { Registry } from "../workers/registry"; +import type { Registry } from "../actors/registry"; import type { StreamState } from "./actor"; // Import shared types from actor const client = createClient("http://localhost:6420"); diff --git a/examples/snippets/stream/actor-json.ts b/examples/snippets/stream/actor-json.ts index ba49b271e..63bfb3526 100644 --- a/examples/snippets/stream/actor-json.ts +++ b/examples/snippets/stream/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; export type StreamState = { topValues: number[]; diff --git a/examples/snippets/stream/actor-sqlite.ts b/examples/snippets/stream/actor-sqlite.ts index ae7bd64f0..31356f685 100644 --- a/examples/snippets/stream/actor-sqlite.ts +++ b/examples/snippets/stream/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { drizzle } from "@rivetkit/drizzle"; import { streams, streamValues } from "./schema"; diff --git a/examples/snippets/sync/App.tsx b/examples/snippets/sync/App.tsx index ab6283ddb..0cb5a1300 100644 --- a/examples/snippets/sync/App.tsx +++ b/examples/snippets/sync/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect, useRef } from "react"; import type { Contact } from "./actor"; diff --git a/examples/snippets/sync/actor-json.ts b/examples/snippets/sync/actor-json.ts index 656904868..a723d2c24 100644 --- a/examples/snippets/sync/actor-json.ts +++ b/examples/snippets/sync/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; export type Contact = { id: string; name: string; email: string; phone: string; updatedAt: number; } diff --git a/examples/snippets/sync/actor-sqlite.ts b/examples/snippets/sync/actor-sqlite.ts index 25d88f81a..638ffeae0 100644 --- a/examples/snippets/sync/actor-sqlite.ts +++ b/examples/snippets/sync/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { drizzle } from "@rivetkit/drizzle"; import { contacts } from "./schema"; diff --git a/examples/snippets/tenant/App.tsx b/examples/snippets/tenant/App.tsx index 0f0cf9dc8..2f4b0971e 100644 --- a/examples/snippets/tenant/App.tsx +++ b/examples/snippets/tenant/App.tsx @@ -1,7 +1,7 @@ -import { createClient } from "@rivetkit/worker/client"; +import { createClient } from "@rivetkit/actor/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { Registry } from "../workers/registry"; +import type { Registry } from "../actors/registry"; // Create client and hooks const client = createClient("http://localhost:6420"); diff --git a/examples/snippets/tenant/actor-json.ts b/examples/snippets/tenant/actor-json.ts index d09cf4ebb..1d6c54172 100644 --- a/examples/snippets/tenant/actor-json.ts +++ b/examples/snippets/tenant/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { authenticate } from "./my-utils"; // Simple tenant organization actor diff --git a/examples/snippets/tenant/actor-sqlite.ts b/examples/snippets/tenant/actor-sqlite.ts index eab708652..954ebf8ae 100644 --- a/examples/snippets/tenant/actor-sqlite.ts +++ b/examples/snippets/tenant/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/worker"; +import { actor } from "@rivetkit/actor"; import { drizzle } from "@rivetkit/drizzle"; import { members, invoices } from "./schema"; import { authenticate } from "./my-utils"; diff --git a/examples/trpc/package.json b/examples/trpc/package.json index 7a360d443..c9a7cb014 100644 --- a/examples/trpc/package.json +++ b/examples/trpc/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@types/node": "^22.13.9", - "@rivetkit/worker": "workspace:*", + "@rivetkit/actor": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.5.2" }, diff --git a/examples/trpc/scripts/client.ts b/examples/trpc/scripts/client.ts index fbbee233c..f87ec1703 100644 --- a/examples/trpc/scripts/client.ts +++ b/examples/trpc/scripts/client.ts @@ -1,34 +1,34 @@ -import { createTRPCClient, httpBatchLink } from "@trpc/client"; -import type { AppRouter } from "../src/server.js"; - -// Create tRPC client -const client = createTRPCClient({ - links: [ - httpBatchLink({ - url: "http://localhost:3001", - }), - ], -}); - -async function main() { - console.log("🚀 tRPC Client Demo"); - - try { - // Increment counter - console.log("Incrementing counter 'demo'..."); - const result = await client.increment.mutate({ name: "demo" }); - console.log("New count:", result); - - // Increment again - console.log("Incrementing counter 'demo' again..."); - const result2 = await client.increment.mutate({ name: "demo" }); - console.log("New count:", result2); - - console.log("✅ Demo completed!"); - } catch (error) { - console.error("❌ Error:", error); - process.exit(1); - } -} - -main().catch(console.error); +// import { createTRPCClient, httpBatchLink } from "@trpc/client"; +// import type { AppRouter } from "../src/server.js"; +// +// // Create tRPC client +// const client = createTRPCClient({ +// links: [ +// httpBatchLink({ +// url: "http://localhost:3001", +// }), +// ], +// }); +// +// async function main() { +// console.log("🚀 tRPC Client Demo"); +// +// try { +// // Increment counter +// console.log("Incrementing counter 'demo'..."); +// const result = await client.increment.mutate({ name: "demo" }); +// console.log("New count:", result); +// +// // Increment again +// console.log("Incrementing counter 'demo' again..."); +// const result2 = await client.increment.mutate({ name: "demo" }); +// console.log("New count:", result2); +// +// console.log("✅ Demo completed!"); +// } catch (error) { +// console.error("❌ Error:", error); +// process.exit(1); +// } +// } +// +// main().catch(console.error); diff --git a/examples/trpc/src/registry.ts b/examples/trpc/src/registry.ts index 84f7d059b..4c0f83a0f 100644 --- a/examples/trpc/src/registry.ts +++ b/examples/trpc/src/registry.ts @@ -1,6 +1,6 @@ -import { worker, setup } from "@rivetkit/worker"; +import { actor, setup } from "@rivetkit/actor"; -export const counter = worker({ +export const counter = actor({ onAuth: () => { // Configure auth here }, @@ -14,7 +14,7 @@ export const counter = worker({ }); export const registry = setup({ - workers: { counter }, + actors: { counter }, }); export type Registry = typeof registry; diff --git a/packages/worker/package.json b/packages/actor/package.json similarity index 97% rename from packages/worker/package.json rename to packages/actor/package.json index 48b337b65..e5f8cf3b7 100644 --- a/packages/worker/package.json +++ b/packages/actor/package.json @@ -1,9 +1,9 @@ { - "name": "@rivetkit/worker", + "name": "@rivetkit/actor", "version": "0.9.0-rc.1", "keywords": [ "rivetkit", - "worker", + "actor", "stateful", "actor", "rpc", diff --git a/packages/worker/src/client.ts b/packages/actor/src/client.ts similarity index 100% rename from packages/worker/src/client.ts rename to packages/actor/src/client.ts diff --git a/packages/worker/src/drivers/rivet.ts b/packages/actor/src/drivers/rivet.ts similarity index 100% rename from packages/worker/src/drivers/rivet.ts rename to packages/actor/src/drivers/rivet.ts diff --git a/packages/worker/src/errors.ts b/packages/actor/src/errors.ts similarity index 100% rename from packages/worker/src/errors.ts rename to packages/actor/src/errors.ts diff --git a/packages/worker/src/log.ts b/packages/actor/src/log.ts similarity index 100% rename from packages/worker/src/log.ts rename to packages/actor/src/log.ts diff --git a/packages/worker/src/mod.ts b/packages/actor/src/mod.ts similarity index 100% rename from packages/worker/src/mod.ts rename to packages/actor/src/mod.ts diff --git a/packages/worker/src/test.ts b/packages/actor/src/test.ts similarity index 100% rename from packages/worker/src/test.ts rename to packages/actor/src/test.ts diff --git a/packages/worker/tsconfig.json b/packages/actor/tsconfig.json similarity index 100% rename from packages/worker/tsconfig.json rename to packages/actor/tsconfig.json diff --git a/packages/worker/tsup.config.ts b/packages/actor/tsup.config.ts similarity index 100% rename from packages/worker/tsup.config.ts rename to packages/actor/tsup.config.ts diff --git a/packages/worker/turbo.json b/packages/actor/turbo.json similarity index 100% rename from packages/worker/turbo.json rename to packages/actor/turbo.json diff --git a/packages/core/fixtures/driver-test-suite/action-inputs.ts b/packages/core/fixtures/driver-test-suite/action-inputs.ts new file mode 100644 index 000000000..fd4caa654 --- /dev/null +++ b/packages/core/fixtures/driver-test-suite/action-inputs.ts @@ -0,0 +1,31 @@ +import { actor } from "@rivetkit/core"; + +export interface State { + initialInput?: unknown; + onCreateInput?: unknown; +} + +// Test actor that can capture input during creation +export const inputActor = actor({ + onAuth: () => {}, + createState: (c, { input }): State => { + return { + initialInput: input, + onCreateInput: undefined, + }; + }, + + onCreate: (c, { input }) => { + c.state.onCreateInput = input; + }, + + actions: { + getInputs: (c) => { + return { + initialInput: c.state.initialInput, + onCreateInput: c.state.onCreateInput, + }; + }, + }, +}); + diff --git a/packages/core/fixtures/driver-test-suite/action-timeout.ts b/packages/core/fixtures/driver-test-suite/action-timeout.ts index 598d47e8e..f3b32ffa7 100644 --- a/packages/core/fixtures/driver-test-suite/action-timeout.ts +++ b/packages/core/fixtures/driver-test-suite/action-timeout.ts @@ -1,7 +1,7 @@ -import { worker } from "rivetkit"; +import { actor } from "@rivetkit/core"; -// Short timeout worker -export const shortTimeoutWorker = worker({ +// Short timeout actor +export const shortTimeoutActor = actor({ onAuth: () => {}, state: { value: 0 }, options: { @@ -21,8 +21,8 @@ export const shortTimeoutWorker = worker({ }, }); -// Long timeout worker -export const longTimeoutWorker = worker({ +// Long timeout actor +export const longTimeoutActor = actor({ onAuth: () => {}, state: { value: 0 }, options: { @@ -39,8 +39,8 @@ export const longTimeoutWorker = worker({ }, }); -// Default timeout worker -export const defaultTimeoutWorker = worker({ +// Default timeout actor +export const defaultTimeoutActor = actor({ onAuth: () => {}, state: { value: 0 }, actions: { @@ -51,8 +51,8 @@ export const defaultTimeoutWorker = worker({ }, }); -// Sync worker (timeout shouldn't apply) -export const syncTimeoutWorker = worker({ +// Sync actor (timeout shouldn't apply) +export const syncTimeoutActor = actor({ onAuth: () => {}, state: { value: 0 }, options: { @@ -66,3 +66,5 @@ export const syncTimeoutWorker = worker({ }, }, }); + + diff --git a/packages/core/fixtures/driver-test-suite/action-types.ts b/packages/core/fixtures/driver-test-suite/action-types.ts new file mode 100644 index 000000000..da34b99e7 --- /dev/null +++ b/packages/core/fixtures/driver-test-suite/action-types.ts @@ -0,0 +1,86 @@ +import { actor, UserError } from "@rivetkit/core"; + +// Actor with synchronous actions +export const syncActionActor = actor({ + onAuth: () => {}, + state: { value: 0 }, + actions: { + // Simple synchronous action that returns a value directly + increment: (c, amount: number = 1) => { + c.state.value += amount; + return c.state.value; + }, + // Synchronous action that returns an object + getInfo: (c) => { + return { + currentValue: c.state.value, + timestamp: Date.now(), + }; + }, + // Synchronous action with no return value (void) + reset: (c) => { + c.state.value = 0; + }, + }, +}); + +// Actor with asynchronous actions +export const asyncActionActor = actor({ + onAuth: () => {}, + state: { value: 0, data: null as any }, + actions: { + // Async action with a delay + delayedIncrement: async (c, amount: number = 1) => { + await Promise.resolve(); + c.state.value += amount; + return c.state.value; + }, + // Async action that simulates an API call + fetchData: async (c, id: string) => { + await Promise.resolve(); + + // Simulate response data + const data = { id, timestamp: Date.now() }; + c.state.data = data; + return data; + }, + // Async action with error handling + asyncWithError: async (c, shouldError: boolean) => { + await Promise.resolve(); + + if (shouldError) { + throw new UserError("Intentional error"); + } + + return "Success"; + }, + }, +}); + +// Actor with promise actions +export const promiseActor = actor({ + onAuth: () => {}, + state: { results: [] as string[] }, + actions: { + // Action that returns a resolved promise + resolvedPromise: (c) => { + return Promise.resolve("resolved value"); + }, + // Action that returns a promise that resolves after a delay + delayedPromise: (c): Promise => { + return new Promise((resolve) => { + c.state.results.push("delayed"); + resolve("delayed value"); + }); + }, + // Action that returns a rejected promise + rejectedPromise: (c) => { + return Promise.reject(new UserError("promised rejection")); + }, + // Action to check the collected results + getResults: (c) => { + return c.state.results; + }, + }, +}); + diff --git a/packages/core/fixtures/driver-test-suite/auth.ts b/packages/core/fixtures/driver-test-suite/auth.ts new file mode 100644 index 000000000..b50ef8a0b --- /dev/null +++ b/packages/core/fixtures/driver-test-suite/auth.ts @@ -0,0 +1,105 @@ +import { actor, UserError } from "@rivetkit/core"; + +// Basic auth actor - requires API key +export const authActor = actor({ + state: { requests: 0 }, + onAuth: (opts) => { + const { req, intents, params } = opts; + const apiKey = (params as any)?.apiKey; + if (!apiKey) { + throw new UserError("API key required", { code: "missing_auth" }); + } + + if (apiKey !== "valid-api-key") { + throw new UserError("Invalid API key", { code: "invalid_auth" }); + } + + return { userId: "user123", token: apiKey }; + }, + actions: { + getRequests: (c) => { + c.state.requests++; + return c.state.requests; + }, + getUserAuth: (c) => c.conn.auth, + }, +}); + +// Intent-specific auth actor - checks different permissions for different intents +export const intentAuthActor = actor({ + state: { value: 0 }, + onAuth: (opts) => { + const { req, intents, params } = opts; + console.log('intents', intents, params); + const role = (params as any)?.role; + + if (intents.has("create") && role !== "admin") { + throw new UserError("Admin role required for create operations", { code: "insufficient_permissions" }); + } + + if (intents.has("action") && !["admin", "user"].includes(role || "")) { + throw new UserError("User or admin role required for actions", { code: "insufficient_permissions" }); + } + + return { role, timestamp: Date.now() }; + }, + actions: { + getValue: (c) => c.state.value, + setValue: (c, value: number) => { + c.state.value = value; + return value; + }, + getAuth: (c) => c.conn.auth, + }, +}); + +// Public actor - empty onAuth to allow public access +export const publicActor = actor({ + state: { visitors: 0 }, + onAuth: () => { + return null; // Allow public access + }, + actions: { + visit: (c) => { + c.state.visitors++; + return c.state.visitors; + }, + }, +}); + +// No auth actor - should fail when accessed publicly (no onAuth defined) +export const noAuthActor = actor({ + state: { value: 42 }, + actions: { + getValue: (c) => c.state.value, + }, +}); + +// Async auth actor - tests promise-based authentication +export const asyncAuthActor = actor({ + state: { count: 0 }, + onAuth: async (opts) => { + const { req, intents, params } = opts; + // Simulate async auth check (e.g., database lookup) + await new Promise(resolve => setTimeout(resolve, 10)); + + const token = (params as any)?.token; + if (!token) { + throw new UserError("Token required", { code: "missing_token" }); + } + + // Simulate token validation + if (token === "invalid") { + throw new UserError("Token is invalid", { code: "invalid_token" }); + } + + return { userId: `user-${token}`, validated: true }; + }, + actions: { + increment: (c) => { + c.state.count++; + return c.state.count; + }, + getAuthData: (c) => c.conn.auth, + }, +}); diff --git a/packages/core/fixtures/driver-test-suite/conn-params.ts b/packages/core/fixtures/driver-test-suite/conn-params.ts new file mode 100644 index 000000000..352d3fc9c --- /dev/null +++ b/packages/core/fixtures/driver-test-suite/conn-params.ts @@ -0,0 +1,29 @@ +import { actor } from "@rivetkit/core"; + +export const counterWithParams = actor({ + onAuth: () => {}, + state: { count: 0, initializers: [] as string[] }, + createConnState: (c, { params }: { params: { name?: string } }) => { + return { + name: params?.name || "anonymous", + }; + }, + onConnect: (c, conn) => { + // Record connection name + c.state.initializers.push(conn.state.name); + }, + actions: { + increment: (c, x: number) => { + c.state.count += x; + c.broadcast("newCount", { + count: c.state.count, + by: c.conn.state.name, + }); + return c.state.count; + }, + getInitializers: (c) => { + return c.state.initializers; + }, + }, +}); + diff --git a/packages/core/fixtures/driver-test-suite/conn-state.ts b/packages/core/fixtures/driver-test-suite/conn-state.ts new file mode 100644 index 000000000..8f7096936 --- /dev/null +++ b/packages/core/fixtures/driver-test-suite/conn-state.ts @@ -0,0 +1,97 @@ +import { actor } from "@rivetkit/core"; + +export type ConnState = { + username: string; + role: string; + counter: number; + createdAt: number; +}; + +export const connStateActor = actor({ + onAuth: () => {}, + state: { + sharedCounter: 0, + disconnectionCount: 0, + }, + // Define connection state + createConnState: ( + c, + { params }: { params?: { username?: string; role?: string } }, + ): ConnState => { + return { + username: params?.username || "anonymous", + role: params?.role || "user", + counter: 0, + createdAt: Date.now(), + }; + }, + // Lifecycle hook when a connection is established + onConnect: (c, conn) => { + // Broadcast event about the new connection + c.broadcast("userConnected", { + id: conn.id, + username: "anonymous", + role: "user", + }); + }, + // Lifecycle hook when a connection is closed + onDisconnect: (c, conn) => { + c.state.disconnectionCount += 1; + c.broadcast("userDisconnected", { + id: conn.id, + }); + }, + actions: { + // Action to increment the connection's counter + incrementConnCounter: (c, amount: number = 1) => { + c.conn.state.counter += amount; + }, + + // Action to increment the shared counter + incrementSharedCounter: (c, amount: number = 1) => { + c.state.sharedCounter += amount; + return c.state.sharedCounter; + }, + + // Get the connection state + getConnectionState: (c) => { + return { id: c.conn.id, ...c.conn.state }; + }, + + // Check all active connections + getConnectionIds: (c) => { + return c.conns.keys().toArray(); + }, + + // Get disconnection count + getDisconnectionCount: (c) => { + return c.state.disconnectionCount; + }, + + // Get all active connection states + getAllConnectionStates: (c) => { + return c.conns.entries().map(([id, conn]) => ({ id, ...conn.state })).toArray(); + }, + + // Send message to a specific connection with matching ID + sendToConnection: (c, targetId: string, message: string) => { + if (c.conns.has(targetId)) { + c.conns.get(targetId)!.send("directMessage", { from: c.conn.id, message }); + return true; + } else { + return false; + } + }, + + // Update connection state (simulated for tests) + updateConnection: ( + c, + updates: Partial<{ username: string; role: string }>, + ) => { + if (updates.username) c.conn.state.username = updates.username; + if (updates.role) c.conn.state.role = updates.role; + return c.conn.state; + }, + }, +}); + diff --git a/packages/core/fixtures/driver-test-suite/counter.ts b/packages/core/fixtures/driver-test-suite/counter.ts new file mode 100644 index 000000000..752aec3e4 --- /dev/null +++ b/packages/core/fixtures/driver-test-suite/counter.ts @@ -0,0 +1,16 @@ +import { actor } from "@rivetkit/core"; + +export const counter = actor({ + onAuth: () => {}, + state: { count: 0 }, + actions: { + increment: (c, x: number) => { + c.state.count += x; + c.broadcast("newCount", c.state.count); + return c.state.count; + }, + getCount: (c) => { + return c.state.count; + }, + }, +}); diff --git a/packages/core/fixtures/driver-test-suite/error-handling.ts b/packages/core/fixtures/driver-test-suite/error-handling.ts new file mode 100644 index 000000000..4f91c9a43 --- /dev/null +++ b/packages/core/fixtures/driver-test-suite/error-handling.ts @@ -0,0 +1,98 @@ +import { actor, UserError } from "@rivetkit/core"; + +export const errorHandlingActor = actor({ + onAuth: () => {}, + state: { + errorLog: [] as string[], + }, + actions: { + // Action that throws a UserError with just a message + throwSimpleError: () => { + throw new UserError("Simple error message"); + }, + + // Action that throws a UserError with code and metadata + throwDetailedError: () => { + throw new UserError("Detailed error message", { + code: "detailed_error", + metadata: { + reason: "test", + timestamp: Date.now(), + }, + }); + }, + + // Action that throws an internal error + throwInternalError: () => { + throw new Error("This is an internal error"); + }, + + // Action that returns successfully + successfulAction: () => { + return "success"; + }, + + // Action that times out (simulated with a long delay) + timeoutAction: async (c) => { + // This action should time out if the timeout is configured + return new Promise((resolve) => { + setTimeout(() => { + resolve("This should not be reached if timeout works"); + }, 10000); // 10 seconds + }); + }, + + // Action with configurable delay to test timeout edge cases + delayedAction: async (c, delayMs: number) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(`Completed after ${delayMs}ms`); + }, delayMs); + }); + }, + + // Log an error for inspection + logError: (c, error: string) => { + c.state.errorLog.push(error); + return c.state.errorLog; + }, + + // Get the error log + getErrorLog: (c) => { + return c.state.errorLog; + }, + + // Clear the error log + clearErrorLog: (c) => { + c.state.errorLog = []; + return true; + }, + }, + options: { + // Set a short timeout for this actor's actions + action: { + timeout: 500, // 500ms timeout for actions + }, + }, +}); + +// Actor with custom timeout +export const customTimeoutActor = actor({ + state: {}, + actions: { + quickAction: async () => { + await new Promise((resolve) => setTimeout(resolve, 50)); + return "Quick action completed"; + }, + slowAction: async () => { + await new Promise((resolve) => setTimeout(resolve, 300)); + return "Slow action completed"; + }, + }, + options: { + action: { + timeout: 200, // 200ms timeout + }, + }, +}); + diff --git a/packages/core/fixtures/driver-test-suite/lifecycle.ts b/packages/core/fixtures/driver-test-suite/lifecycle.ts new file mode 100644 index 000000000..23faf6f8b --- /dev/null +++ b/packages/core/fixtures/driver-test-suite/lifecycle.ts @@ -0,0 +1,37 @@ +import { actor } from "@rivetkit/core"; + +export const counterWithLifecycle = actor({ + onAuth: () => {}, + state: { + count: 0, + events: [] as string[], + }, + createConnState: ( + c, + opts: { params: { trackLifecycle?: boolean } | undefined }, + ) => ({ + joinTime: Date.now(), + }), + onStart: (c) => { + c.state.events.push("onStart"); + }, + onBeforeConnect: (c, conn) => { + if (conn.params?.trackLifecycle) c.state.events.push("onBeforeConnect"); + }, + onConnect: (c, conn) => { + if (conn.params?.trackLifecycle) c.state.events.push("onConnect"); + }, + onDisconnect: (c, conn) => { + if (conn.params?.trackLifecycle) c.state.events.push("onDisconnect"); + }, + actions: { + getEvents: (c) => { + return c.state.events; + }, + increment: (c, x: number) => { + c.state.count += x; + return c.state.count; + }, + }, +}); + diff --git a/packages/core/fixtures/driver-test-suite/metadata.ts b/packages/core/fixtures/driver-test-suite/metadata.ts new file mode 100644 index 000000000..ebba5e6cf --- /dev/null +++ b/packages/core/fixtures/driver-test-suite/metadata.ts @@ -0,0 +1,78 @@ +import { actor } from "@rivetkit/core"; + +// Note: For testing only - metadata API will need to be mocked +// in tests since this is implementation-specific +export const metadataActor = actor({ + onAuth: () => {}, + state: { + lastMetadata: null as any, + actorName: "", + // Store tags and region in state for testing since they may not be + // available in the context in all environments + storedTags: {} as Record, + storedRegion: null as string | null, + }, + onStart: (c) => { + // Store the actor name during initialization + c.state.actorName = c.name; + }, + actions: { + // Set up test tags - this will be called by tests to simulate tags + setupTestTags: (c, tags: Record) => { + c.state.storedTags = tags; + return tags; + }, + + // Set up test region - this will be called by tests to simulate region + setupTestRegion: (c, region: string) => { + c.state.storedRegion = region; + return region; + }, + + // Get all available metadata + getMetadata: (c) => { + // Create metadata object from stored values + const metadata = { + name: c.name, + tags: c.state.storedTags, + region: c.state.storedRegion, + }; + + // Store for later inspection + c.state.lastMetadata = metadata; + return metadata; + }, + + // Get the actor name + getActorName: (c) => { + return c.name; + }, + + // Get a specific tag by key + getTag: (c, key: string) => { + return c.state.storedTags[key] || null; + }, + + // Get all tags + getTags: (c) => { + return c.state.storedTags; + }, + + // Get the region + getRegion: (c) => { + return c.state.storedRegion; + }, + + // Get the stored actor name (from onStart) + getStoredActorName: (c) => { + return c.state.actorName; + }, + + // Get last retrieved metadata + getLastMetadata: (c) => { + return c.state.lastMetadata; + }, + }, +}); + + diff --git a/packages/core/fixtures/driver-test-suite/registry.ts b/packages/core/fixtures/driver-test-suite/registry.ts new file mode 100644 index 000000000..06bdcca40 --- /dev/null +++ b/packages/core/fixtures/driver-test-suite/registry.ts @@ -0,0 +1,82 @@ +import { setup } from "@rivetkit/core"; + +// Import actors from individual files +import { counter } from "./counter"; +import { counterWithLifecycle } from "./lifecycle"; +import { scheduled } from "./scheduled"; +import { errorHandlingActor, customTimeoutActor } from "./error-handling"; +import { inputActor } from "./action-inputs"; +import { + shortTimeoutActor, + longTimeoutActor, + defaultTimeoutActor, + syncTimeoutActor, +} from "./action-timeout"; +import { + syncActionActor, + asyncActionActor, + promiseActor, +} from "./action-types"; +import { counterWithParams } from "./conn-params"; +import { connStateActor } from "./conn-state"; +import { metadataActor } from "./metadata"; +import { + staticVarActor, + nestedVarActor, + dynamicVarActor, + uniqueVarActor, + driverCtxActor, +} from "./vars"; +import { + authActor, + intentAuthActor, + publicActor, + noAuthActor, + asyncAuthActor, +} from "./auth"; + +// Consolidated setup with all actors +export const registry = setup({ + actors: { + // From counter.ts + counter, + // From lifecycle.ts + counterWithLifecycle, + // From scheduled.ts + scheduled, + // From error-handling.ts + errorHandlingActor, + customTimeoutActor, + // From action-inputs.ts + inputActor, + // From action-timeout.ts + shortTimeoutActor, + longTimeoutActor, + defaultTimeoutActor, + syncTimeoutActor, + // From action-types.ts + syncActionActor, + asyncActionActor, + promiseActor, + // From conn-params.ts + counterWithParams, + // From conn-state.ts + connStateActor, + // From metadata.ts + metadataActor, + // From vars.ts + staticVarActor, + nestedVarActor, + dynamicVarActor, + uniqueVarActor, + driverCtxActor, + // From auth.ts + authActor, + intentAuthActor, + publicActor, + noAuthActor, + asyncAuthActor, + }, +}); + +export type Registry = typeof registry; diff --git a/packages/core/fixtures/driver-test-suite/scheduled.ts b/packages/core/fixtures/driver-test-suite/scheduled.ts new file mode 100644 index 000000000..58ce8b698 --- /dev/null +++ b/packages/core/fixtures/driver-test-suite/scheduled.ts @@ -0,0 +1,79 @@ +import { actor } from "@rivetkit/core"; + +export const scheduled = actor({ + onAuth: () => {}, + state: { + lastRun: 0, + scheduledCount: 0, + taskHistory: [] as string[], + }, + actions: { + // Schedule using 'at' with specific timestamp + scheduleTaskAt: (c, timestamp: number) => { + c.schedule.at(timestamp, "onScheduledTask"); + return timestamp; + }, + + // Schedule using 'after' with delay + scheduleTaskAfter: (c, delayMs: number) => { + c.schedule.after(delayMs, "onScheduledTask"); + return Date.now() + delayMs; + }, + + // Schedule with a task ID for ordering tests + scheduleTaskAfterWithId: (c, taskId: string, delayMs: number) => { + c.schedule.after(delayMs, "onScheduledTaskWithId", taskId); + return { taskId, scheduledFor: Date.now() + delayMs }; + }, + + // Original method for backward compatibility + scheduleTask: (c, delayMs: number) => { + const timestamp = Date.now() + delayMs; + c.schedule.at(timestamp, "onScheduledTask"); + return timestamp; + }, + + // Getters for state + getLastRun: (c) => { + return c.state.lastRun; + }, + + getScheduledCount: (c) => { + return c.state.scheduledCount; + }, + + getTaskHistory: (c) => { + return c.state.taskHistory; + }, + + clearHistory: (c) => { + c.state.taskHistory = []; + c.state.scheduledCount = 0; + c.state.lastRun = 0; + return true; + }, + + // Scheduled task handlers + onScheduledTask: (c) => { + c.state.lastRun = Date.now(); + c.state.scheduledCount++; + c.broadcast("scheduled", { + time: c.state.lastRun, + count: c.state.scheduledCount, + }); + }, + + onScheduledTaskWithId: (c, taskId: string) => { + c.state.lastRun = Date.now(); + c.state.scheduledCount++; + c.state.taskHistory.push(taskId); + c.broadcast("scheduledWithId", { + taskId, + time: c.state.lastRun, + count: c.state.scheduledCount, + }); + }, + }, +}); + + diff --git a/packages/core/fixtures/driver-test-suite/vars.ts b/packages/core/fixtures/driver-test-suite/vars.ts new file mode 100644 index 000000000..e5deb77d6 --- /dev/null +++ b/packages/core/fixtures/driver-test-suite/vars.ts @@ -0,0 +1,98 @@ +import { actor } from "@rivetkit/core"; + +// Actor with static vars +export const staticVarActor = actor({ + onAuth: () => {}, + state: { value: 0 }, + connState: { hello: "world" }, + vars: { counter: 42, name: "test-actor" }, + actions: { + getVars: (c) => { + return c.vars; + }, + getName: (c) => { + return c.vars.name; + }, + }, +}); + +// Actor with nested vars +export const nestedVarActor = actor({ + onAuth: () => {}, + state: { value: 0 }, + connState: { hello: "world" }, + vars: { + counter: 42, + nested: { + value: "original", + array: [1, 2, 3], + obj: { key: "value" }, + }, + }, + actions: { + getVars: (c) => { + return c.vars; + }, + modifyNested: (c) => { + // Attempt to modify the nested object + c.vars.nested.value = "modified"; + c.vars.nested.array.push(4); + c.vars.nested.obj.key = "new-value"; + return c.vars; + }, + }, +}); + +// Actor with dynamic vars +export const dynamicVarActor = actor({ + onAuth: () => {}, + state: { value: 0 }, + connState: { hello: "world" }, + createVars: () => { + return { + random: Math.random(), + computed: `Actor-${Math.floor(Math.random() * 1000)}`, + }; + }, + actions: { + getVars: (c) => { + return c.vars; + }, + }, +}); + +// Actor with unique vars per instance +export const uniqueVarActor = actor({ + onAuth: () => {}, + state: { value: 0 }, + connState: { hello: "world" }, + createVars: () => { + return { + id: Math.floor(Math.random() * 1000000), + }; + }, + actions: { + getVars: (c) => { + return c.vars; + }, + }, +}); + +// Actor that uses driver context +export const driverCtxActor = actor({ + onAuth: () => {}, + state: { value: 0 }, + connState: { hello: "world" }, + createVars: (c, driverCtx: any) => { + return { + hasDriverCtx: Boolean(driverCtx?.isTest), + }; + }, + actions: { + getVars: (c) => { + return c.vars; + }, + }, +}); + + diff --git a/packages/core/package.json b/packages/core/package.json index e90a30375..1c66882af 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -6,7 +6,7 @@ "rivetkit", "stateful", "serverless", - "workers", + "actors", "agents", "realtime", "websocket", @@ -48,12 +48,12 @@ }, "./errors": { "import": { - "types": "./dist/worker/errors.d.ts", - "default": "./dist/worker/errors.js" + "types": "./dist/actor/errors.d.ts", + "default": "./dist/actor/errors.js" }, "require": { - "types": "./dist/worker/errors.d.cts", - "default": "./dist/worker/errors.cjs" + "types": "./dist/actor/errors.d.cts", + "default": "./dist/actor/errors.cjs" } }, "./utils": { @@ -143,7 +143,7 @@ "sideEffects": false, "scripts": { "dev": "pnpm build --watch", - "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/common/websocket.ts src/worker/errors.ts src/topologies/coordinate/mod.ts src/topologies/partition/mod.ts src/utils.ts src/drivers/rivet/mod.ts src/driver-helpers/mod.ts src/driver-test-suite/mod.ts src/test/mod.ts", + "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/common/websocket.ts src/actor/errors.ts src/topologies/coordinate/mod.ts src/topologies/partition/mod.ts src/utils.ts src/drivers/rivet/mod.ts src/driver-helpers/mod.ts src/driver-test-suite/mod.ts src/test/mod.ts", "check-types": "tsc --noEmit", "boop": "tsc --outDir dist/test -d", "test": "vitest run", diff --git a/packages/core/scripts/dump-openapi.ts b/packages/core/scripts/dump-openapi.ts index d77872eb9..66eb7642b 100644 --- a/packages/core/scripts/dump-openapi.ts +++ b/packages/core/scripts/dump-openapi.ts @@ -1,9 +1,9 @@ import { createManagerRouter } from "@/manager/router"; import { RegistryConfig, RegistryConfigSchema, Encoding, setup } from "@/mod"; -import { ConnectionHandlers } from "@/worker/router-endpoints"; +import { ConnectionHandlers } from "@/actor/router-endpoints"; import { TestGlobalState, - TestWorkerDriver, + TestActorDriver, TestManagerDriver, } from "@/test/driver/mod"; import { OpenAPIHono } from "@hono/zod-openapi"; @@ -11,8 +11,8 @@ import { VERSION } from "@/utils"; import * as fs from "node:fs/promises"; import { resolve } from "node:path"; import { ClientDriver } from "@/client/client"; -import { WorkerQuery } from "@/manager/protocol/query"; -import { ToServer } from "@/worker/protocol/message/to-server"; +import { ActorQuery } from "@/manager/protocol/query"; +import { ToServer } from "@/actor/protocol/message/to-server"; import { EventSource } from "eventsource"; import { Context } from "hono"; import { @@ -23,7 +23,7 @@ import { function main() { const registryConfig: RegistryConfig = RegistryConfigSchema.parse({ - workers: {}, + actors: {}, }); const registry = setup(registryConfig); @@ -31,7 +31,7 @@ function main() { const driverConfig: RunConfig = RunConfigSchema.parse({ driver: { topology: "standalone", - worker: new TestWorkerDriver(memoryState), + actor: new TestActorDriver(memoryState), manager: new TestManagerDriver(memoryState), }, getUpgradeWebSocket: () => () => unimplemented(), @@ -54,7 +54,7 @@ function main() { const inlineClientDriver: ClientDriver = { action: unimplemented, - resolveWorkerId: unimplemented, + resolveActorId: unimplemented, connectWebSocket: unimplemented, connectSse: unimplemented, sendHttpMessage: unimplemented, diff --git a/packages/core/src/worker/action.ts b/packages/core/src/actor/action.ts similarity index 56% rename from packages/core/src/worker/action.ts rename to packages/core/src/actor/action.ts index ed9f49119..d2addd431 100644 --- a/packages/core/src/worker/action.ts +++ b/packages/core/src/actor/action.ts @@ -1,120 +1,120 @@ import type { Conn } from "./connection"; import type { Logger } from "@/common/log"; -import type { WorkerKey } from "@/common/utils"; +import type { ActorKey } from "@/common/utils"; import type { Schedule } from "./schedule"; import type { ConnId } from "./connection"; import type { SaveStateOptions } from "./instance"; -import type { WorkerContext } from "./context"; +import type { ActorContext } from "./context"; /** * Context for a remote procedure call. * - * @typeParam A Worker this action belongs to + * @typeParam A Actor this action belongs to */ export class ActionContext { - #workerContext: WorkerContext; + #actorContext: ActorContext; /** * Should not be called directly. * - * @param workerContext - The worker context + * @param actorContext - The actor context * @param conn - The connection associated with the action */ constructor( - workerContext: WorkerContext, + actorContext: ActorContext, public readonly conn: Conn, ) { - this.#workerContext = workerContext; + this.#actorContext = actorContext; } /** - * Get the worker state + * Get the actor state */ get state(): S { - return this.#workerContext.state; + return this.#actorContext.state; } /** - * Get the worker variables + * Get the actor variables */ get vars(): V { - return this.#workerContext.vars; + return this.#actorContext.vars; } /** * Broadcasts an event to all connected clients. */ broadcast(name: string, ...args: any[]): void { - this.#workerContext.broadcast(name, ...args); + this.#actorContext.broadcast(name, ...args); } /** * Gets the logger instance. */ get log(): Logger { - return this.#workerContext.log; + return this.#actorContext.log; } /** - * Gets worker ID. + * Gets actor ID. */ - get workerId(): string { - return this.#workerContext.workerId; + get actorId(): string { + return this.#actorContext.actorId; } /** - * Gets the worker name. + * Gets the actor name. */ get name(): string { - return this.#workerContext.name; + return this.#actorContext.name; } /** - * Gets the worker key. + * Gets the actor key. */ - get key(): WorkerKey { - return this.#workerContext.key; + get key(): ActorKey { + return this.#actorContext.key; } /** * Gets the region. */ get region(): string { - return this.#workerContext.region; + return this.#actorContext.region; } /** * Gets the scheduler. */ get schedule(): Schedule { - return this.#workerContext.schedule; + return this.#actorContext.schedule; } /** * Gets the map of connections. */ get conns(): Map> { - return this.#workerContext.conns; + return this.#actorContext.conns; } /** * @experimental */ get db(): DB { - return this.#workerContext.db; + return this.#actorContext.db; } /** * Forces the state to get saved. */ async saveState(opts: SaveStateOptions): Promise { - return this.#workerContext.saveState(opts); + return this.#actorContext.saveState(opts); } /** * Runs a promise in the background. */ runInBackground(promise: Promise): void { - return this.#workerContext.runInBackground(promise); + return this.#actorContext.runInBackground(promise); } } diff --git a/packages/core/src/worker/config.ts b/packages/core/src/actor/config.ts similarity index 78% rename from packages/core/src/worker/config.ts rename to packages/core/src/actor/config.ts index 8a6aeb917..cf178c500 100644 --- a/packages/core/src/worker/config.ts +++ b/packages/core/src/actor/config.ts @@ -1,14 +1,14 @@ import type { Conn } from "./connection"; import type { ActionContext } from "./action"; -import type { WorkerContext } from "./context"; +import type { ActorContext } from "./context"; import { z } from "zod"; -// This schema is used to validate the input at runtime. The generic types are defined below in `WorkerConfig`. +// This schema is used to validate the input at runtime. The generic types are defined below in `ActorConfig`. // // We don't use Zod generics with `z.custom` because: // (a) there seems to be a weird bug in either Zod, tsup, or TSC that causese external packages to have different types from `z.infer` than from within the same package and // (b) it makes the type definitions incredibly difficult to read as opposed to vanilla TypeScript. -export const WorkerConfigSchema = z +export const ActorConfigSchema = z .object({ onAuth: z.function().optional(), onCreate: z.function().optional(), @@ -103,7 +103,7 @@ type CreateState = | { state: S } | { createState: ( - c: WorkerContext< + c: ActorContext< undefined, undefined, undefined, @@ -126,7 +126,7 @@ type CreateConnState = | { connState: CS } | { createConnState: ( - c: WorkerContext< + c: ActorContext< undefined, undefined, undefined, @@ -158,7 +158,7 @@ type CreateVars = * @experimental */ createVars: ( - c: WorkerContext< + c: ActorContext< undefined, undefined, undefined, @@ -179,8 +179,8 @@ export interface Actions { ) => any; } -//export type WorkerConfig = BaseWorkerConfig & -// WorkerConfigLifecycle & +//export type ActorConfig = BaseActorConfig & +// ActorConfigLifecycle & // CreateState & // CreateConnState; @@ -198,7 +198,7 @@ interface OnAuthOptions { params: CP; } -interface BaseWorkerConfig< +interface BaseActorConfig< S, CP, CS, @@ -209,55 +209,55 @@ interface BaseWorkerConfig< R extends Actions, > { /** - * Called on the HTTP server before clients can interact with the worker. + * Called on the HTTP server before clients can interact with the actor. * - * Only called for public endpoints. Calls to workers from within the backend + * Only called for public endpoints. Calls to actors from within the backend * do not trigger this handler. * * Data returned from this handler will be available on `c.conn.auth`. * * This function is required for any public HTTP endpoint access. Use this hook * to validate client credentials and return authentication data that will be - * available on connections. This runs on the HTTP server (not the worker) - * in order to reduce load on the worker & prevent denial of server attacks - * against individual workers. + * available on connections. This runs on the HTTP server (not the actor) + * in order to reduce load on the actor & prevent denial of server attacks + * against individual actors. * - * If you need access to worker state for authentication, use onBeforeConnect + * If you need access to actor state for authentication, use onBeforeConnect * with an empty onAuth function instead. * * You can also provide your own authentication middleware on your router if you * choose, then use onAuth to pass the authentication data (e.g. user ID) to the - * worker itself. + * actor itself. * * @param opts Authentication options including request and intent * @returns Authentication data to attach to connections (must be serializable) - * @throws Throw an error to deny access to the worker + * @throws Throw an error to deny access to the actor */ onAuth?: (opts: OnAuthOptions) => AD | Promise; /** - * Called when the worker is first initialized. + * Called when the actor is first initialized. * - * Use this hook to initialize your worker's state. + * Use this hook to initialize your actor's state. * This is called before any other lifecycle hooks. */ onCreate?: ( - c: WorkerContext, + c: ActorContext, opts: OnCreateOptions, ) => void | Promise; /** - * Called when the worker is started and ready to receive connections and action. + * Called when the actor is started and ready to receive connections and action. * - * Use this hook to initialize resources needed for the worker's operation + * Use this hook to initialize resources needed for the actor's operation * (timers, external connections, etc.) * * @returns Void or a Promise that resolves when startup is complete */ - onStart?: (c: WorkerContext) => void | Promise; + onStart?: (c: ActorContext) => void | Promise; /** - * Called when the worker's state changes. + * Called when the actor's state changes. * * Use this hook to react to state changes, such as updating * external systems or triggering events. @@ -265,24 +265,24 @@ interface BaseWorkerConfig< * @param newState The updated state */ onStateChange?: ( - c: WorkerContext, + c: ActorContext, newState: S, ) => void; /** - * Called before a client connects to the worker. + * Called before a client connects to the actor. * * Unlike onAuth, this handler is still called for both internal and * public clients. * * Use this hook to determine if a connection should be accepted * and to initialize connection-specific state. Unlike onAuth, this runs - * on the worker and has access to worker state, but uses slightly - * more resources on the worker rather than authenticating with onAuth. + * on the actor and has access to actor state, but uses slightly + * more resources on the actor rather than authenticating with onAuth. * - * For authentication without worker state access, prefer onAuth. + * For authentication without actor state access, prefer onAuth. * - * For authentication with worker state, use onBeforeConnect with an empty + * For authentication with actor state, use onBeforeConnect with an empty * onAuth handler. * * @param opts Connection parameters including client-provided data @@ -290,35 +290,35 @@ interface BaseWorkerConfig< * @throws Throw an error to reject the connection */ onBeforeConnect?: ( - c: WorkerContext, + c: ActorContext, opts: OnConnectOptions, ) => void | Promise; /** - * Called when a client successfully connects to the worker. + * Called when a client successfully connects to the actor. * * Use this hook to perform actions when a connection is established, - * such as sending initial data or updating the worker's state. + * such as sending initial data or updating the actor's state. * * @param conn The connection object * @returns Void or a Promise that resolves when connection handling is complete */ onConnect?: ( - c: WorkerContext, + c: ActorContext, conn: Conn, ) => void | Promise; /** - * Called when a client disconnects from the worker. + * Called when a client disconnects from the actor. * * Use this hook to clean up resources associated with the connection - * or update the worker's state. + * or update the actor's state. * * @param conn The connection that is being closed * @returns Void or a Promise that resolves when disconnect handling is complete */ onDisconnect?: ( - c: WorkerContext, + c: ActorContext, conn: Conn, ) => void | Promise; @@ -335,7 +335,7 @@ interface BaseWorkerConfig< * @returns The modified output to send to the client */ onBeforeActionResponse?: ( - c: WorkerContext, + c: ActorContext, name: string, args: unknown[], output: Out, @@ -357,7 +357,7 @@ export type DatabaseFactory = (ctx: { onMigrate?: () => void | Promise; }>; -type WorkerDatabaseConfig = +type ActorDatabaseConfig = | { /** * @experimental @@ -369,8 +369,8 @@ type WorkerDatabaseConfig = // 1. Infer schema // 2. Omit keys that we'll manually define (because of generics) // 3. Define our own types that have generic constraints -export type WorkerConfig = Omit< - z.infer, +export type ActorConfig = Omit< + z.infer, | "actions" | "onAuth" | "onCreate" @@ -388,14 +388,14 @@ export type WorkerConfig = Omit< | "createVars" | "db" > & - BaseWorkerConfig> & + BaseActorConfig> & CreateState & CreateConnState & CreateVars & - WorkerDatabaseConfig; + ActorDatabaseConfig; -// See description on `WorkerConfig` -export type WorkerConfigInput< +// See description on `ActorConfig` +export type ActorConfigInput< S, CP, CS, @@ -405,7 +405,7 @@ export type WorkerConfigInput< DB, R extends Actions, > = Omit< - z.input, + z.input, | "actions" | "onAuth" | "onCreate" @@ -423,11 +423,11 @@ export type WorkerConfigInput< | "createVars" | "db" > & - BaseWorkerConfig & + BaseActorConfig & CreateState & CreateConnState & CreateVars & - WorkerDatabaseConfig; + ActorDatabaseConfig; // For testing type definitions: export function test< @@ -440,9 +440,9 @@ export function test< DB, R extends Actions, >( - input: WorkerConfigInput, -): WorkerConfig { - const config = WorkerConfigSchema.parse(input) as WorkerConfig< + input: ActorConfigInput, +): ActorConfig { + const config = ActorConfigSchema.parse(input) as ActorConfig< S, CP, CS, @@ -454,7 +454,7 @@ export function test< return config; } -export const testWorker = test({ +export const testActor = test({ state: { count: 0 }, // createState: () => ({ count: 0 }), actions: { diff --git a/packages/core/src/worker/conn-routing-handler.ts b/packages/core/src/actor/conn-routing-handler.ts similarity index 82% rename from packages/core/src/worker/conn-routing-handler.ts rename to packages/core/src/actor/conn-routing-handler.ts index ac80d3e8a..6b82138f4 100644 --- a/packages/core/src/worker/conn-routing-handler.ts +++ b/packages/core/src/actor/conn-routing-handler.ts @@ -4,7 +4,7 @@ import type { ConnectionHandlers as ConnHandlers } from "./router-endpoints"; import type { Context as HonoContext } from "hono"; /** - * Deterines how requests to workers should be routed. + * Deterines how requests to actors should be routed. * * Inline handlers calls the connection handlers directly. * @@ -27,29 +27,29 @@ export interface ConnRoutingHandlerCustom { proxyWebSocket: ProxyWebSocketHandler; } -export type BuildProxyEndpoint = (c: HonoContext, workerId: string) => string; +export type BuildProxyEndpoint = (c: HonoContext, actorId: string) => string; export type SendRequestHandler = ( - workerId: string, - workerRequest: Request, + actorId: string, + actorRequest: Request, ) => Promise; export type OpenWebSocketHandler = ( - workerId: string, + actorId: string, encodingKind: Encoding, params: unknown ) => Promise; export type ProxyRequestHandler = ( c: HonoContext, - workerRequest: Request, - workerId: string, + actorRequest: Request, + actorId: string, ) => Promise; export type ProxyWebSocketHandler = ( c: HonoContext, path: string, - workerId: string, + actorId: string, encoding: Encoding, connParams: unknown, authData: unknown, diff --git a/packages/core/src/worker/connection.ts b/packages/core/src/actor/connection.ts similarity index 86% rename from packages/core/src/worker/connection.ts rename to packages/core/src/actor/connection.ts index 897f35c1a..e4f305585 100644 --- a/packages/core/src/worker/connection.ts +++ b/packages/core/src/actor/connection.ts @@ -1,11 +1,11 @@ -import type { WorkerInstance } from "./instance"; +import type { ActorInstance } from "./instance"; import * as errors from "./errors"; import { generateSecureToken } from "./utils"; import { CachedSerializer } from "./protocol/serde"; import type { ConnDriver } from "./driver"; -import type * as messageToClient from "@/worker/protocol/message/to-client"; +import type * as messageToClient from "@/actor/protocol/message/to-client"; import type { PersistedConn } from "./persisted"; -import type * as wsToClient from "@/worker/protocol/message/to-client"; +import type * as wsToClient from "@/actor/protocol/message/to-client"; export function generateConnId(): string { return crypto.randomUUID(); @@ -20,7 +20,7 @@ export type ConnId = string; export type AnyConn = Conn; /** - * Represents a client connection to a worker. + * Represents a client connection to a actor. * * Manages connection-specific data and controls the connection lifecycle. * @@ -32,7 +32,7 @@ export class Conn { #stateEnabled: boolean; // TODO: Remove this cyclical reference - #worker: WorkerInstance; + #actor: ActorInstance; /** * The proxied state that notifies of changes automatically. @@ -98,17 +98,17 @@ export class Conn { /** * Initializes a new instance of the Connection class. * - * This should only be constructed by {@link Worker}. + * This should only be constructed by {@link Actor}. * * @protected */ public constructor( - worker: WorkerInstance, + actor: ActorInstance, persist: PersistedConn, driver: ConnDriver, stateEnabled: boolean, ) { - this.#worker = worker; + this.#actor = actor; this.__persist = persist; this.#driver = driver; this.#stateEnabled = stateEnabled; @@ -128,7 +128,7 @@ export class Conn { * @protected */ public _sendMessage(message: CachedSerializer) { - this.#driver.sendMessage?.(this.#worker, this, this.__persist.ds, message); + this.#driver.sendMessage?.(this.#actor, this, this.__persist.ds, message); } /** @@ -158,7 +158,7 @@ export class Conn { */ public async disconnect(reason?: string) { await this.#driver.disconnect( - this.#worker, + this.#actor, this, this.__persist.ds, reason, diff --git a/packages/core/src/worker/context.ts b/packages/core/src/actor/context.ts similarity index 57% rename from packages/core/src/worker/context.ts rename to packages/core/src/actor/context.ts index 6363e2a4b..6d10aa036 100644 --- a/packages/core/src/worker/context.ts +++ b/packages/core/src/actor/context.ts @@ -1,31 +1,31 @@ import type { Logger } from "@/common/log"; -import type { WorkerInstance, SaveStateOptions } from "./instance"; +import type { ActorInstance, SaveStateOptions } from "./instance"; import type { Conn, ConnId } from "./connection"; -import type { WorkerKey } from "@/common/utils"; +import type { ActorKey } from "@/common/utils"; import type { Schedule } from "./schedule"; /** - * WorkerContext class that provides access to worker methods and state + * ActorContext class that provides access to actor methods and state */ -export class WorkerContext { - #worker: WorkerInstance; +export class ActorContext { + #actor: ActorInstance; - constructor(worker: WorkerInstance) { - this.#worker = worker; + constructor(actor: ActorInstance) { + this.#actor = actor; } /** - * Get the worker state + * Get the actor state */ get state(): S { - return this.#worker.state; + return this.#actor.state; } /** - * Get the worker variables + * Get the actor variables */ get vars(): V { - return this.#worker.vars; + return this.#actor.vars; } /** @@ -34,7 +34,7 @@ export class WorkerContext { * @param args - The arguments to send with the event. */ broadcast>(name: string, ...args: Args): void { - this.#worker._broadcast(name, ...args); + this.#actor._broadcast(name, ...args); return; } @@ -42,49 +42,49 @@ export class WorkerContext { * Gets the logger instance. */ get log(): Logger { - return this.#worker.log; + return this.#actor.log; } /** - * Gets worker ID. + * Gets actor ID. */ - get workerId(): string { - return this.#worker.id; + get actorId(): string { + return this.#actor.id; } /** - * Gets the worker name. + * Gets the actor name. */ get name(): string { - return this.#worker.name; + return this.#actor.name; } /** - * Gets the worker key. + * Gets the actor key. */ - get key(): WorkerKey { - return this.#worker.key; + get key(): ActorKey { + return this.#actor.key; } /** * Gets the region. */ get region(): string { - return this.#worker.region; + return this.#actor.region; } /** * Gets the scheduler. */ get schedule(): Schedule { - return this.#worker.schedule; + return this.#actor.schedule; } /** * Gets the map of connections. */ get conns(): Map> { - return this.#worker.conns; + return this.#actor.conns; } /** @@ -93,7 +93,7 @@ export class WorkerContext { * @throws {DatabaseNotEnabled} If the database is not enabled. */ get db(): DB { - return this.#worker.db; + return this.#actor.db; } /** @@ -102,7 +102,7 @@ export class WorkerContext { * @param opts - Options for saving the state. */ async saveState(opts: SaveStateOptions): Promise { - return this.#worker.saveState(opts); + return this.#actor.saveState(opts); } /** @@ -111,7 +111,7 @@ export class WorkerContext { * @param promise - The promise to run in the background. */ runInBackground(promise: Promise): void { - this.#worker._runInBackground(promise); + this.#actor._runInBackground(promise); return; } } diff --git a/packages/core/src/actor/definition.ts b/packages/core/src/actor/definition.ts new file mode 100644 index 000000000..dd2d05fa0 --- /dev/null +++ b/packages/core/src/actor/definition.ts @@ -0,0 +1,74 @@ +import type { ActorConfig, Actions } from "./config"; +import { ActorInstance } from "./instance"; +import type { ActorContext } from "./context"; +import type { ActionContext } from "./action"; + +export type AnyActorDefinition = ActorDefinition< + any, + any, + any, + any, + any, + any, + any, + any +>; + +/** + * Extracts the context type from an ActorDefinition + */ +export type ActorContextOf = + AD extends ActorDefinition< + infer S, + infer CP, + infer CS, + infer V, + infer I, + infer AD, + infer DB, + any + > + ? ActorContext + : never; + +/** + * Extracts the context type from an ActorDefinition + */ +export type ActionContextOf = + AD extends ActorDefinition< + infer S, + infer CP, + infer CS, + infer V, + infer I, + infer AD, + infer DB, + any + > + ? ActionContext + : never; + +export class ActorDefinition< + S, + CP, + CS, + V, + I, + AD, + DB, + R extends Actions, +> { + #config: ActorConfig; + + constructor(config: ActorConfig) { + this.#config = config; + } + + get config(): ActorConfig { + return this.#config; + } + + instantiate(): ActorInstance { + return new ActorInstance(this.#config); + } +} diff --git a/packages/core/src/worker/driver.ts b/packages/core/src/actor/driver.ts similarity index 53% rename from packages/core/src/worker/driver.ts rename to packages/core/src/actor/driver.ts index ae51063fc..35873f05d 100644 --- a/packages/core/src/worker/driver.ts +++ b/packages/core/src/actor/driver.ts @@ -1,28 +1,28 @@ -import type * as messageToClient from "@/worker/protocol/message/to-client"; -import type { CachedSerializer } from "@/worker/protocol/serde"; -import type { AnyWorkerInstance } from "./instance"; +import type * as messageToClient from "@/actor/protocol/message/to-client"; +import type { CachedSerializer } from "@/actor/protocol/serde"; +import type { AnyActorInstance } from "./instance"; import { AnyConn } from "./connection"; export type ConnDrivers = Record; -export interface WorkerDriver { +export interface ActorDriver { //load(): Promise; - getContext(workerId: string): unknown; + getContext(actorId: string): unknown; - readInput(workerId: string): Promise; + readInput(actorId: string): Promise; - readPersistedData(workerId: string): Promise; - writePersistedData(workerId: string, unknown: unknown): Promise; + readPersistedData(actorId: string): Promise; + writePersistedData(actorId: string, unknown: unknown): Promise; // Schedule - setAlarm(worker: AnyWorkerInstance, timestamp: number): Promise; + setAlarm(actor: AnyActorInstance, timestamp: number): Promise; // Database /** * @experimental * This is an experimental API that may change in the future. */ - getDatabase(workerId: string): Promise; + getDatabase(actorId: string): Promise; // TODO: //destroy(): Promise; @@ -31,7 +31,7 @@ export interface WorkerDriver { export interface ConnDriver { sendMessage?( - worker: AnyWorkerInstance, + actor: AnyActorInstance, conn: AnyConn, state: ConnDriverState, message: CachedSerializer, @@ -41,7 +41,7 @@ export interface ConnDriver { * This returns a promise since we commonly disconnect at the end of a program, and not waiting will cause the socket to not close cleanly. */ disconnect( - worker: AnyWorkerInstance, + actor: AnyActorInstance, conn: AnyConn, state: ConnDriverState, reason?: string, diff --git a/packages/core/src/worker/errors.ts b/packages/core/src/actor/errors.ts similarity index 73% rename from packages/core/src/worker/errors.ts rename to packages/core/src/actor/errors.ts index f97b600cd..4eb8cd02b 100644 --- a/packages/core/src/worker/errors.ts +++ b/packages/core/src/actor/errors.ts @@ -2,38 +2,38 @@ import { DeconstructedError } from "@/common/utils"; export const INTERNAL_ERROR_CODE = "internal_error"; export const INTERNAL_ERROR_DESCRIPTION = - "Internal error. Read the worker logs for more details."; + "Internal error. Read the actor logs for more details."; export type InternalErrorMetadata = {}; export const USER_ERROR_CODE = "user_error"; -interface WorkerErrorOptions extends ErrorOptions { +interface ActorErrorOptions extends ErrorOptions { /** Error data can safely be serialized in a response to the client. */ public?: boolean; /** Metadata associated with this error. This will be sent to clients. */ metadata?: unknown; } -export class WorkerError extends Error { - __type = "WorkerError"; +export class ActorError extends Error { + __type = "ActorError"; public public: boolean; public metadata?: unknown; public statusCode = 500; - public static isWorkerError( + public static isActorError( error: unknown, - ): error is WorkerError | DeconstructedError { + ): error is ActorError | DeconstructedError { return ( typeof error === "object" && - (error as WorkerError | DeconstructedError).__type === "WorkerError" + (error as ActorError | DeconstructedError).__type === "ActorError" ); } constructor( public readonly code: string, message: string, - opts?: WorkerErrorOptions, + opts?: ActorErrorOptions, ) { super(message, { cause: opts?.cause }); this.public = opts?.public ?? false; @@ -62,7 +62,7 @@ export class WorkerError extends Error { } } -export class InternalError extends WorkerError { +export class InternalError extends ActorError { constructor(message: string) { super(INTERNAL_ERROR_CODE, message); } @@ -74,7 +74,7 @@ export class Unreachable extends InternalError { } } -export class StateNotEnabled extends WorkerError { +export class StateNotEnabled extends ActorError { constructor() { super( "state_not_enabled", @@ -83,7 +83,7 @@ export class StateNotEnabled extends WorkerError { } } -export class ConnStateNotEnabled extends WorkerError { +export class ConnStateNotEnabled extends ActorError { constructor() { super( "conn_state_not_enabled", @@ -92,7 +92,7 @@ export class ConnStateNotEnabled extends WorkerError { } } -export class VarsNotEnabled extends WorkerError { +export class VarsNotEnabled extends ActorError { constructor() { super( "vars_not_enabled", @@ -101,19 +101,19 @@ export class VarsNotEnabled extends WorkerError { } } -export class ActionTimedOut extends WorkerError { +export class ActionTimedOut extends ActorError { constructor() { super("action_timed_out", "Action timed out.", { public: true }); } } -export class ActionNotFound extends WorkerError { +export class ActionNotFound extends ActorError { constructor() { super("action_not_found", "Action not found.", { public: true }); } } -export class InvalidEncoding extends WorkerError { +export class InvalidEncoding extends ActorError { constructor(format?: string) { super("invalid_encoding", `Invalid encoding \`${format}\`.`, { public: true, @@ -121,7 +121,7 @@ export class InvalidEncoding extends WorkerError { } } -export class ConnNotFound extends WorkerError { +export class ConnNotFound extends ActorError { constructor(id?: string) { super("conn_not_found", `Connection not found for ID \`${id}\`.`, { public: true, @@ -129,7 +129,7 @@ export class ConnNotFound extends WorkerError { } } -export class IncorrectConnToken extends WorkerError { +export class IncorrectConnToken extends ActorError { constructor() { super("incorrect_conn_token", "Incorrect connection token.", { public: true, @@ -137,7 +137,7 @@ export class IncorrectConnToken extends WorkerError { } } -export class ConnParamsTooLong extends WorkerError { +export class ConnParamsTooLong extends ActorError { constructor() { super("conn_params_too_long", "Connection parameters too long.", { public: true, @@ -145,7 +145,7 @@ export class ConnParamsTooLong extends WorkerError { } } -export class MalformedConnParams extends WorkerError { +export class MalformedConnParams extends ActorError { constructor(cause: unknown) { super( "malformed_conn_params", @@ -155,13 +155,13 @@ export class MalformedConnParams extends WorkerError { } } -export class MessageTooLong extends WorkerError { +export class MessageTooLong extends ActorError { constructor() { super("message_too_long", "Message too long.", { public: true }); } } -export class MalformedMessage extends WorkerError { +export class MalformedMessage extends ActorError { constructor(cause?: unknown) { super("malformed_message", `Malformed message: ${cause}`, { public: true, @@ -174,7 +174,7 @@ export interface InvalidStateTypeOptions { path?: unknown; } -export class InvalidStateType extends WorkerError { +export class InvalidStateType extends ActorError { constructor(opts?: InvalidStateTypeOptions) { let msg = ""; if (opts?.path) { @@ -187,13 +187,13 @@ export class InvalidStateType extends WorkerError { } } -export class StateTooLarge extends WorkerError { +export class StateTooLarge extends ActorError { constructor() { super("state_too_large", "State too large."); } } -export class Unsupported extends WorkerError { +export class Unsupported extends ActorError { constructor(feature: string) { super("unsupported", `Unsupported feature: ${feature}`); } @@ -215,7 +215,7 @@ export interface UserErrorOptions extends ErrorOptions { } /** Error that can be safely returned to the user. */ -export class UserError extends WorkerError { +export class UserError extends ActorError { /** * Constructs a new UserError instance. * @@ -230,7 +230,7 @@ export class UserError extends WorkerError { } } -export class InvalidQueryJSON extends WorkerError { +export class InvalidQueryJSON extends ActorError { constructor(error?: unknown) { super("invalid_query_json", `Invalid query JSON: ${error}`, { public: true, @@ -239,7 +239,7 @@ export class InvalidQueryJSON extends WorkerError { } } -export class InvalidRequest extends WorkerError { +export class InvalidRequest extends ActorError { constructor(error?: unknown) { super("invalid_request", `Invalid request: ${error}`, { public: true, @@ -248,27 +248,27 @@ export class InvalidRequest extends WorkerError { } } -export class WorkerNotFound extends WorkerError { +export class ActorNotFound extends ActorError { constructor(identifier?: string) { super( - "worker_not_found", - identifier ? `Worker not found: ${identifier}` : "Worker not found", + "actor_not_found", + identifier ? `Actor not found: ${identifier}` : "Actor not found", { public: true }, ); } } -export class WorkerAlreadyExists extends WorkerError { +export class ActorAlreadyExists extends ActorError { constructor(name: string, key: string[]) { super( - "worker_already_exists", - `Worker already exists with name "${name}" and key ${JSON.stringify(key)}`, + "actor_already_exists", + `Actor already exists with name "${name}" and key ${JSON.stringify(key)}`, { public: true }, ); } } -export class ProxyError extends WorkerError { +export class ProxyError extends ActorError { constructor(operation: string, error?: unknown) { super("proxy_error", `Error proxying ${operation}: ${error}`, { public: true, @@ -277,26 +277,26 @@ export class ProxyError extends WorkerError { } } -export class InvalidActionRequest extends WorkerError { +export class InvalidActionRequest extends ActorError { constructor(message: string) { super("invalid_action_request", message, { public: true }); } } -export class InvalidParams extends WorkerError { +export class InvalidParams extends ActorError { constructor(message: string) { super("invalid_params", message, { public: true }); } } -export class Forbidden extends WorkerError { +export class Forbidden extends ActorError { constructor(message?: string) { super("forbidden", message ?? "Access denied", { public: true }); this.statusCode = 403; } } -export class DatabaseNotEnabled extends WorkerError { +export class DatabaseNotEnabled extends ActorError { constructor() { super( "database_not_enabled", diff --git a/packages/core/src/worker/instance.ts b/packages/core/src/actor/instance.ts similarity index 87% rename from packages/core/src/worker/instance.ts rename to packages/core/src/actor/instance.ts index ab078b371..948ec711f 100644 --- a/packages/core/src/worker/instance.ts +++ b/packages/core/src/actor/instance.ts @@ -1,13 +1,13 @@ import type { Logger } from "@/common//log"; import { - type WorkerKey, + type ActorKey, isJsonSerializable, stringifyError, } from "@/common//utils"; import onChange from "on-change"; -import type { WorkerConfig } from "./config"; +import type { ActorConfig } from "./config"; import { Conn, type ConnId } from "./connection"; -import type { WorkerDriver, ConnDrivers } from "./driver"; +import type { ActorDriver, ConnDrivers } from "./driver"; import type { ConnDriver } from "./driver"; import * as errors from "./errors"; import { processMessage } from "./protocol/message/mod"; @@ -15,13 +15,13 @@ import { instanceLogger, logger } from "./log"; import type { ActionContext } from "./action"; import { DeadlineError, Lock, deadline } from "./utils"; import { Schedule } from "./schedule"; -import type * as wsToClient from "@/worker/protocol/message/to-client"; -import type * as wsToServer from "@/worker/protocol/message/to-server"; +import type * as wsToClient from "@/actor/protocol/message/to-client"; +import type * as wsToServer from "@/actor/protocol/message/to-server"; import { CachedSerializer } from "./protocol/serde"; -import { WorkerContext } from "./context"; +import { ActorContext } from "./context"; import invariant from "invariant"; import type { - PersistedWorker, + PersistedActor, PersistedConn, PersistedScheduleEvents, } from "./persisted"; @@ -36,8 +36,8 @@ export interface SaveStateOptions { immediate?: boolean; } -/** Worker type alias with all `any` types. Used for `extends` in classes referencing this worker. */ -export type AnyWorkerInstance = WorkerInstance< +/** Actor type alias with all `any` types. Used for `extends` in classes referencing this actor. */ +export type AnyActorInstance = ActorInstance< // biome-ignore lint/suspicious/noExplicitAny: Needs to be used in `extends` any, // biome-ignore lint/suspicious/noExplicitAny: Needs to be used in `extends` @@ -54,8 +54,8 @@ export type AnyWorkerInstance = WorkerInstance< any >; -export type ExtractWorkerState = - A extends WorkerInstance< +export type ExtractActorState = + A extends ActorInstance< infer State, // biome-ignore lint/suspicious/noExplicitAny: Must be used for `extends` any, @@ -73,8 +73,8 @@ export type ExtractWorkerState = ? State : never; -export type ExtractWorkerConnParams = - A extends WorkerInstance< +export type ExtractActorConnParams = + A extends ActorInstance< // biome-ignore lint/suspicious/noExplicitAny: Must be used for `extends` any, infer ConnParams, @@ -92,8 +92,8 @@ export type ExtractWorkerConnParams = ? ConnParams : never; -export type ExtractWorkerConnState = - A extends WorkerInstance< +export type ExtractActorConnState = + A extends ActorInstance< // biome-ignore lint/suspicious/noExplicitAny: Must be used for `extends` any, // biome-ignore lint/suspicious/noExplicitAny: Must be used for `extends` @@ -111,9 +111,9 @@ export type ExtractWorkerConnState = ? ConnState : never; -export class WorkerInstance { - // Shared worker context for this instance - workerContext: WorkerContext; +export class ActorInstance { + // Shared actor context for this instance + actorContext: ActorContext; isStopping = false; #persistChanged = false; @@ -123,10 +123,10 @@ export class WorkerInstance { * * Any data that should be stored indefinitely should be held within this object. */ - #persist!: PersistedWorker; + #persist!: PersistedActor; /** Raw state without the proxy wrapper */ - #persistRaw!: PersistedWorker; + #persistRaw!: PersistedActor; #writePersistLock = new Lock(void 0); @@ -136,12 +136,12 @@ export class WorkerInstance { #vars?: V; #backgroundPromises: Promise[] = []; - #config: WorkerConfig; + #config: ActorConfig; #connectionDrivers!: ConnDrivers; - #workerDriver!: WorkerDriver; - #workerId!: string; + #actorDriver!: ActorDriver; + #actorId!: string; #name!: string; - #key!: WorkerKey; + #key!: ActorKey; #region!: string; #ready = false; @@ -150,41 +150,41 @@ export class WorkerInstance { #schedule!: Schedule; - // inspector!: WorkerInspector; + // inspector!: ActorInspector; #db!: DB; get id() { - return this.#workerId; + return this.#actorId; } /** * This constructor should never be used directly. * - * Constructed in {@link WorkerInstance.start}. + * Constructed in {@link ActorInstance.start}. * * @private */ - constructor(config: WorkerConfig) { + constructor(config: ActorConfig) { this.#config = config; - this.workerContext = new WorkerContext(this); + this.actorContext = new ActorContext(this); } async start( connectionDrivers: ConnDrivers, - workerDriver: WorkerDriver, - workerId: string, + actorDriver: ActorDriver, + actorId: string, name: string, - key: WorkerKey, + key: ActorKey, region: string, ) { this.#connectionDrivers = connectionDrivers; - this.#workerDriver = workerDriver; - this.#workerId = workerId; + this.#actorDriver = actorDriver; + this.#actorId = actorId; this.#name = name; this.#key = key; this.#region = region; this.#schedule = new Schedule(this); - // this.inspector = new WorkerInspector(this); + // this.inspector = new ActorInspector(this); // Initialize server // @@ -196,7 +196,7 @@ export class WorkerInstance { let vars: V | undefined = undefined; if ("createVars" in this.#config) { const dataOrPromise = this.#config.createVars( - this.workerContext as unknown as WorkerContext< + this.actorContext as unknown as ActorContext< undefined, undefined, undefined, @@ -205,7 +205,7 @@ export class WorkerInstance { undefined, undefined >, - this.#workerDriver.getContext(this.#workerId), + this.#actorDriver.getContext(this.#actorId), ); if (dataOrPromise instanceof Promise) { vars = await deadline( @@ -224,9 +224,9 @@ export class WorkerInstance { } // TODO: Exit process if this errors - logger().info("worker starting"); + logger().info("actor starting"); if (this.#config.onStart) { - const result = this.#config.onStart(this.workerContext); + const result = this.#config.onStart(this.actorContext); if (result instanceof Promise) { await result; } @@ -235,7 +235,7 @@ export class WorkerInstance { // Setup Database if ("db" in this.#config) { const db = await this.#config.db({ - createDatabase: () => workerDriver.getDatabase(this.#workerId), + createDatabase: () => actorDriver.getDatabase(this.#actorId), }); logger().info("database migration starting"); @@ -245,10 +245,10 @@ export class WorkerInstance { // Set alarm for next scheduled event if any exist after finishing initiation sequence if (this.#persist.e.length > 0) { - await this.#workerDriver.setAlarm(this, this.#persist.e[0].t); + await this.#actorDriver.setAlarm(this, this.#persist.e[0].t); } - logger().info("worker ready"); + logger().info("actor ready"); this.#ready = true; } @@ -266,7 +266,7 @@ export class WorkerInstance { ar: args, }; - this.workerContext.log.info("scheduling event", { + this.actorContext.log.info("scheduling event", { event: eventId, timestamp, action: fn, @@ -284,14 +284,14 @@ export class WorkerInstance { // - this is the newest event (i.e. at beginning of array) or // - this is the only event (i.e. the only event in the array) if (insertIndex === 0 || this.#persist.e.length === 1) { - this.workerContext.log.info("setting alarm", { timestamp }); - await this.#workerDriver.setAlarm(this, newEvent.t); + this.actorContext.log.info("setting alarm", { timestamp }); + await this.#actorDriver.setAlarm(this, newEvent.t); } } async onAlarm() { const now = Date.now(); - this.workerContext.log.debug("alarm triggered", { + this.actorContext.log.debug("alarm triggered", { now, events: this.#persist.e.length, }); @@ -299,23 +299,23 @@ export class WorkerInstance { // Remove events from schedule that we're about to run const runIndex = this.#persist.e.findIndex((x) => x.t <= now); if (runIndex === -1) { - this.workerContext.log.debug("no events to run", { now }); + this.actorContext.log.debug("no events to run", { now }); return; } const scheduleEvents = this.#persist.e.splice(0, runIndex + 1); - this.workerContext.log.debug("running events", { + this.actorContext.log.debug("running events", { count: scheduleEvents.length, }); // Set alarm for next event if (this.#persist.e.length > 0) { - await this.#workerDriver.setAlarm(this, this.#persist.e[0].t); + await this.#actorDriver.setAlarm(this, this.#persist.e[0].t); } // Iterate by event key in order to ensure we call the events in order for (const event of scheduleEvents) { try { - this.workerContext.log.info("running action for event", { + this.actorContext.log.info("running action for event", { event: event.e, timestamp: event.t, action: event.a, @@ -332,9 +332,9 @@ export class WorkerInstance { // Call function try { - await fn.call(undefined, this.workerContext, ...event.ar); + await fn.call(undefined, this.actorContext, ...event.ar); } catch (error) { - this.workerContext.log.error("error while running event", { + this.actorContext.log.error("error while running event", { error: stringifyError(error), event: event.e, timestamp: event.t, @@ -343,7 +343,7 @@ export class WorkerInstance { }); } } catch (error) { - this.workerContext.log.error("internal error while running event", { + this.actorContext.log.error("internal error while running event", { error: stringifyError(error), event: event.e, timestamp: event.t, @@ -418,8 +418,8 @@ export class WorkerInstance { this.#persistChanged = false; // Write to KV - await this.#workerDriver.writePersistedData( - this.#workerId, + await this.#actorDriver.writePersistedData( + this.#actorId, this.#persistRaw, ); @@ -437,7 +437,7 @@ export class WorkerInstance { /** * Creates proxy for `#persist` that handles automatically flagging when state needs to be updated. */ - #setPersist(target: PersistedWorker) { + #setPersist(target: PersistedActor) { // Set raw persist object this.#persistRaw = target; @@ -492,7 +492,7 @@ export class WorkerInstance { // Call onStateChange if it exists if (this.#config.onStateChange && this.#ready) { try { - this.#config.onStateChange(this.workerContext, this.#persistRaw.s); + this.#config.onStateChange(this.actorContext, this.#persistRaw.s); } catch (error) { logger().error("error in `_onStateChange`", { error: stringifyError(error), @@ -508,12 +508,12 @@ export class WorkerInstance { async #initialize() { // Read initial state - const persistData = (await this.#workerDriver.readPersistedData( - this.#workerId, - )) as PersistedWorker; + const persistData = (await this.#actorDriver.readPersistedData( + this.#actorId, + )) as PersistedActor; if (persistData !== undefined) { - logger().info("worker restoring", { + logger().info("actor restoring", { connections: persistData.c.length, }); @@ -538,21 +538,21 @@ export class WorkerInstance { } } } else { - logger().info("worker creating"); + logger().info("actor creating"); - const input = (await this.#workerDriver.readInput(this.#workerId)) as I; + const input = (await this.#actorDriver.readInput(this.#actorId)) as I; - // Initialize worker state + // Initialize actor state let stateData: unknown = undefined; if (this.stateEnabled) { - logger().info("worker state initializing"); + logger().info("actor state initializing"); if ("createState" in this.#config) { this.#config.createState; // Convert state to undefined since state is not defined yet here stateData = await this.#config.createState( - this.workerContext as unknown as WorkerContext< + this.actorContext as unknown as ActorContext< undefined, undefined, undefined, @@ -572,7 +572,7 @@ export class WorkerInstance { logger().debug("state not enabled"); } - const persist: PersistedWorker = { + const persist: PersistedActor = { s: stateData as S, c: [], e: [], @@ -580,13 +580,13 @@ export class WorkerInstance { // Update state logger().debug("writing state"); - await this.#workerDriver.writePersistedData(this.#workerId, persist); + await this.#actorDriver.writePersistedData(this.#actorId, persist); this.#setPersist(persist); // Notify creation if (this.#config.onCreate) { - await this.#config.onCreate(this.workerContext, { input }); + await this.#config.onCreate(this.actorContext, { input }); } } } @@ -626,7 +626,7 @@ export class WorkerInstance { // this.inspector.onConnChange(this.#connections); if (this.#config.onDisconnect) { try { - const result = this.#config.onDisconnect(this.workerContext, conn); + const result = this.#config.onDisconnect(this.actorContext, conn); if (result instanceof Promise) { // Handle promise but don't await it to prevent blocking result.catch((error) => { @@ -644,7 +644,7 @@ export class WorkerInstance { } async prepareConn( - // biome-ignore lint/suspicious/noExplicitAny: TypeScript bug with ExtractWorkerConnParams, + // biome-ignore lint/suspicious/noExplicitAny: TypeScript bug with ExtractActorConnParams, params: any, request?: Request, ): Promise { @@ -658,7 +658,7 @@ export class WorkerInstance { if (this.#config.onBeforeConnect) { await this.#config.onBeforeConnect( - this.workerContext, + this.actorContext, onBeforeConnectOpts, ); } @@ -666,7 +666,7 @@ export class WorkerInstance { if (this.#connStateEnabled) { if ("createConnState" in this.#config) { const dataOrPromise = this.#config.createConnState( - this.workerContext as unknown as WorkerContext< + this.actorContext as unknown as ActorContext< undefined, undefined, undefined, @@ -749,7 +749,7 @@ export class WorkerInstance { // Handle connection if (this.#config.onConnect) { try { - const result = this.#config.onConnect(this.workerContext, conn); + const result = this.#config.onConnect(this.actorContext, conn); if (result instanceof Promise) { deadline( result, @@ -873,7 +873,7 @@ export class WorkerInstance { } #assertReady() { - if (!this.#ready) throw new errors.InternalError("Worker not ready"); + if (!this.#ready) throw new errors.InternalError("Actor not ready"); } /** @@ -942,7 +942,7 @@ export class WorkerInstance { if (this.#config.onBeforeActionResponse) { try { const processedOutput = this.#config.onBeforeActionResponse( - this.workerContext, + this.actorContext, actionName, args, output, @@ -991,7 +991,7 @@ export class WorkerInstance { } /** - * Returns a list of action methods available on this worker. + * Returns a list of action methods available on this actor. */ get actions(): string[] { return Object.keys(this.#config.actions); @@ -1017,7 +1017,7 @@ export class WorkerInstance { /** * Gets the key. */ - get key(): WorkerKey { + get key(): ActorKey { return this.#key; } @@ -1110,7 +1110,7 @@ export class WorkerInstance { /** * Runs a promise in the background. * - * This allows the worker runtime to ensure that a promise completes while + * This allows the actor runtime to ensure that a promise completes while * returning from an action request early. * * @param promise - The promise to run in the background. @@ -1164,7 +1164,7 @@ export class WorkerInstance { async stop() { if (this.isStopping) { - logger().warn("already stopping worker"); + logger().warn("already stopping actor"); return; } this.isStopping = true; diff --git a/packages/core/src/actor/log.ts b/packages/core/src/actor/log.ts new file mode 100644 index 000000000..6f97702ca --- /dev/null +++ b/packages/core/src/actor/log.ts @@ -0,0 +1,16 @@ +import { getLogger } from "@/common//log"; + +/** Logger for this library. */ +export const RUNTIME_LOGGER_NAME = "actor-runtime"; + +/** Logger used for logs from the actor instance itself. */ +export const ACTOR_LOGGER_NAME = "actor"; + +export function logger() { + return getLogger(RUNTIME_LOGGER_NAME); +} + +export function instanceLogger() { + return getLogger(ACTOR_LOGGER_NAME); +} + diff --git a/packages/core/src/actor/mod.ts b/packages/core/src/actor/mod.ts new file mode 100644 index 000000000..857579462 --- /dev/null +++ b/packages/core/src/actor/mod.ts @@ -0,0 +1,45 @@ +import { + type ActorConfigInput, + ActorConfigSchema, + type Actions, + type ActorConfig, +} from "./config"; +import { ActorDefinition } from "./definition"; + +export type { ActorContext } from "./context"; +export { UserError, type UserErrorOptions } from "./errors"; +export type { Conn } from "./connection"; +export type { ActionContext } from "./action"; +export type { ActorConfig, OnConnectOptions } from "./config"; +export type { Encoding } from "@/actor/protocol/serde"; +export type { ActorKey } from "@/common/utils"; +export type { + ActorDefinition, + AnyActorDefinition, + ActorContextOf, + ActionContextOf, +} from "./definition"; + +export function actor< + S, + CP, + CS, + V, + I, + AD, + DB, + R extends Actions, +>( + input: ActorConfigInput, +): ActorDefinition { + const config = ActorConfigSchema.parse(input) as ActorConfig< + S, + CP, + CS, + V, + I, + AD, + DB + >; + return new ActorDefinition(config); +} diff --git a/packages/core/src/worker/persisted.ts b/packages/core/src/actor/persisted.ts similarity index 94% rename from packages/core/src/worker/persisted.ts rename to packages/core/src/actor/persisted.ts index cd33563f5..1c4c55762 100644 --- a/packages/core/src/worker/persisted.ts +++ b/packages/core/src/actor/persisted.ts @@ -1,5 +1,5 @@ /** State object that gets automatically persisted to storage. */ -export interface PersistedWorker { +export interface PersistedActor { // State s: S; // Connections diff --git a/packages/core/src/worker/protocol/http/action.ts b/packages/core/src/actor/protocol/http/action.ts similarity index 100% rename from packages/core/src/worker/protocol/http/action.ts rename to packages/core/src/actor/protocol/http/action.ts diff --git a/packages/core/src/worker/protocol/http/error.ts b/packages/core/src/actor/protocol/http/error.ts similarity index 100% rename from packages/core/src/worker/protocol/http/error.ts rename to packages/core/src/actor/protocol/http/error.ts diff --git a/packages/core/src/worker/protocol/http/resolve.ts b/packages/core/src/actor/protocol/http/resolve.ts similarity index 92% rename from packages/core/src/worker/protocol/http/resolve.ts rename to packages/core/src/actor/protocol/http/resolve.ts index 3f044ac49..54c124539 100644 --- a/packages/core/src/worker/protocol/http/resolve.ts +++ b/packages/core/src/actor/protocol/http/resolve.ts @@ -1,7 +1,7 @@ import { z } from "zod"; export const ResolveResponseSchema = z.object({ - // Worker ID + // Actor ID i: z.string(), }); diff --git a/packages/core/src/worker/protocol/message/mod.ts b/packages/core/src/actor/protocol/message/mod.ts similarity index 92% rename from packages/core/src/worker/protocol/message/mod.ts rename to packages/core/src/actor/protocol/message/mod.ts index 0d9619e19..b50381471 100644 --- a/packages/core/src/worker/protocol/message/mod.ts +++ b/packages/core/src/actor/protocol/message/mod.ts @@ -1,6 +1,6 @@ -import type * as wsToClient from "@/worker/protocol/message/to-client"; -import * as wsToServer from "@/worker/protocol/message/to-server"; -import type { WorkerInstance } from "../../instance"; +import type * as wsToClient from "@/actor/protocol/message/to-client"; +import * as wsToServer from "@/actor/protocol/message/to-server"; +import type { ActorInstance } from "../../instance"; import type { Conn } from "../../connection"; import * as errors from "../../errors"; import { logger } from "../../log"; @@ -12,13 +12,13 @@ import { type Encoding, type InputData, CachedSerializer, -} from "@/worker/protocol/serde"; +} from "@/actor/protocol/serde"; import { deconstructError } from "@/common/utils"; export const TransportSchema = z.enum(["websocket", "sse"]); /** - * Transport mechanism used to communicate between client & worker. + * Transport mechanism used to communicate between client & actor. */ export type Transport = z.infer; @@ -85,7 +85,7 @@ export interface ProcessMessageHandler { export async function processMessage( message: wsToServer.ToServer, - worker: WorkerInstance, + actor: ActorInstance, conn: Conn, handler: ProcessMessageHandler, ) { @@ -112,7 +112,7 @@ export async function processMessage( }); const ctx = new ActionContext( - worker.workerContext, + actor.actorContext, conn, ); diff --git a/packages/core/src/worker/protocol/message/to-client.ts b/packages/core/src/actor/protocol/message/to-client.ts similarity index 98% rename from packages/core/src/worker/protocol/message/to-client.ts rename to packages/core/src/actor/protocol/message/to-client.ts index b24c517cc..a0a5330a9 100644 --- a/packages/core/src/worker/protocol/message/to-client.ts +++ b/packages/core/src/actor/protocol/message/to-client.ts @@ -2,7 +2,7 @@ import { z } from "zod"; // Only called for SSE because we don't need this for WebSockets export const InitSchema = z.object({ - // Worker ID + // Actor ID ai: z.string(), // Connection ID ci: z.string(), diff --git a/packages/core/src/worker/protocol/message/to-server.ts b/packages/core/src/actor/protocol/message/to-server.ts similarity index 100% rename from packages/core/src/worker/protocol/message/to-server.ts rename to packages/core/src/actor/protocol/message/to-server.ts diff --git a/packages/core/src/worker/protocol/serde.ts b/packages/core/src/actor/protocol/serde.ts similarity index 96% rename from packages/core/src/worker/protocol/serde.ts rename to packages/core/src/actor/protocol/serde.ts index 41a3532fe..cc2e31022 100644 --- a/packages/core/src/worker/protocol/serde.ts +++ b/packages/core/src/actor/protocol/serde.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import * as errors from "@/worker/errors"; +import * as errors from "@/actor/errors"; import { logger } from "../log"; import { assertUnreachable } from "../utils"; import * as cbor from "cbor-x"; @@ -13,7 +13,7 @@ export type OutputData = string | Uint8Array; export const EncodingSchema = z.enum(["json", "cbor"]); /** - * Encoding used to communicate between the client & worker. + * Encoding used to communicate between the client & actor. */ export type Encoding = z.infer; diff --git a/packages/core/src/worker/router-endpoints.ts b/packages/core/src/actor/router-endpoints.ts similarity index 92% rename from packages/core/src/worker/router-endpoints.ts rename to packages/core/src/actor/router-endpoints.ts index 38beff692..429439d8f 100644 --- a/packages/core/src/worker/router-endpoints.ts +++ b/packages/core/src/actor/router-endpoints.ts @@ -8,11 +8,11 @@ import { EncodingSchema, serialize, deserialize, -} from "@/worker/protocol/serde"; -import { parseMessage } from "@/worker/protocol/message/mod"; -import * as protoHttpAction from "@/worker/protocol/http/action"; -import type * as messageToServer from "@/worker/protocol/message/to-server"; -import type { InputData } from "@/worker/protocol/serde"; +} from "@/actor/protocol/serde"; +import { parseMessage } from "@/actor/protocol/message/mod"; +import * as protoHttpAction from "@/actor/protocol/http/action"; +import type * as messageToServer from "@/actor/protocol/message/to-server"; +import type { InputData } from "@/actor/protocol/serde"; import { assertUnreachable } from "./utils"; import { deconstructError, stringifyError } from "@/common/utils"; import type { RegistryConfig } from "@/registry/config"; @@ -21,7 +21,7 @@ import { DriverConfig, RunConfig } from "@/registry/run-config"; export interface ConnectWebSocketOpts { req?: HonoRequest; encoding: Encoding; - workerId: string; + actorId: string; params: unknown; authData: unknown; } @@ -36,7 +36,7 @@ export interface ConnectSseOpts { req?: HonoRequest; encoding: Encoding; params: unknown; - workerId: string; + actorId: string; authData: unknown; } @@ -50,7 +50,7 @@ export interface ActionOpts { params: unknown; actionName: string; actionArgs: unknown[]; - workerId: string; + actorId: string; authData: unknown; } @@ -63,11 +63,11 @@ export interface ConnsMessageOpts { connId: string; connToken: string; message: messageToServer.ToServer; - workerId: string; + actorId: string; } /** - * Shared interface for connection handlers used by both WorkerRouterHandler and ManagerRouterHandler + * Shared interface for connection handlers used by both ActorRouterHandler and ManagerRouterHandler */ export interface ConnectionHandlers { onConnectWebSocket?( @@ -86,7 +86,7 @@ export function handleWebSocketConnect( registryConfig: RegistryConfig, runConfig: RunConfig, handler: (opts: ConnectWebSocketOpts) => Promise, - workerId: string, + actorId: string, encoding: Encoding, params: unknown, authData: unknown, @@ -110,7 +110,7 @@ export function handleWebSocketConnect( req: context.req, encoding, params, - workerId, + actorId, authData, }); @@ -197,7 +197,7 @@ export function handleWebSocketConnect( }, onError: async (_error: unknown) => { try { - // Workers don't need to know about this, since it's abstracted away + // Actors don't need to know about this, since it's abstracted away logger().warn("websocket error"); } catch (error) { deconstructError( @@ -219,7 +219,7 @@ export async function handleSseConnect( registryConfig: RegistryConfig, runConfig: RunConfig, handler: (opts: ConnectSseOpts) => Promise, - workerId: string, + actorId: string, authData: unknown, ) { const encoding = getRequestEncoding(c.req); @@ -229,7 +229,7 @@ export async function handleSseConnect( req: c.req, encoding, params: parameters, - workerId, + actorId, authData, }); @@ -278,7 +278,7 @@ export async function handleAction( runConfig: RunConfig, handler: (opts: ActionOpts) => Promise, actionName: string, - workerId: string, + actorId: string, authData: unknown, ) { const encoding = getRequestEncoding(c.req); @@ -332,7 +332,7 @@ export async function handleAction( params: parameters, actionName: actionName, actionArgs: actionArgs, - workerId, + actorId, authData, }); @@ -364,7 +364,7 @@ export async function handleConnectionMessage( handler: (opts: ConnsMessageOpts) => Promise, connId: string, connToken: string, - workerId: string, + actorId: string, ) { const encoding = getRequestEncoding(c.req); @@ -398,7 +398,7 @@ export async function handleConnectionMessage( connId, connToken, message, - workerId, + actorId, }); return c.json({}); @@ -429,8 +429,8 @@ export function getRequestExposeInternalError(req: HonoRequest): boolean { } export function getRequestQuery(c: HonoContext): unknown { - // Get query parameters for worker lookup - const queryParam = c.req.header(HEADER_WORKER_QUERY); + // Get query parameters for actor lookup + const queryParam = c.req.header(HEADER_ACTOR_QUERY); if (!queryParam) { logger().error("missing query parameter"); throw new errors.InvalidRequest("missing query"); @@ -446,7 +446,7 @@ export function getRequestQuery(c: HonoContext): unknown { } } -export const HEADER_WORKER_QUERY = "X-RivetKit-Query"; +export const HEADER_ACTOR_QUERY = "X-RivetKit-Query"; export const HEADER_ENCODING = "X-RivetKit-Encoding"; @@ -459,7 +459,7 @@ export const HEADER_CONN_PARAMS = "X-RivetKit-Conn-Params"; // Internal header export const HEADER_AUTH_DATA = "X-RivetKit-Auth-Data"; -export const HEADER_WORKER_ID = "X-RivetKit-Worker"; +export const HEADER_ACTOR_ID = "X-RivetKit-Actor"; export const HEADER_CONN_ID = "X-RivetKit-Conn"; @@ -471,10 +471,10 @@ export const HEADER_CONN_TOKEN = "X-RivetKit-Conn-Token"; * Used for CORS. **/ export const ALL_PUBLIC_HEADERS = [ - HEADER_WORKER_QUERY, + HEADER_ACTOR_QUERY, HEADER_ENCODING, HEADER_CONN_PARAMS, - HEADER_WORKER_ID, + HEADER_ACTOR_ID, HEADER_CONN_ID, HEADER_CONN_TOKEN, ]; diff --git a/packages/core/src/actor/schedule.ts b/packages/core/src/actor/schedule.ts new file mode 100644 index 000000000..8948e8ccf --- /dev/null +++ b/packages/core/src/actor/schedule.ts @@ -0,0 +1,17 @@ +import type { AnyActorInstance } from "./instance"; + +export class Schedule { + #actor: AnyActorInstance; + + constructor(actor: AnyActorInstance) { + this.#actor = actor; + } + + async after(duration: number, fn: string, ...args: unknown[]) { + await this.#actor.scheduleEvent(Date.now() + duration, fn, args); + } + + async at(timestamp: number, fn: string, ...args: unknown[]) { + await this.#actor.scheduleEvent(timestamp, fn, args); + } +} diff --git a/packages/core/src/worker/unstable-react.ts b/packages/core/src/actor/unstable-react.ts similarity index 94% rename from packages/core/src/worker/unstable-react.ts rename to packages/core/src/actor/unstable-react.ts index d7d8aa8ca..0719a56df 100644 --- a/packages/core/src/worker/unstable-react.ts +++ b/packages/core/src/actor/unstable-react.ts @@ -2,21 +2,21 @@ //import { renderToPipeableStream } from "@jogit/tmp-react-server-dom-nodeless"; //import getStream from "get-stream"; //import { isValidElement } from "react"; -//import { Worker } from "./worker"; +//import { Actor } from "./actor"; // ///** -// * A React Server Components (RSC) worker. +// * A React Server Components (RSC) actor. // * // * Supports rendering React elements as action responses. // * // * @see [Documentation](https://rivet.gg/docs/client/react) // * @experimental // */ -//export class RscWorker< +//export class RscActor< // State = undefined, // ConnParams = undefined, // ConnState = undefined, -//> extends Worker { +//> extends Actor { // /** // * Updates the RSCs for all connected clients. // */ diff --git a/packages/core/src/worker/utils.ts b/packages/core/src/actor/utils.ts similarity index 100% rename from packages/core/src/worker/utils.ts rename to packages/core/src/actor/utils.ts diff --git a/packages/core/src/client/worker-common.ts b/packages/core/src/client/actor-common.ts similarity index 51% rename from packages/core/src/client/worker-common.ts rename to packages/core/src/client/actor-common.ts index 4d17862fb..0a5082c7c 100644 --- a/packages/core/src/client/worker-common.ts +++ b/packages/core/src/client/actor-common.ts @@ -1,18 +1,18 @@ import type { - AnyWorkerDefinition, - WorkerDefinition, -} from "@/worker/definition"; + AnyActorDefinition, + ActorDefinition, +} from "@/actor/definition"; /** - * Action function returned by Worker connections and handles. + * Action function returned by Actor connections and handles. * - * @typedef {Function} WorkerActionFunction + * @typedef {Function} ActorActionFunction * @template Args * @template Response * @param {...Args} args - Arguments for the action function. * @returns {Promise} */ -export type WorkerActionFunction< +export type ActorActionFunction< Args extends Array = unknown[], Response = unknown, > = ( @@ -20,14 +20,14 @@ export type WorkerActionFunction< ) => Promise; /** - * Maps action methods from worker definition to typed function signatures. + * Maps action methods from actor definition to typed function signatures. */ -export type WorkerDefinitionActions = +export type ActorDefinitionActions = // biome-ignore lint/suspicious/noExplicitAny: safe to use any here - AD extends WorkerDefinition + AD extends ActorDefinition ? { [K in keyof R]: R[K] extends (...args: infer Args) => infer Return - ? WorkerActionFunction + ? ActorActionFunction : never; } : never; diff --git a/packages/core/src/client/worker-conn.ts b/packages/core/src/client/actor-conn.ts similarity index 89% rename from packages/core/src/client/worker-conn.ts rename to packages/core/src/client/actor-conn.ts index adc6fbec6..a2b690c98 100644 --- a/packages/core/src/client/worker-conn.ts +++ b/packages/core/src/client/actor-conn.ts @@ -1,18 +1,18 @@ -import type { AnyWorkerDefinition } from "@/worker/definition"; -import type { Transport } from "@/worker/protocol/message/mod"; -import type * as wsToClient from "@/worker/protocol/message/to-client"; -import type * as wsToServer from "@/worker/protocol/message/to-server"; -import type { Encoding } from "@/worker/protocol/serde"; +import type { AnyActorDefinition } from "@/actor/definition"; +import type { Transport } from "@/actor/protocol/message/mod"; +import type * as wsToClient from "@/actor/protocol/message/to-client"; +import type * as wsToServer from "@/actor/protocol/message/to-server"; +import type { Encoding } from "@/actor/protocol/serde"; import { importEventSource } from "@/common/eventsource"; import { MAX_CONN_PARAMS_SIZE } from "@/common/network"; import { httpUserAgent } from "@/utils"; import { assertUnreachable, stringifyError } from "@/common/utils"; import { importWebSocket } from "@/common/websocket"; -import type { WorkerQuery } from "@/manager/protocol/query"; +import type { ActorQuery } from "@/manager/protocol/query"; import * as cbor from "cbor-x"; import pRetry from "p-retry"; import { - WORKER_CONNS_SYMBOL, + ACTOR_CONNS_SYMBOL, ClientDriver, type ClientRaw, TRANSPORT_SYMBOL, @@ -25,15 +25,15 @@ import { serializeWithEncoding, } from "./utils"; import { - HEADER_WORKER_ID, - HEADER_WORKER_QUERY, + HEADER_ACTOR_ID, + HEADER_ACTOR_QUERY, HEADER_CONN_ID, HEADER_CONN_TOKEN, HEADER_ENCODING, HEADER_CONN_PARAMS, -} from "@/worker/router-endpoints"; +} from "@/actor/router-endpoints"; import type { EventSource } from "eventsource"; -import { WorkerDefinitionActions } from "./worker-common"; +import { ActorDefinitionActions } from "./actor-common"; import type { WebSocket, CloseEvent, ErrorEvent } from "ws"; interface ActionInFlight { @@ -57,9 +57,9 @@ export type EventUnsubscribe = () => void; /** * A function that handles connection errors. * - * @typedef {Function} WorkerErrorCallback + * @typedef {Function} ActorErrorCallback */ -export type WorkerErrorCallback = (error: errors.WorkerError) => void; +export type ActorErrorCallback = (error: errors.ActorError) => void; export interface SendHttpMessageOpts { ephemeral: boolean; @@ -71,11 +71,11 @@ export type ConnTransport = { websocket: WebSocket } | { sse: EventSource }; export const CONNECT_SYMBOL = Symbol("connect"); /** - * Provides underlying functions for {@link WorkerConn}. See {@link WorkerConn} for using type-safe remote procedure calls. + * Provides underlying functions for {@link ActorConn}. See {@link ActorConn} for using type-safe remote procedure calls. * - * @see {@link WorkerConn} + * @see {@link ActorConn} */ -export class WorkerConnRaw { +export class ActorConnRaw { #disposed = false; /* Will be aborted on dispose. */ @@ -85,7 +85,7 @@ export class WorkerConnRaw { #connecting = false; // These will only be set on SSE driver - #workerId?: string; + #actorId?: string; #connectionId?: string; #connectionToken?: string; @@ -97,7 +97,7 @@ export class WorkerConnRaw { // biome-ignore lint/suspicious/noExplicitAny: Unknown subscription type #eventSubscriptions = new Map>>(); - #errorHandlers = new Set(); + #errorHandlers = new Set(); #actionIdCounter = 0; @@ -115,14 +115,14 @@ export class WorkerConnRaw { #driver: ClientDriver; #params: unknown; #encodingKind: Encoding; - #workerQuery: WorkerQuery; + #actorQuery: ActorQuery; // TODO: ws message queue /** * Do not call this directly. * - * Creates an instance of WorkerConnRaw. + * Creates an instance of ActorConnRaw. * * @protected */ @@ -131,21 +131,21 @@ export class WorkerConnRaw { private driver: ClientDriver, private params: unknown, private encodingKind: Encoding, - private workerQuery: WorkerQuery, + private actorQuery: ActorQuery, ) { this.#client = client; this.#driver = driver; this.#params = params; this.#encodingKind = encodingKind; - this.#workerQuery = workerQuery; + this.#actorQuery = actorQuery; this.#keepNodeAliveInterval = setInterval(() => 60_000); } /** - * Call a raw action connection. See {@link WorkerConn} for type-safe action calls. + * Call a raw action connection. See {@link ActorConn} for type-safe action calls. * - * @see {@link WorkerConn} + * @see {@link ActorConn} * @template Args - The type of arguments to pass to the action function. * @template Response - The type of the response returned by the action function. * @param {string} name - The name of the action function to call. @@ -262,7 +262,7 @@ enc async #connectWebSocket({ signal }: { signal?: AbortSignal } = {}) { const ws = await this.#driver.connectWebSocket( undefined, - this.#workerQuery, + this.#actorQuery, this.#encodingKind, this.#params, signal ? { signal } : undefined, @@ -285,7 +285,7 @@ enc async #connectSse({ signal }: { signal?: AbortSignal } = {}) { const eventSource = await this.#driver.connectSse( undefined, - this.#workerQuery, + this.#actorQuery, this.#encodingKind, this.#params, signal ? { signal } : undefined, @@ -354,11 +354,11 @@ enc if ("i" in response.b) { // This is only called for SSE - this.#workerId = response.b.i.ai; + this.#actorId = response.b.i.ai; this.#connectionId = response.b.i.ci; this.#connectionToken = response.b.i.ct; logger().trace("received init message", { - workerId: this.#workerId, + actorId: this.#actorId, connectionId: this.#connectionId, }); this.#handleOnOpen(); @@ -377,7 +377,7 @@ enc metadata, }); - inFlight.reject(new errors.WorkerError(code, message, metadata)); + inFlight.reject(new errors.ActorError(code, message, metadata)); } else { logger().warn("connection error", { code, @@ -386,21 +386,21 @@ enc }); // Create a connection error - const workerError = new errors.WorkerError(code, message, metadata); + const actorError = new errors.ActorError(code, message, metadata); // If we have an onOpenPromise, reject it with the error if (this.#onOpenPromise) { - this.#onOpenPromise.reject(workerError); + this.#onOpenPromise.reject(actorError); } // Reject any in-flight requests for (const [id, inFlight] of this.#actionsInFlight.entries()) { - inFlight.reject(workerError); + inFlight.reject(actorError); this.#actionsInFlight.delete(id); } // Dispatch to error handler if registered - this.#dispatchWorkerError(workerError); + this.#dispatchActorError(actorError); } } else if ("ar" in response.b) { // Action response OK @@ -459,7 +459,7 @@ enc // Automatically reconnect. Skip if already attempting to connect. if (!this.#disposed && !this.#connecting) { - // TODO: Fetch worker to check if it's destroyed + // TODO: Fetch actor to check if it's destroyed // TODO: Add backoff for reconnect // TODO: Add a way of preserving connection ID for connection state @@ -507,7 +507,7 @@ enc } } - #dispatchWorkerError(error: errors.WorkerError) { + #dispatchActorError(error: errors.ActorError) { // Call all registered error handlers for (const handler of [...this.#errorHandlers]) { try { @@ -586,10 +586,10 @@ enc /** * Subscribes to connection errors. * - * @param {WorkerErrorCallback} callback - The callback function to execute when a connection error occurs. + * @param {ActorErrorCallback} callback - The callback function to execute when a connection error occurs. * @returns {() => void} - A function to unsubscribe from the error handler. */ - onError(callback: WorkerErrorCallback): () => void { + onError(callback: ActorErrorCallback): () => void { this.#errorHandlers.add(callback); // Return unsubscribe function @@ -600,7 +600,7 @@ enc #sendMessage(message: wsToServer.ToServer, opts?: SendHttpMessageOpts) { if (this.#disposed) { - throw new errors.WorkerConnDisposed(); + throw new errors.ActorConnDisposed(); } let queueMessage = false; @@ -651,7 +651,7 @@ enc opts?: SendHttpMessageOpts, ) { try { - if (!this.#workerId || !this.#connectionId || !this.#connectionToken) + if (!this.#actorId || !this.#connectionId || !this.#connectionToken) throw new errors.InternalError("Missing connection ID or token."); logger().trace("sent http message", { @@ -660,7 +660,7 @@ enc const res = await this.#driver.sendHttpMessage( undefined, - this.#workerId, + this.#actorId, this.#encodingKind, this.#connectionId, this.#connectionToken, @@ -738,7 +738,7 @@ enc } /** - * Disconnects from the worker. + * Disconnects from the actor. * * @returns {Promise} A promise that resolves when the socket is gracefully closed. */ @@ -751,7 +751,7 @@ enc } this.#disposed = true; - logger().debug("disposing worker"); + logger().debug("disposing actor"); // Clear interval so NodeJS process can exit clearInterval(this.#keepNodeAliveInterval); @@ -760,7 +760,7 @@ enc this.#abortController.abort(); // Remove from registry - this.#client[WORKER_CONNS_SYMBOL].delete(this); + this.#client[ACTOR_CONNS_SYMBOL].delete(this); // Disconnect transport cleanly if (!this.#transport) { @@ -797,19 +797,19 @@ enc } /** - * Connection to a worker. Allows calling worker's remote procedure calls with inferred types. See {@link WorkerConnRaw} for underlying methods. + * Connection to a actor. Allows calling actor's remote procedure calls with inferred types. See {@link ActorConnRaw} for underlying methods. * * @example * ``` * const room = client.connect(...etc...); - * // This calls the action named `sendMessage` on the `ChatRoom` worker. + * // This calls the action named `sendMessage` on the `ChatRoom` actor. * await room.sendMessage('Hello, world!'); * ``` * * Private methods (e.g. those starting with `_`) are automatically excluded. * - * @template AD The worker class that this connection is for. - * @see {@link WorkerConnRaw} + * @template AD The actor class that this connection is for. + * @see {@link ActorConnRaw} */ -export type WorkerConn = WorkerConnRaw & - WorkerDefinitionActions; +export type ActorConn = ActorConnRaw & + ActorDefinitionActions; diff --git a/packages/core/src/client/actor-handle.ts b/packages/core/src/client/actor-handle.ts new file mode 100644 index 000000000..07cd708fa --- /dev/null +++ b/packages/core/src/client/actor-handle.ts @@ -0,0 +1,155 @@ +import type { AnyActorDefinition } from "@/actor/definition"; +import type { Encoding } from "@/actor/protocol/serde"; +import type { ActorQuery } from "@/manager/protocol/query"; +import { type ActorDefinitionActions } from "./actor-common"; +import { type ActorConn, ActorConnRaw } from "./actor-conn"; +import { + ClientDriver, + CREATE_ACTOR_CONN_PROXY, + type ClientRaw, +} from "./client"; +import { logger } from "./log"; +import invariant from "invariant"; +import { assertUnreachable } from "@/actor/utils"; + +/** + * Provides underlying functions for stateless {@link ActorHandle} for action calls. + * Similar to ActorConnRaw but doesn't maintain a connection. + * + * @see {@link ActorHandle} + */ +export class ActorHandleRaw { + #client: ClientRaw; + #driver: ClientDriver; + #encodingKind: Encoding; + #actorQuery: ActorQuery; + #params: unknown; + + /** + * Do not call this directly. + * + * Creates an instance of ActorHandleRaw. + * + * @protected + */ + public constructor( + client: any, + driver: ClientDriver, + params: unknown, + encodingKind: Encoding, + actorQuery: ActorQuery, + ) { + this.#client = client; + this.#driver = driver; + this.#encodingKind = encodingKind; + this.#actorQuery = actorQuery; + this.#params = params; + } + + /** + * Call a raw action. This method sends an HTTP request to invoke the named action. + * + * @see {@link ActorHandle} + * @template Args - The type of arguments to pass to the action function. + * @template Response - The type of the response returned by the action function. + */ + async action< + Args extends Array = unknown[], + Response = unknown, + >(opts: { + name: string; + args: Args; + signal?: AbortSignal; + }): Promise { + return await this.#driver.action( + undefined, + this.#actorQuery, + this.#encodingKind, + this.#params, + opts.name, + opts.args, + { signal: opts.signal }, + ); + } + + /** + * Establishes a persistent connection to the actor. + * + * @template AD The actor class that this connection is for. + * @returns {ActorConn} A connection to the actor. + */ + connect(): ActorConn { + logger().debug("establishing connection from handle", { + query: this.#actorQuery, + }); + + const conn = new ActorConnRaw( + this.#client, + this.#driver, + this.#params, + this.#encodingKind, + this.#actorQuery, + ); + + return this.#client[CREATE_ACTOR_CONN_PROXY]( + conn, + ) as ActorConn; + } + + /** + * Resolves the actor to get its unique actor ID + * + * @returns {Promise} - A promise that resolves to the actor's ID + */ + async resolve({ signal }: { signal?: AbortSignal } = {}): Promise { + if ( + "getForKey" in this.#actorQuery || + "getOrCreateForKey" in this.#actorQuery + ) { + // TODO: + const actorId = await this.#driver.resolveActorId( + undefined, + this.#actorQuery, + this.#encodingKind, + this.#params, + signal ? { signal } : undefined, + ); + this.#actorQuery = { getForId: { actorId } }; + return actorId; + } else if ("getForId" in this.#actorQuery) { + // SKip since it's already resolved + return this.#actorQuery.getForId.actorId; + } else if ("create" in this.#actorQuery) { + // Cannot create a handle with this query + invariant(false, "actorQuery cannot be create"); + } else { + assertUnreachable(this.#actorQuery); + } + } +} + +/** + * Stateless handle to a actor. Allows calling actor's remote procedure calls with inferred types + * without establishing a persistent connection. + * + * @example + * ``` + * const room = client.get(...etc...); + * // This calls the action named `sendMessage` on the `ChatRoom` actor without a connection. + * await room.sendMessage('Hello, world!'); + * ``` + * + * Private methods (e.g. those starting with `_`) are automatically excluded. + * + * @template AD The actor class that this handle is for. + * @see {@link ActorHandleRaw} + */ +export type ActorHandle = Omit< + ActorHandleRaw, + "connect" +> & { + // Add typed version of ActorConn (instead of using AnyActorDefinition) + connect(): ActorConn; + // Resolve method returns the actor ID + resolve(): Promise; +} & ActorDefinitionActions; diff --git a/packages/core/src/client/client.ts b/packages/core/src/client/client.ts index f67f2a9b8..52ea01f6d 100644 --- a/packages/core/src/client/client.ts +++ b/packages/core/src/client/client.ts @@ -1,77 +1,77 @@ -import type { Transport } from "@/worker/protocol/message/mod"; -import type { Encoding } from "@/worker/protocol/serde"; -import type { WorkerQuery } from "@/manager/protocol/query"; -import { WorkerConn, WorkerConnRaw, CONNECT_SYMBOL } from "./worker-conn"; -import { WorkerHandle, WorkerHandleRaw } from "./worker-handle"; -import { WorkerActionFunction } from "./worker-common"; +import type { Transport } from "@/actor/protocol/message/mod"; +import type { Encoding } from "@/actor/protocol/serde"; +import type { ActorQuery } from "@/manager/protocol/query"; +import { ActorConn, ActorConnRaw, CONNECT_SYMBOL } from "./actor-conn"; +import { ActorHandle, ActorHandleRaw } from "./actor-handle"; +import { ActorActionFunction } from "./actor-common"; import { logger } from "./log"; import type { Registry } from "@/mod"; -import type { AnyWorkerDefinition } from "@/worker/definition"; -import type * as wsToServer from "@/worker/protocol/message/to-server"; +import type { AnyActorDefinition } from "@/actor/definition"; +import type * as wsToServer from "@/actor/protocol/message/to-server"; import type { EventSource } from "eventsource"; import type { Context as HonoContext } from "hono"; import type { WebSocket } from "ws"; -/** Extract the worker registry from the registry definition. */ -export type ExtractWorkersFromRegistry> = - A extends Registry ? Workers : never; +/** Extract the actor registry from the registry definition. */ +export type ExtractActorsFromRegistry> = + A extends Registry ? Actors : never; /** Extract the registry definition from the client. */ export type ExtractRegistryFromClient>> = C extends Client ? A : never; /** - * Represents a worker accessor that provides methods to interact with a specific worker. + * Represents a actor accessor that provides methods to interact with a specific actor. */ -export interface WorkerAccessor { +export interface ActorAccessor { /** - * Gets a stateless handle to a worker by its key, but does not create the worker if it doesn't exist. - * The worker name is automatically injected from the property accessor. + * Gets a stateless handle to a actor by its key, but does not create the actor if it doesn't exist. + * The actor name is automatically injected from the property accessor. * - * @template AD The worker class that this handle is for. - * @param {string | string[]} [key=[]] - The key to identify the worker. Can be a single string or an array of strings. - * @param {GetWithIdOptions} [opts] - Options for getting the worker. - * @returns {WorkerHandle} - A handle to the worker. + * @template AD The actor class that this handle is for. + * @param {string | string[]} [key=[]] - The key to identify the actor. Can be a single string or an array of strings. + * @param {GetWithIdOptions} [opts] - Options for getting the actor. + * @returns {ActorHandle} - A handle to the actor. */ - get(key?: string | string[], opts?: GetWithIdOptions): WorkerHandle; + get(key?: string | string[], opts?: GetWithIdOptions): ActorHandle; /** - * Gets a stateless handle to a worker by its key, creating it if necessary. - * The worker name is automatically injected from the property accessor. + * Gets a stateless handle to a actor by its key, creating it if necessary. + * The actor name is automatically injected from the property accessor. * - * @template AD The worker class that this handle is for. - * @param {string | string[]} [key=[]] - The key to identify the worker. Can be a single string or an array of strings. - * @param {GetOptions} [opts] - Options for getting the worker. - * @returns {WorkerHandle} - A handle to the worker. + * @template AD The actor class that this handle is for. + * @param {string | string[]} [key=[]] - The key to identify the actor. Can be a single string or an array of strings. + * @param {GetOptions} [opts] - Options for getting the actor. + * @returns {ActorHandle} - A handle to the actor. */ getOrCreate( key?: string | string[], opts?: GetOrCreateOptions, - ): WorkerHandle; + ): ActorHandle; /** - * Gets a stateless handle to a worker by its ID. + * Gets a stateless handle to a actor by its ID. * - * @template AD The worker class that this handle is for. - * @param {string} workerId - The ID of the worker. - * @param {GetWithIdOptions} [opts] - Options for getting the worker. - * @returns {WorkerHandle} - A handle to the worker. + * @template AD The actor class that this handle is for. + * @param {string} actorId - The ID of the actor. + * @param {GetWithIdOptions} [opts] - Options for getting the actor. + * @returns {ActorHandle} - A handle to the actor. */ - getForId(workerId: string, opts?: GetWithIdOptions): WorkerHandle; + getForId(actorId: string, opts?: GetWithIdOptions): ActorHandle; /** - * Creates a new worker with the name automatically injected from the property accessor, - * and returns a stateless handle to it with the worker ID resolved. + * Creates a new actor with the name automatically injected from the property accessor, + * and returns a stateless handle to it with the actor ID resolved. * - * @template AD The worker class that this handle is for. - * @param {string | string[]} key - The key to identify the worker. Can be a single string or an array of strings. - * @param {CreateOptions} [opts] - Options for creating the worker (excluding name and key). - * @returns {Promise>} - A promise that resolves to a handle to the worker. + * @template AD The actor class that this handle is for. + * @param {string | string[]} key - The key to identify the actor. Can be a single string or an array of strings. + * @param {CreateOptions} [opts] - Options for creating the actor (excluding name and key). + * @returns {Promise>} - A promise that resolves to a handle to the actor. */ create( key?: string | string[], opts?: CreateOptions, - ): Promise>; + ): Promise>; } /** @@ -84,7 +84,7 @@ export interface ClientOptions { } /** - * Options for querying workers. + * Options for querying actors. * @typedef {Object} QueryOptions * @property {unknown} [parameters] - Parameters to pass to the connection. */ @@ -96,38 +96,38 @@ export interface QueryOptions { } /** - * Options for getting a worker by ID. + * Options for getting a actor by ID. * @typedef {QueryOptions} GetWithIdOptions */ export interface GetWithIdOptions extends QueryOptions {} /** - * Options for getting a worker. + * Options for getting a actor. * @typedef {QueryOptions} GetOptions */ export interface GetOptions extends QueryOptions {} /** - * Options for getting or creating a worker. + * Options for getting or creating a actor. * @typedef {QueryOptions} GetOrCreateOptions - * @property {string} [createInRegion] - Region to create the worker in if it doesn't exist. + * @property {string} [createInRegion] - Region to create the actor in if it doesn't exist. */ export interface GetOrCreateOptions extends QueryOptions { - /** Region to create the worker in if it doesn't exist. */ + /** Region to create the actor in if it doesn't exist. */ createInRegion?: string; - /** Input data to pass to the worker. */ + /** Input data to pass to the actor. */ createWithInput?: unknown; } /** - * Options for creating a worker. + * Options for creating a actor. * @typedef {QueryOptions} CreateOptions - * @property {string} [region] - The region to create the worker in. + * @property {string} [region] - The region to create the actor in. */ export interface CreateOptions extends QueryOptions { - /** The region to create the worker in. */ + /** The region to create the actor in. */ region?: string; - /** Input data to pass to the worker. */ + /** Input data to pass to the actor. */ input?: unknown; } @@ -151,44 +151,44 @@ export interface Region { name: string; } -export const WORKER_CONNS_SYMBOL = Symbol("workerConns"); -export const CREATE_WORKER_CONN_PROXY = Symbol("createWorkerConnProxy"); +export const ACTOR_CONNS_SYMBOL = Symbol("actorConns"); +export const CREATE_ACTOR_CONN_PROXY = Symbol("createActorConnProxy"); export const TRANSPORT_SYMBOL = Symbol("transport"); export interface ClientDriver { action = unknown[], Response = unknown>( c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encoding: Encoding, params: unknown, name: string, args: Args, opts: { signal?: AbortSignal } | undefined, ): Promise; - resolveWorkerId( + resolveActorId( c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encodingKind: Encoding, params: unknown, opts: { signal?: AbortSignal } | undefined, ): Promise; connectWebSocket( c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encodingKind: Encoding, params: unknown, opts: { signal?: AbortSignal } | undefined, ): Promise; connectSse( c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encodingKind: Encoding, params: unknown, opts: { signal?: AbortSignal } | undefined, ): Promise; sendHttpMessage( c: HonoContext | undefined, - workerId: string, + actorId: string, encoding: Encoding, connectionId: string, connectionToken: string, @@ -198,15 +198,15 @@ export interface ClientDriver { } /** - * Client for managing & connecting to workers. + * Client for managing & connecting to actors. * - * @template A The workers map type that defines the available workers. - * @see {@link https://rivet.gg/docs/manage|Create & Manage Workers} + * @template A The actors map type that defines the available actors. + * @see {@link https://rivet.gg/docs/manage|Create & Manage Actors} */ export class ClientRaw { #disposed = false; - [WORKER_CONNS_SYMBOL] = new Set(); + [ACTOR_CONNS_SYMBOL] = new Set(); #driver: ClientDriver; #encodingKind: Encoding; @@ -227,94 +227,94 @@ export class ClientRaw { } /** - * Gets a stateless handle to a worker by its ID. + * Gets a stateless handle to a actor by its ID. * - * @template AD The worker class that this handle is for. - * @param {string} name - The name of the worker. - * @param {string} workerId - The ID of the worker. - * @param {GetWithIdOptions} [opts] - Options for getting the worker. - * @returns {WorkerHandle} - A handle to the worker. + * @template AD The actor class that this handle is for. + * @param {string} name - The name of the actor. + * @param {string} actorId - The ID of the actor. + * @param {GetWithIdOptions} [opts] - Options for getting the actor. + * @returns {ActorHandle} - A handle to the actor. */ - getForId( + getForId( name: string, - workerId: string, + actorId: string, opts?: GetWithIdOptions, - ): WorkerHandle { - logger().debug("get handle to worker with id", { + ): ActorHandle { + logger().debug("get handle to actor with id", { name, - workerId, + actorId, params: opts?.params, }); - const workerQuery = { + const actorQuery = { getForId: { - workerId, + actorId, }, }; - const handle = this.#createHandle(opts?.params, workerQuery); - return createWorkerProxy(handle) as WorkerHandle; + const handle = this.#createHandle(opts?.params, actorQuery); + return createActorProxy(handle) as ActorHandle; } /** - * Gets a stateless handle to a worker by its key, but does not create the worker if it doesn't exist. + * Gets a stateless handle to a actor by its key, but does not create the actor if it doesn't exist. * - * @template AD The worker class that this handle is for. - * @param {string} name - The name of the worker. - * @param {string | string[]} [key=[]] - The key to identify the worker. Can be a single string or an array of strings. - * @param {GetWithIdOptions} [opts] - Options for getting the worker. - * @returns {WorkerHandle} - A handle to the worker. + * @template AD The actor class that this handle is for. + * @param {string} name - The name of the actor. + * @param {string | string[]} [key=[]] - The key to identify the actor. Can be a single string or an array of strings. + * @param {GetWithIdOptions} [opts] - Options for getting the actor. + * @returns {ActorHandle} - A handle to the actor. */ - get( + get( name: string, key?: string | string[], opts?: GetWithIdOptions, - ): WorkerHandle { + ): ActorHandle { // Convert string to array of strings const keyArray: string[] = typeof key === "string" ? [key] : key || []; - logger().debug("get handle to worker", { + logger().debug("get handle to actor", { name, key: keyArray, parameters: opts?.params, }); - const workerQuery: WorkerQuery = { + const actorQuery: ActorQuery = { getForKey: { name, key: keyArray, }, }; - const handle = this.#createHandle(opts?.params, workerQuery); - return createWorkerProxy(handle) as WorkerHandle; + const handle = this.#createHandle(opts?.params, actorQuery); + return createActorProxy(handle) as ActorHandle; } /** - * Gets a stateless handle to a worker by its key, creating it if necessary. + * Gets a stateless handle to a actor by its key, creating it if necessary. * - * @template AD The worker class that this handle is for. - * @param {string} name - The name of the worker. - * @param {string | string[]} [key=[]] - The key to identify the worker. Can be a single string or an array of strings. - * @param {GetOptions} [opts] - Options for getting the worker. - * @returns {WorkerHandle} - A handle to the worker. + * @template AD The actor class that this handle is for. + * @param {string} name - The name of the actor. + * @param {string | string[]} [key=[]] - The key to identify the actor. Can be a single string or an array of strings. + * @param {GetOptions} [opts] - Options for getting the actor. + * @returns {ActorHandle} - A handle to the actor. */ - getOrCreate( + getOrCreate( name: string, key?: string | string[], opts?: GetOrCreateOptions, - ): WorkerHandle { + ): ActorHandle { // Convert string to array of strings const keyArray: string[] = typeof key === "string" ? [key] : key || []; - logger().debug("get or create handle to worker", { + logger().debug("get or create handle to actor", { name, key: keyArray, parameters: opts?.params, createInRegion: opts?.createInRegion, }); - const workerQuery: WorkerQuery = { + const actorQuery: ActorQuery = { getOrCreateForKey: { name, key: keyArray, @@ -323,25 +323,25 @@ export class ClientRaw { }, }; - const handle = this.#createHandle(opts?.params, workerQuery); - return createWorkerProxy(handle) as WorkerHandle; + const handle = this.#createHandle(opts?.params, actorQuery); + return createActorProxy(handle) as ActorHandle; } /** - * Creates a new worker with the provided key and returns a stateless handle to it. - * Resolves the worker ID and returns a handle with getForId query. + * Creates a new actor with the provided key and returns a stateless handle to it. + * Resolves the actor ID and returns a handle with getForId query. * - * @template AD The worker class that this handle is for. - * @param {string} name - The name of the worker. - * @param {string | string[]} key - The key to identify the worker. Can be a single string or an array of strings. - * @param {CreateOptions} [opts] - Options for creating the worker (excluding name and key). - * @returns {Promise>} - A promise that resolves to a handle to the worker. + * @template AD The actor class that this handle is for. + * @param {string} name - The name of the actor. + * @param {string | string[]} key - The key to identify the actor. Can be a single string or an array of strings. + * @param {CreateOptions} [opts] - Options for creating the actor (excluding name and key). + * @returns {Promise>} - A promise that resolves to a handle to the actor. */ - async create( + async create( name: string, key?: string | string[], opts?: CreateOptions, - ): Promise> { + ): Promise> { // Convert string to array of strings const keyArray: string[] = typeof key === "string" ? [key] : key || []; @@ -352,66 +352,66 @@ export class ClientRaw { name, key: keyArray, }, - } satisfies WorkerQuery; + } satisfies ActorQuery; - logger().debug("create worker handle", { + logger().debug("create actor handle", { name, key: keyArray, parameters: opts?.params, create: createQuery.create, }); - // Create the worker - const workerId = await this.#driver.resolveWorkerId( + // Create the actor + const actorId = await this.#driver.resolveActorId( undefined, createQuery, this.#encodingKind, opts?.params, opts?.signal ? { signal: opts.signal } : undefined, ); - logger().debug("created worker with ID", { + logger().debug("created actor with ID", { name, key: keyArray, - workerId, + actorId, }); - // Create handle with worker ID + // Create handle with actor ID const getForIdQuery = { getForId: { - workerId, + actorId, }, - } satisfies WorkerQuery; + } satisfies ActorQuery; const handle = this.#createHandle(opts?.params, getForIdQuery); - const proxy = createWorkerProxy(handle) as WorkerHandle; + const proxy = createActorProxy(handle) as ActorHandle; return proxy; } - #createHandle(params: unknown, workerQuery: WorkerQuery): WorkerHandleRaw { - return new WorkerHandleRaw( + #createHandle(params: unknown, actorQuery: ActorQuery): ActorHandleRaw { + return new ActorHandleRaw( this, this.#driver, params, this.#encodingKind, - workerQuery, + actorQuery, ); } - [CREATE_WORKER_CONN_PROXY]( - conn: WorkerConnRaw, - ): WorkerConn { + [CREATE_ACTOR_CONN_PROXY]( + conn: ActorConnRaw, + ): ActorConn { // Save to connection list - this[WORKER_CONNS_SYMBOL].add(conn); + this[ACTOR_CONNS_SYMBOL].add(conn); // Start connection conn[CONNECT_SYMBOL](); - return createWorkerProxy(conn) as WorkerConn; + return createActorProxy(conn) as ActorConn; } /** - * Disconnects from all workers. + * Disconnects from all actors. * * @returns {Promise} A promise that resolves when all connections are closed. */ @@ -427,7 +427,7 @@ export class ClientRaw { const disposePromises = []; // Dispose all connections - for (const conn of this[WORKER_CONNS_SYMBOL].values()) { + for (const conn of this[ACTOR_CONNS_SYMBOL].values()) { disposePromises.push(conn.dispose()); } @@ -436,14 +436,14 @@ export class ClientRaw { } /** - * Client type with worker accessors. - * This adds property accessors for worker names to the ClientRaw base class. + * Client type with actor accessors. + * This adds property accessors for actor names to the ClientRaw base class. * - * @template A The worker registry type. + * @template A The actor registry type. */ export type Client> = ClientRaw & { - [K in keyof ExtractWorkersFromRegistry]: WorkerAccessor< - ExtractWorkersFromRegistry[K] + [K in keyof ExtractActorsFromRegistry]: ActorAccessor< + ExtractActorsFromRegistry[K] >; }; @@ -453,7 +453,7 @@ export function createClientWithDriver>( ): Client { const client = new ClientRaw(driver, opts); - // Create proxy for accessing workers by name + // Create proxy for accessing actors by name return new Proxy(client, { get: (target: ClientRaw, prop: string | symbol, receiver: unknown) => { // Get the real property if it exists @@ -466,16 +466,16 @@ export function createClientWithDriver>( return value; } - // Handle worker accessor for string properties (worker names) + // Handle actor accessor for string properties (actor names) if (typeof prop === "string") { - // Return worker accessor object with methods + // Return actor accessor object with methods return { // Handle methods (stateless action) get: ( key?: string | string[], opts?: GetWithIdOptions, - ): WorkerHandle[typeof prop]> => { - return target.get[typeof prop]>( + ): ActorHandle[typeof prop]> => { + return target.get[typeof prop]>( prop, key, opts, @@ -484,18 +484,18 @@ export function createClientWithDriver>( getOrCreate: ( key?: string | string[], opts?: GetOptions, - ): WorkerHandle[typeof prop]> => { + ): ActorHandle[typeof prop]> => { return target.getOrCreate< - ExtractWorkersFromRegistry[typeof prop] + ExtractActorsFromRegistry[typeof prop] >(prop, key, opts); }, getForId: ( - workerId: string, + actorId: string, opts?: GetWithIdOptions, - ): WorkerHandle[typeof prop]> => { - return target.getForId[typeof prop]>( + ): ActorHandle[typeof prop]> => { + return target.getForId[typeof prop]>( prop, - workerId, + actorId, opts, ); }, @@ -503,13 +503,13 @@ export function createClientWithDriver>( key: string | string[], opts: CreateOptions = {}, ): Promise< - WorkerHandle[typeof prop]> + ActorHandle[typeof prop]> > => { return await target.create< - ExtractWorkersFromRegistry[typeof prop] + ExtractActorsFromRegistry[typeof prop] >(prop, key, opts); }, - } as WorkerAccessor[typeof prop]>; + } as ActorAccessor[typeof prop]>; } return undefined; @@ -518,15 +518,15 @@ export function createClientWithDriver>( } /** - * Creates a proxy for a worker that enables calling actions without explicitly using `.action`. + * Creates a proxy for a actor that enables calling actions without explicitly using `.action`. **/ -function createWorkerProxy( - handle: WorkerHandleRaw | WorkerConnRaw, -): WorkerHandle | WorkerConn { +function createActorProxy( + handle: ActorHandleRaw | ActorConnRaw, +): ActorHandle | ActorConn { // Stores returned action functions for faster calls - const methodCache = new Map(); + const methodCache = new Map(); return new Proxy(handle, { - get(target: WorkerHandleRaw, prop: string | symbol, receiver: unknown) { + get(target: ActorHandleRaw, prop: string | symbol, receiver: unknown) { // Handle built-in Symbol properties if (typeof prop === "symbol") { return Reflect.get(target, prop, receiver); @@ -557,7 +557,7 @@ function createWorkerProxy( }, // Support for 'in' operator - has(target: WorkerHandleRaw, prop: string | symbol) { + has(target: ActorHandleRaw, prop: string | symbol) { // All string properties are potentially action functions if (typeof prop === "string") { return true; @@ -567,17 +567,17 @@ function createWorkerProxy( }, // Support instanceof checks - getPrototypeOf(target: WorkerHandleRaw) { + getPrototypeOf(target: ActorHandleRaw) { return Reflect.getPrototypeOf(target); }, // Prevent property enumeration of non-existent action methods - ownKeys(target: WorkerHandleRaw) { + ownKeys(target: ActorHandleRaw) { return Reflect.ownKeys(target); }, // Support proper property descriptors - getOwnPropertyDescriptor(target: WorkerHandleRaw, prop: string | symbol) { + getOwnPropertyDescriptor(target: ActorHandleRaw, prop: string | symbol) { const targetDescriptor = Reflect.getOwnPropertyDescriptor(target, prop); if (targetDescriptor) { return targetDescriptor; @@ -593,5 +593,5 @@ function createWorkerProxy( } return undefined; }, - }) as WorkerHandle | WorkerConn; + }) as ActorHandle | ActorConn; } diff --git a/packages/core/src/client/errors.ts b/packages/core/src/client/errors.ts index 15724aa63..406cb8c48 100644 --- a/packages/core/src/client/errors.ts +++ b/packages/core/src/client/errors.ts @@ -1,16 +1,16 @@ import { MAX_CONN_PARAMS_SIZE } from "@/common//network"; -export class WorkerClientError extends Error {} +export class ActorClientError extends Error {} -export class InternalError extends WorkerClientError {} +export class InternalError extends ActorClientError {} -export class ManagerError extends WorkerClientError { +export class ManagerError extends ActorClientError { constructor(error: string, opts?: ErrorOptions) { super(`Manager error: ${error}`, opts); } } -export class ConnParamsTooLong extends WorkerClientError { +export class ConnParamsTooLong extends ActorClientError { constructor() { super( `Connection parameters must be less than ${MAX_CONN_PARAMS_SIZE} bytes`, @@ -18,14 +18,14 @@ export class ConnParamsTooLong extends WorkerClientError { } } -export class MalformedResponseMessage extends WorkerClientError { +export class MalformedResponseMessage extends ActorClientError { constructor(cause?: unknown) { super(`Malformed response message: ${cause}`, { cause }); } } -export class WorkerError extends WorkerClientError { - __type = "WorkerError"; +export class ActorError extends ActorClientError { + __type = "ActorError"; constructor( public readonly code: string, @@ -36,14 +36,14 @@ export class WorkerError extends WorkerClientError { } } -export class HttpRequestError extends WorkerClientError { +export class HttpRequestError extends ActorClientError { constructor(message: string, opts?: { cause?: unknown }) { super(`HTTP request error: ${message}`, { cause: opts?.cause }); } } -export class WorkerConnDisposed extends WorkerClientError { +export class ActorConnDisposed extends ActorClientError { constructor() { - super("Attempting to interact with a disposed worker connection."); + super("Attempting to interact with a disposed actor connection."); } } diff --git a/packages/core/src/client/http-client-driver.ts b/packages/core/src/client/http-client-driver.ts index 22fd532a9..9338d355b 100644 --- a/packages/core/src/client/http-client-driver.ts +++ b/packages/core/src/client/http-client-driver.ts @@ -1,19 +1,19 @@ import * as cbor from "cbor-x"; -import type { Encoding } from "@/worker/protocol/serde"; -import type { WorkerQuery } from "@/manager/protocol/query"; +import type { Encoding } from "@/actor/protocol/serde"; +import type { ActorQuery } from "@/manager/protocol/query"; import * as errors from "./errors"; import { logger } from "./log"; -import type * as wsToServer from "@/worker/protocol/message/to-server"; -import type * as protoHttpResolve from "@/worker/protocol/http/resolve"; +import type * as wsToServer from "@/actor/protocol/message/to-server"; +import type * as protoHttpResolve from "@/actor/protocol/http/resolve"; import { assertUnreachable, httpUserAgent } from "@/utils"; import { - HEADER_WORKER_ID, - HEADER_WORKER_QUERY, + HEADER_ACTOR_ID, + HEADER_ACTOR_QUERY, HEADER_CONN_ID, HEADER_CONN_PARAMS, HEADER_CONN_TOKEN, HEADER_ENCODING, -} from "@/worker/router-endpoints"; +} from "@/actor/router-endpoints"; import type { EventSource } from "eventsource"; import { importWebSocket } from "@/common/websocket"; import { importEventSource } from "@/common/eventsource"; @@ -22,8 +22,8 @@ import { serializeWithEncoding, type WebSocketMessage, } from "./utils"; -import type { ActionRequest } from "@/worker/protocol/http/action"; -import type { ActionResponse } from "@/worker/protocol/message/to-client"; +import type { ActionRequest } from "@/actor/protocol/http/action"; +import type { ActionResponse } from "@/actor/protocol/message/to-client"; import { ClientDriver } from "./client"; import { HonoRequest, Context as HonoContext } from "hono"; import type { WebSocket } from "ws"; @@ -48,26 +48,26 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { const driver: ClientDriver = { action: async = unknown[], Response = unknown>( _c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encoding: Encoding, params: unknown, name: string, args: Args, opts: { signal?: AbortSignal } | undefined, ): Promise => { - logger().debug("worker handle action", { + logger().debug("actor handle action", { name, args, - query: workerQuery, + query: actorQuery, }); const responseData = await sendHttpRequest( { - url: `${managerEndpoint}/registry/workers/actions/${encodeURIComponent(name)}`, + url: `${managerEndpoint}/registry/actors/actions/${encodeURIComponent(name)}`, method: "POST", headers: { [HEADER_ENCODING]: encoding, - [HEADER_WORKER_QUERY]: JSON.stringify(workerQuery), + [HEADER_ACTOR_QUERY]: JSON.stringify(actorQuery), ...(params !== undefined ? { [HEADER_CONN_PARAMS]: JSON.stringify(params) } : {}), @@ -81,24 +81,24 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { return responseData.o as Response; }, - resolveWorkerId: async ( + resolveActorId: async ( _c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encodingKind: Encoding, params: unknown, ): Promise => { - logger().debug("resolving worker ID", { query: workerQuery }); + logger().debug("resolving actor ID", { query: actorQuery }); try { const result = await sendHttpRequest< Record, protoHttpResolve.ResolveResponse >({ - url: `${managerEndpoint}/registry/workers/resolve`, + url: `${managerEndpoint}/registry/actors/resolve`, method: "POST", headers: { [HEADER_ENCODING]: encodingKind, - [HEADER_WORKER_QUERY]: JSON.stringify(workerQuery), + [HEADER_ACTOR_QUERY]: JSON.stringify(actorQuery), ...(params !== undefined ? { [HEADER_CONN_PARAMS]: JSON.stringify(params) } : {}), @@ -107,15 +107,15 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { encoding: encodingKind, }); - logger().debug("resolved worker ID", { workerId: result.i }); + logger().debug("resolved actor ID", { actorId: result.i }); return result.i; } catch (error) { - logger().error("failed to resolve worker ID", { error }); - if (error instanceof errors.WorkerError) { + logger().error("failed to resolve actor ID", { error }); + if (error instanceof errors.ActorError) { throw error; } else { throw new errors.InternalError( - `Failed to resolve worker ID: ${String(error)}`, + `Failed to resolve actor ID: ${String(error)}`, ); } } @@ -123,7 +123,7 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { connectWebSocket: async ( _c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encodingKind: Encoding, params: unknown, ): Promise => { @@ -132,11 +132,11 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { const endpoint = managerEndpoint .replace(/^http:/, "ws:") .replace(/^https:/, "wss:"); - const url = `${endpoint}/registry/workers/connect/websocket`; + const url = `${endpoint}/registry/actors/connect/websocket`; // Pass sensitive data via protocol const protocol = [ - `query.${encodeURIComponent(JSON.stringify(workerQuery))}`, + `query.${encodeURIComponent(JSON.stringify(actorQuery))}`, `encoding.${encodingKind}`, ]; if (params) @@ -166,13 +166,13 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { connectSse: async ( _c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encodingKind: Encoding, params: unknown, ): Promise => { const { EventSource } = await dynamicImports; - const url = `${managerEndpoint}/registry/workers/connect/sse`; + const url = `${managerEndpoint}/registry/actors/connect/sse`; logger().debug("connecting to sse", { url }); const eventSource = new EventSource(url, { @@ -183,7 +183,7 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { ...init?.headers, "User-Agent": httpUserAgent(), [HEADER_ENCODING]: encodingKind, - [HEADER_WORKER_QUERY]: JSON.stringify(workerQuery), + [HEADER_ACTOR_QUERY]: JSON.stringify(actorQuery), ...(params !== undefined ? { [HEADER_CONN_PARAMS]: JSON.stringify(params) } : {}), @@ -197,7 +197,7 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { sendHttpMessage: async ( _c: HonoContext | undefined, - workerId: string, + actorId: string, encoding: Encoding, connectionId: string, connectionToken: string, @@ -206,12 +206,12 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { // TODO: Implement ordered messages, this is not guaranteed order. Needs to use an index in order to ensure we can pipeline requests efficiently. // TODO: Validate that we're using HTTP/3 whenever possible for pipelining requests const messageSerialized = serializeWithEncoding(encoding, message); - const res = await fetch(`${managerEndpoint}/registry/workers/message`, { + const res = await fetch(`${managerEndpoint}/registry/actors/message`, { method: "POST", headers: { "User-Agent": httpUserAgent(), [HEADER_ENCODING]: encoding, - [HEADER_WORKER_ID]: workerId, + [HEADER_ACTOR_ID]: actorId, [HEADER_CONN_ID]: connectionId, [HEADER_CONN_TOKEN]: connectionToken, }, diff --git a/packages/core/src/client/log.ts b/packages/core/src/client/log.ts index aa3c582df..09823be08 100644 --- a/packages/core/src/client/log.ts +++ b/packages/core/src/client/log.ts @@ -1,6 +1,6 @@ import { getLogger } from "@/common//log"; -export const LOGGER_NAME = "worker-client"; +export const LOGGER_NAME = "actor-client"; export function logger() { return getLogger(LOGGER_NAME); diff --git a/packages/core/src/client/mod.ts b/packages/core/src/client/mod.ts index 0e69f896c..5e6300f6a 100644 --- a/packages/core/src/client/mod.ts +++ b/packages/core/src/client/mod.ts @@ -4,46 +4,46 @@ import { createHttpClientDriver } from "./http-client-driver"; export type { Client, - WorkerAccessor, + ActorAccessor, ClientOptions, CreateOptions, GetOptions, GetWithIdOptions, QueryOptions, Region, - ExtractWorkersFromRegistry, + ExtractActorsFromRegistry, ExtractRegistryFromClient, ClientRaw, } from "./client"; -export type { WorkerConn } from "./worker-conn"; -export { WorkerConnRaw } from "./worker-conn"; -export type { EventUnsubscribe } from "./worker-conn"; -export type { WorkerHandle } from "./worker-handle"; -export { WorkerHandleRaw } from "./worker-handle"; -export type { WorkerActionFunction } from "./worker-common"; -export type { Transport } from "@/worker/protocol/message/mod"; -export type { Encoding } from "@/worker/protocol/serde"; +export type { ActorConn } from "./actor-conn"; +export { ActorConnRaw } from "./actor-conn"; +export type { EventUnsubscribe } from "./actor-conn"; +export type { ActorHandle } from "./actor-handle"; +export { ActorHandleRaw } from "./actor-handle"; +export type { ActorActionFunction } from "./actor-common"; +export type { Transport } from "@/actor/protocol/message/mod"; +export type { Encoding } from "@/actor/protocol/serde"; export type { CreateRequest } from "@/manager/protocol/query"; export { - WorkerClientError, + ActorClientError, InternalError, ManagerError, ConnParamsTooLong, MalformedResponseMessage, - WorkerError, + ActorError, } from "@/client/errors"; export { - AnyWorkerDefinition, - WorkerDefinition, -} from "@/worker/definition"; + AnyActorDefinition, + ActorDefinition, +} from "@/actor/definition"; /** - * Creates a client with the worker accessor proxy. + * Creates a client with the actor accessor proxy. * - * @template A The worker application type. + * @template A The actor application type. * @param {string} managerEndpoint - The manager endpoint. * @param {ClientOptions} [opts] - Options for configuring the client. - * @returns {Client} - A proxied client that supports the `client.myWorker.connect()` syntax. + * @returns {Client} - A proxied client that supports the `client.myActor.connect()` syntax. */ export function createClient>( endpoint: string, diff --git a/packages/core/src/client/utils.ts b/packages/core/src/client/utils.ts index 65e368bd5..5773425b5 100644 --- a/packages/core/src/client/utils.ts +++ b/packages/core/src/client/utils.ts @@ -2,8 +2,8 @@ import { assertUnreachable } from "@/common/utils"; import { httpUserAgent } from "@/utils"; import { Encoding } from "@/mod"; import * as cbor from "cbor-x"; -import { WorkerError, HttpRequestError } from "./errors"; -import { ResponseError } from "@/worker/protocol/http/error"; +import { ActorError, HttpRequestError } from "./errors"; +import { ResponseError } from "@/actor/protocol/http/error"; import { logger } from "./log"; export type WebSocketMessage = string | Blob | ArrayBuffer | Uint8Array; @@ -115,7 +115,7 @@ export async function sendHttpRequest< } // Throw structured error - throw new WorkerError(responseData.c, responseData.m, responseData.md); + throw new ActorError(responseData.c, responseData.m, responseData.md); } // Some requests don't need the success response to be parsed, so this can speed things up diff --git a/packages/core/src/client/worker-handle.ts b/packages/core/src/client/worker-handle.ts deleted file mode 100644 index 53c7f1b4e..000000000 --- a/packages/core/src/client/worker-handle.ts +++ /dev/null @@ -1,155 +0,0 @@ -import type { AnyWorkerDefinition } from "@/worker/definition"; -import type { Encoding } from "@/worker/protocol/serde"; -import type { WorkerQuery } from "@/manager/protocol/query"; -import { type WorkerDefinitionActions } from "./worker-common"; -import { type WorkerConn, WorkerConnRaw } from "./worker-conn"; -import { - ClientDriver, - CREATE_WORKER_CONN_PROXY, - type ClientRaw, -} from "./client"; -import { logger } from "./log"; -import invariant from "invariant"; -import { assertUnreachable } from "@/worker/utils"; - -/** - * Provides underlying functions for stateless {@link WorkerHandle} for action calls. - * Similar to WorkerConnRaw but doesn't maintain a connection. - * - * @see {@link WorkerHandle} - */ -export class WorkerHandleRaw { - #client: ClientRaw; - #driver: ClientDriver; - #encodingKind: Encoding; - #workerQuery: WorkerQuery; - #params: unknown; - - /** - * Do not call this directly. - * - * Creates an instance of WorkerHandleRaw. - * - * @protected - */ - public constructor( - client: any, - driver: ClientDriver, - params: unknown, - encodingKind: Encoding, - workerQuery: WorkerQuery, - ) { - this.#client = client; - this.#driver = driver; - this.#encodingKind = encodingKind; - this.#workerQuery = workerQuery; - this.#params = params; - } - - /** - * Call a raw action. This method sends an HTTP request to invoke the named action. - * - * @see {@link WorkerHandle} - * @template Args - The type of arguments to pass to the action function. - * @template Response - The type of the response returned by the action function. - */ - async action< - Args extends Array = unknown[], - Response = unknown, - >(opts: { - name: string; - args: Args; - signal?: AbortSignal; - }): Promise { - return await this.#driver.action( - undefined, - this.#workerQuery, - this.#encodingKind, - this.#params, - opts.name, - opts.args, - { signal: opts.signal }, - ); - } - - /** - * Establishes a persistent connection to the worker. - * - * @template AD The worker class that this connection is for. - * @returns {WorkerConn} A connection to the worker. - */ - connect(): WorkerConn { - logger().debug("establishing connection from handle", { - query: this.#workerQuery, - }); - - const conn = new WorkerConnRaw( - this.#client, - this.#driver, - this.#params, - this.#encodingKind, - this.#workerQuery, - ); - - return this.#client[CREATE_WORKER_CONN_PROXY]( - conn, - ) as WorkerConn; - } - - /** - * Resolves the worker to get its unique worker ID - * - * @returns {Promise} - A promise that resolves to the worker's ID - */ - async resolve({ signal }: { signal?: AbortSignal } = {}): Promise { - if ( - "getForKey" in this.#workerQuery || - "getOrCreateForKey" in this.#workerQuery - ) { - // TODO: - const workerId = await this.#driver.resolveWorkerId( - undefined, - this.#workerQuery, - this.#encodingKind, - this.#params, - signal ? { signal } : undefined, - ); - this.#workerQuery = { getForId: { workerId } }; - return workerId; - } else if ("getForId" in this.#workerQuery) { - // SKip since it's already resolved - return this.#workerQuery.getForId.workerId; - } else if ("create" in this.#workerQuery) { - // Cannot create a handle with this query - invariant(false, "workerQuery cannot be create"); - } else { - assertUnreachable(this.#workerQuery); - } - } -} - -/** - * Stateless handle to a worker. Allows calling worker's remote procedure calls with inferred types - * without establishing a persistent connection. - * - * @example - * ``` - * const room = client.get(...etc...); - * // This calls the action named `sendMessage` on the `ChatRoom` worker without a connection. - * await room.sendMessage('Hello, world!'); - * ``` - * - * Private methods (e.g. those starting with `_`) are automatically excluded. - * - * @template AD The worker class that this handle is for. - * @see {@link WorkerHandleRaw} - */ -export type WorkerHandle = Omit< - WorkerHandleRaw, - "connect" -> & { - // Add typed version of WorkerConn (instead of using AnyWorkerDefinition) - connect(): WorkerConn; - // Resolve method returns the worker ID - resolve(): Promise; -} & WorkerDefinitionActions; diff --git a/packages/core/src/common/router.ts b/packages/core/src/common/router.ts index 467ce0d46..83e4bebe8 100644 --- a/packages/core/src/common/router.ts +++ b/packages/core/src/common/router.ts @@ -4,9 +4,9 @@ import { deconstructError, stringifyError } from "./utils"; import { getRequestEncoding, getRequestExposeInternalError, -} from "@/worker/router-endpoints"; -import { Encoding, serialize } from "@/worker/protocol/serde"; -import { ResponseError } from "@/worker/protocol/http/error"; +} from "@/actor/router-endpoints"; +import { Encoding, serialize } from "@/actor/protocol/serde"; +import { ResponseError } from "@/actor/protocol/http/error"; export function logger() { return getLogger("router"); diff --git a/packages/core/src/common/utils.ts b/packages/core/src/common/utils.ts index 0de4becbe..6e707e6f8 100644 --- a/packages/core/src/common/utils.ts +++ b/packages/core/src/common/utils.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import type { ContentfulStatusCode } from "hono/utils/http-status"; -import * as errors from "@/worker/errors"; +import * as errors from "@/actor/errors"; import type { Logger } from "./log"; import { getEnvUniversal } from "@/utils"; @@ -9,9 +9,9 @@ import { getEnvUniversal } from "@/utils"; // Cloudflare's maximum key size is 512 bytes, so we need to be significantly smaller export const MAX_KEY_SIZE = 128; -export const WorkerKeySchema = z.array(z.string().max(MAX_KEY_SIZE)); +export const ActorKeySchema = z.array(z.string().max(MAX_KEY_SIZE)); -export type WorkerKey = z.infer; +export type ActorKey = z.infer; export interface RivetEnvironment { project?: string; @@ -117,7 +117,7 @@ export function isJsonSerializable( } export interface DeconstructedError { - __type: "WorkerError"; + __type: "ActorError"; statusCode: ContentfulStatusCode; public: boolean; code: string; @@ -140,7 +140,7 @@ export function deconstructError( let code: string; let message: string; let metadata: unknown = undefined; - if (errors.WorkerError.isWorkerError(error) && error.public) { + if (errors.ActorError.isActorError(error) && error.public) { statusCode = 400; public_ = true; code = error.code; @@ -153,7 +153,7 @@ export function deconstructError( ...extraLog, }); } else if (exposeInternalError) { - if (errors.WorkerError.isWorkerError(error)) { + if (errors.ActorError.isActorError(error)) { statusCode = 500; public_ = false; code = error.code; @@ -183,7 +183,7 @@ export function deconstructError( code = errors.INTERNAL_ERROR_CODE; message = errors.INTERNAL_ERROR_DESCRIPTION; metadata = { - //url: `https://hub.rivet.gg/projects/${workerMetadata.project.slug}/environments/${workerMetadata.environment.slug}/workers?workerId=${workerMetadata.worker.id}`, + //url: `https://hub.rivet.gg/projects/${actorMetadata.project.slug}/environments/${actorMetadata.environment.slug}/actors?actorId=${actorMetadata.actor.id}`, } satisfies errors.InternalErrorMetadata; logger.warn("internal error", { @@ -194,7 +194,7 @@ export function deconstructError( } return { - __type: "WorkerError", + __type: "ActorError", statusCode, public: public_, code, diff --git a/packages/core/src/driver-helpers/mod.ts b/packages/core/src/driver-helpers/mod.ts index 1ce93fa54..064432f1f 100644 --- a/packages/core/src/driver-helpers/mod.ts +++ b/packages/core/src/driver-helpers/mod.ts @@ -1,29 +1,29 @@ -export type { WorkerInstance, AnyWorkerInstance } from "@/worker/instance"; +export type { ActorInstance, AnyActorInstance } from "@/actor/instance"; export type { AttemptAcquireLease, ExtendLeaseOutput, - GetWorkerLeaderOutput, + GetActorLeaderOutput, NodeMessageCallback, CoordinateDriver, - StartWorkerAndAcquireLeaseOutput, + StartActorAndAcquireLeaseOutput, } from "@/topologies/coordinate/driver"; -export type { WorkerDriver } from "@/worker/driver"; +export type { ActorDriver } from "@/actor/driver"; export type { ManagerDriver, CreateInput, GetForIdInput, GetWithKeyInput, GetOrCreateWithKeyInput, - WorkerOutput, + ActorOutput, } from "@/manager/driver"; export { - HEADER_WORKER_QUERY, + HEADER_ACTOR_QUERY, HEADER_ENCODING, HEADER_EXPOSE_INTERNAL_ERROR, HEADER_CONN_PARAMS, HEADER_AUTH_DATA, - HEADER_WORKER_ID, + HEADER_ACTOR_ID, HEADER_CONN_ID, HEADER_CONN_TOKEN, -} from "@/worker/router-endpoints"; +} from "@/actor/router-endpoints"; export { RunConfigSchema, DriverConfigSchema } from "@/registry/run-config"; diff --git a/packages/core/src/driver-test-suite/mod.ts b/packages/core/src/driver-test-suite/mod.ts index 426f2dfe8..a64367828 100644 --- a/packages/core/src/driver-test-suite/mod.ts +++ b/packages/core/src/driver-test-suite/mod.ts @@ -1,5 +1,5 @@ import { serve as honoServe } from "@hono/node-server"; -import { runWorkerDriverTests } from "./tests/worker-driver"; +import { runActorDriverTests } from "./tests/actor-driver"; import { runManagerDriverTests } from "./tests/manager-driver"; import { describe } from "vitest"; import { @@ -14,14 +14,14 @@ import invariant from "invariant"; import { bundleRequire } from "bundle-require"; import { getPort } from "@/test/mod"; import { Transport } from "@/client/mod"; -import { runWorkerConnTests } from "./tests/worker-conn"; -import { runWorkerHandleTests } from "./tests/worker-handle"; +import { runActorConnTests } from "./tests/actor-conn"; +import { runActorHandleTests } from "./tests/actor-handle"; import { runActionFeaturesTests } from "./tests/action-features"; -import { runWorkerVarsTests } from "./tests/worker-vars"; -import { runWorkerConnStateTests } from "./tests/worker-conn-state"; -import { runWorkerMetadataTests } from "./tests/worker-metadata"; -import { runWorkerErrorHandlingTests } from "./tests/worker-error-handling"; -import { runWorkerAuthTests } from "./tests/worker-auth"; +import { runActorVarsTests } from "./tests/actor-vars"; +import { runActorConnStateTests } from "./tests/actor-conn-state"; +import { runActorMetadataTests } from "./tests/actor-metadata"; +import { runActorErrorHandlingTests } from "./tests/actor-error-handling"; +import { runActorAuthTests } from "./tests/actor-auth"; import { RunConfigSchema } from "@/registry/run-config"; export interface DriverTestConfig { @@ -67,33 +67,33 @@ export function runDriverTests( }; describe(`client type (${clientType})`, () => { - runWorkerDriverTests(driverTestConfig); + runActorDriverTests(driverTestConfig); runManagerDriverTests(driverTestConfig); // TODO: Add back SSE once fixed in Rivet driver & CF lifecycle // for (const transport of ["websocket", "sse"] as Transport[]) { for (const transport of ["websocket"] as Transport[]) { describe(`transport (${transport})`, () => { - runWorkerConnTests({ + runActorConnTests({ ...driverTestConfig, transport, }); - runWorkerConnStateTests({ ...driverTestConfig, transport }); + runActorConnStateTests({ ...driverTestConfig, transport }); }); } - runWorkerHandleTests(driverTestConfig); + runActorHandleTests(driverTestConfig); runActionFeaturesTests(driverTestConfig); - runWorkerVarsTests(driverTestConfig); + runActorVarsTests(driverTestConfig); - runWorkerMetadataTests(driverTestConfig); + runActorMetadataTests(driverTestConfig); - runWorkerErrorHandlingTests(driverTestConfig); + runActorErrorHandlingTests(driverTestConfig); - runWorkerAuthTests(driverTestConfig); + runActorAuthTests(driverTestConfig); }); } } diff --git a/packages/core/src/driver-test-suite/test-inline-client-driver.ts b/packages/core/src/driver-test-suite/test-inline-client-driver.ts index 0a08328f8..b6beec864 100644 --- a/packages/core/src/driver-test-suite/test-inline-client-driver.ts +++ b/packages/core/src/driver-test-suite/test-inline-client-driver.ts @@ -1,7 +1,7 @@ import { ClientDriver } from "@/client/client"; -import { type Encoding } from "@/worker/protocol/serde"; -import type * as wsToServer from "@/worker/protocol/message/to-server"; -import type { WorkerQuery } from "@/manager/protocol/query"; +import { type Encoding } from "@/actor/protocol/serde"; +import type * as wsToServer from "@/actor/protocol/message/to-server"; +import type { ActorQuery } from "@/manager/protocol/query"; import { Context as HonoContext } from "hono"; import type { EventSource } from "eventsource"; import { Transport } from "@/client/mod"; @@ -10,9 +10,9 @@ import { TestInlineDriverCallRequest, TestInlineDriverCallResponse, } from "@/manager/router"; -import { assertUnreachable } from "@/worker/utils"; +import { assertUnreachable } from "@/actor/utils"; import * as cbor from "cbor-x"; -import { WorkerError as ClientWorkerError } from "@/client/errors"; +import { ActorError as ClientActorError } from "@/client/errors"; import type { WebSocket } from "ws"; import { importWebSocket } from "@/common/websocket"; @@ -26,7 +26,7 @@ export function createTestInlineClientDriver( return { action: async = unknown[], Response = unknown>( c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encoding: Encoding, params: unknown, name: string, @@ -37,13 +37,13 @@ export function createTestInlineClientDriver( encoding, transport, "action", - [undefined, workerQuery, encoding, params, name, args], + [undefined, actorQuery, encoding, params, name, args], ); }, - resolveWorkerId: async ( + resolveActorId: async ( c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encodingKind: Encoding, params: unknown, ): Promise => { @@ -51,21 +51,21 @@ export function createTestInlineClientDriver( endpoint, encodingKind, transport, - "resolveWorkerId", - [undefined, workerQuery, encodingKind, params], + "resolveActorId", + [undefined, actorQuery, encodingKind, params], ); }, connectWebSocket: async ( c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encodingKind: Encoding, params: unknown, ): Promise => { const WebSocket = await importWebSocket(); logger().info("creating websocket connection via test inline driver", { - workerQuery, + actorQuery, encodingKind, }); @@ -73,7 +73,7 @@ export function createTestInlineClientDriver( const wsUrl = new URL( `${endpoint}/.test/inline-driver/connect-websocket`, ); - wsUrl.searchParams.set("workerQuery", JSON.stringify(workerQuery)); + wsUrl.searchParams.set("actorQuery", JSON.stringify(actorQuery)); if (params !== undefined) wsUrl.searchParams.set("params", JSON.stringify(params)); wsUrl.searchParams.set("encodingKind", encodingKind); @@ -94,12 +94,12 @@ export function createTestInlineClientDriver( connectSse: async ( c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encodingKind: Encoding, params: unknown, ): Promise => { logger().info("creating sse connection via test inline driver", { - workerQuery, + actorQuery, encodingKind, params, }); @@ -111,7 +111,7 @@ export function createTestInlineClientDriver( (EventSourceImport as any).default || EventSourceImport; // Encode parameters for the URL - const workerQueryParam = encodeURIComponent(JSON.stringify(workerQuery)); + const actorQueryParam = encodeURIComponent(JSON.stringify(actorQuery)); const encodingParam = encodeURIComponent(encodingKind); const paramsParam = params ? encodeURIComponent(JSON.stringify(params)) @@ -119,7 +119,7 @@ export function createTestInlineClientDriver( // Create SSE connection URL const sseUrl = new URL(`${endpoint}/.test/inline-driver/connect-sse`); - sseUrl.searchParams.set("workerQueryRaw", workerQueryParam); + sseUrl.searchParams.set("actorQueryRaw", actorQueryParam); sseUrl.searchParams.set("encodingKind", encodingParam); if (paramsParam) { sseUrl.searchParams.set("params", paramsParam); @@ -155,14 +155,14 @@ export function createTestInlineClientDriver( sendHttpMessage: async ( c: HonoContext | undefined, - workerId: string, + actorId: string, encoding: Encoding, connectionId: string, connectionToken: string, message: wsToServer.ToServer, ): Promise => { logger().info("sending http message via test inline driver", { - workerId, + actorId, encoding, connectionId, transport, @@ -179,7 +179,7 @@ export function createTestInlineClientDriver( method: "sendHttpMessage", args: [ undefined, - workerId, + actorId, encoding, connectionId, connectionToken, @@ -244,7 +244,7 @@ async function makeInlineRequest( if ("ok" in callResponse) { return callResponse.ok; } else if ("err" in callResponse) { - throw new ClientWorkerError( + throw new ClientActorError( callResponse.err.code, callResponse.err.message, callResponse.err.metadata, diff --git a/packages/core/src/driver-test-suite/tests/action-features.ts b/packages/core/src/driver-test-suite/tests/action-features.ts index ee93c3f00..cb1500999 100644 --- a/packages/core/src/driver-test-suite/tests/action-features.ts +++ b/packages/core/src/driver-test-suite/tests/action-features.ts @@ -1,7 +1,7 @@ import { describe, test, expect } from "vitest"; import type { DriverTestConfig } from "../mod"; import { setupDriverTest } from "../utils"; -import { WorkerError } from "@/client/errors"; +import { ActorError } from "@/client/errors"; export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { describe("Action Features", () => { @@ -17,14 +17,14 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { ); // The quick action should complete successfully - const quickResult = await client.shortTimeoutWorker + const quickResult = await client.shortTimeoutActor .getOrCreate() .quickAction(); expect(quickResult).toBe("quick response"); // The slow action should throw a timeout error await expect( - client.shortTimeoutWorker.getOrCreate().slowAction(), + client.shortTimeoutActor.getOrCreate().slowAction(), ).rejects.toThrow("Action timed out"); }); @@ -36,7 +36,7 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { ); // This action should complete within the default timeout - const result = await client.defaultTimeoutWorker + const result = await client.defaultTimeoutActor .getOrCreate() .normalAction(); expect(result).toBe("normal response"); @@ -50,24 +50,24 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { ); // Synchronous action should not be affected by timeout - const result = await client.syncTimeoutWorker.getOrCreate().syncAction(); + const result = await client.syncTimeoutActor.getOrCreate().syncAction(); expect(result).toBe("sync response"); }); - test("should allow configuring different timeouts for different workers", async (c) => { + test("should allow configuring different timeouts for different actors", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, ); - // The short timeout worker should fail + // The short timeout actor should fail await expect( - client.shortTimeoutWorker.getOrCreate().slowAction(), + client.shortTimeoutActor.getOrCreate().slowAction(), ).rejects.toThrow("Action timed out"); - // The longer timeout worker should succeed - const result = await client.longTimeoutWorker + // The longer timeout actor should succeed + const result = await client.longTimeoutActor .getOrCreate() .delayedAction(); expect(result).toBe("delayed response"); @@ -82,7 +82,7 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { ); - const instance = client.syncActionWorker.getOrCreate(); + const instance = client.syncActionActor.getOrCreate(); // Test increment action let result = await instance.increment(5); @@ -109,7 +109,7 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { ); - const instance = client.asyncActionWorker.getOrCreate(); + const instance = client.asyncActionActor.getOrCreate(); // Test delayed increment const result = await instance.delayedIncrement(5); @@ -129,7 +129,7 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { await instance.asyncWithError(true); expect.fail("did not error"); } catch (error) { - expect((error as WorkerError).message).toBe("Intentional error"); + expect((error as ActorError).message).toBe("Intentional error"); } }); @@ -140,7 +140,7 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { ); - const instance = client.promiseWorker.getOrCreate(); + const instance = client.promiseActor.getOrCreate(); // Test resolved promise const resolvedValue = await instance.resolvedPromise(); diff --git a/packages/core/src/driver-test-suite/tests/worker-auth.ts b/packages/core/src/driver-test-suite/tests/actor-auth.ts similarity index 78% rename from packages/core/src/driver-test-suite/tests/worker-auth.ts rename to packages/core/src/driver-test-suite/tests/actor-auth.ts index c7fcb61ce..4a4775015 100644 --- a/packages/core/src/driver-test-suite/tests/worker-auth.ts +++ b/packages/core/src/driver-test-suite/tests/actor-auth.ts @@ -1,16 +1,16 @@ import { describe, test, expect } from "vitest"; import type { DriverTestConfig } from "../mod"; import { setupDriverTest } from "../utils"; -import { WorkerError } from "@/client/errors"; +import { ActorError } from "@/client/errors"; -export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { - describe("Worker Authentication Tests", () => { +export function runActorAuthTests(driverTestConfig: DriverTestConfig) { + describe("Actor Authentication Tests", () => { describe("Basic Authentication", () => { test("should allow access with valid auth", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); // Create client with valid auth params - const instance = client.authWorker.getOrCreate(undefined, { + const instance = client.authActor.getOrCreate(undefined, { params: { apiKey: "valid-api-key" }, }); @@ -33,7 +33,7 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { const { client } = await setupDriverTest(c, driverTestConfig); // This should fail without proper authorization - const instance = client.authWorker.getOrCreate(); + const instance = client.authActor.getOrCreate(); if (driverTestConfig.clientType === "inline") { // Inline clients bypass authentication @@ -45,7 +45,7 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { await instance.getRequests(); expect.fail("Expected authentication error"); } catch (error) { - expect((error as WorkerError).code).toBe("missing_auth"); + expect((error as ActorError).code).toBe("missing_auth"); } } }); @@ -53,7 +53,7 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { test("should expose auth data on connection", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - const instance = client.authWorker.getOrCreate(undefined, { + const instance = client.authActor.getOrCreate(undefined, { params: { apiKey: "valid-api-key" }, }); @@ -75,19 +75,19 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { test("should allow get operations for any role", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - const createdInstance = await client.intentAuthWorker.create(["foo"], { + const createdInstance = await client.intentAuthActor.create(["foo"], { params: { role: "admin" }, }); - const workerId = await createdInstance.resolve(); + const actorId = await createdInstance.resolve(); if (driverTestConfig.clientType === "inline") { // Inline clients bypass authentication - const instance = client.intentAuthWorker.getForId(workerId); + const instance = client.intentAuthActor.getForId(actorId); const value = await instance.getValue(); expect(value).toBe(0); } else { // HTTP clients - actions require user or admin role - const instance = client.intentAuthWorker.getForId(workerId, { + const instance = client.intentAuthActor.getForId(actorId, { params: { role: "user" }, // Actions require user or admin role }); const value = await instance.getValue(); @@ -100,7 +100,7 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { if (driverTestConfig.clientType === "inline") { // Inline clients bypass authentication - should succeed - const instance = client.intentAuthWorker.getOrCreate(undefined, { + const instance = client.intentAuthActor.getOrCreate(undefined, { params: { role: "user" }, }); const value = await instance.getValue(); @@ -108,14 +108,14 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { } else { // HTTP clients should enforce authentication try { - const instance = client.intentAuthWorker.getOrCreate(undefined, { + const instance = client.intentAuthActor.getOrCreate(undefined, { params: { role: "user" }, }); await instance.getValue(); expect.fail("Expected permission error for create operation"); } catch (error) { - expect((error as WorkerError).code).toBe("insufficient_permissions"); - expect((error as WorkerError).message).toContain( + expect((error as ActorError).code).toBe("insufficient_permissions"); + expect((error as ActorError).message).toContain( "Admin role required", ); } @@ -125,13 +125,13 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { test("should allow actions for user and admin roles", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - const createdInstance = await client.intentAuthWorker.create(["foo"], { + const createdInstance = await client.intentAuthActor.create(["foo"], { params: { role: "admin" }, }); - const workerId = await createdInstance.resolve(); + const actorId = await createdInstance.resolve(); // This should fail - actions require user or admin role - const instance = client.intentAuthWorker.getForId(workerId, { + const instance = client.intentAuthActor.getForId(actorId, { params: { role: "guest" }, }); @@ -145,8 +145,8 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { await instance.setValue(42); expect.fail("Expected permission error for action"); } catch (error) { - expect((error as WorkerError).code).toBe("insufficient_permissions"); - expect((error as WorkerError).message).toContain( + expect((error as ActorError).code).toBe("insufficient_permissions"); + expect((error as ActorError).message).toContain( "User or admin role required", ); } @@ -158,8 +158,8 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { test("should allow access with empty onAuth", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Public worker should allow access without authentication - const instance = client.publicWorker.getOrCreate(); + // Public actor should allow access without authentication + const instance = client.publicActor.getOrCreate(); const visitors = await instance.visit(); expect(visitors).toBe(1); @@ -172,8 +172,8 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { test("should deny access without onAuth defined", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Worker without onAuth should be blocked - const instance = client.noAuthWorker.getOrCreate(); + // Actor without onAuth should be blocked + const instance = client.noAuthActor.getOrCreate(); if (driverTestConfig.clientType === "inline") { // Inline clients bypass authentication - should succeed @@ -183,9 +183,9 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { // HTTP clients should enforce authentication try { await instance.getValue(); - expect.fail("Expected access to be denied for worker without onAuth"); + expect.fail("Expected access to be denied for actor without onAuth"); } catch (error) { - expect((error as WorkerError).code).toBe("forbidden"); + expect((error as ActorError).code).toBe("forbidden"); } } }); @@ -195,7 +195,7 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { test("should handle promise-based auth", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - const instance = client.asyncAuthWorker.getOrCreate(undefined, { + const instance = client.asyncAuthActor.getOrCreate(undefined, { params: { token: "valid" }, }); @@ -219,7 +219,7 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { test("should handle async auth failures", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - const instance = client.asyncAuthWorker.getOrCreate(); + const instance = client.asyncAuthActor.getOrCreate(); if (driverTestConfig.clientType === "inline") { // Inline clients bypass authentication - should succeed @@ -231,7 +231,7 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { await instance.increment(); expect.fail("Expected async auth failure"); } catch (error) { - expect((error as WorkerError).code).toBe("missing_token"); + expect((error as ActorError).code).toBe("missing_token"); } } }); @@ -243,7 +243,7 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { const { client } = await setupDriverTest(c, driverTestConfig); // Test WebSocket connection auth - const instance = client.authWorker.getOrCreate(undefined, { + const instance = client.authActor.getOrCreate(undefined, { params: { apiKey: "valid-api-key" }, }); @@ -258,7 +258,7 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { const { client } = await setupDriverTest(c, driverTestConfig); // Test HTTP action auth - const instance = client.authWorker.getOrCreate(undefined, { + const instance = client.authActor.getOrCreate(undefined, { params: { apiKey: "valid-api-key" }, }); @@ -272,7 +272,7 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { test("should handle auth errors gracefully", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - const instance = client.authWorker.getOrCreate(); + const instance = client.authActor.getOrCreate(); if (driverTestConfig.clientType === "inline") { // Inline clients bypass authentication - should succeed @@ -285,9 +285,9 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { expect.fail("Expected authentication error"); } catch (error) { // Error should be properly structured - const workerError = error as WorkerError; - expect(workerError.code).toBeDefined(); - expect(workerError.message).toBeDefined(); + const actorError = error as ActorError; + expect(actorError.code).toBeDefined(); + expect(actorError.message).toBeDefined(); } } }); @@ -295,7 +295,7 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { test("should preserve error details for debugging", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - const instance = client.asyncAuthWorker.getOrCreate(); + const instance = client.asyncAuthActor.getOrCreate(); if (driverTestConfig.clientType === "inline") { // Inline clients bypass authentication - should succeed @@ -307,9 +307,9 @@ export function runWorkerAuthTests(driverTestConfig: DriverTestConfig) { await instance.increment(); expect.fail("Expected token error"); } catch (error) { - const workerError = error as WorkerError; - expect(workerError.code).toBe("missing_token"); - expect(workerError.message).toBe("Token required"); + const actorError = error as ActorError; + expect(actorError.code).toBe("missing_token"); + expect(actorError.message).toBe("Token required"); } } }); diff --git a/packages/core/src/driver-test-suite/tests/worker-conn-state.ts b/packages/core/src/driver-test-suite/tests/actor-conn-state.ts similarity index 88% rename from packages/core/src/driver-test-suite/tests/worker-conn-state.ts rename to packages/core/src/driver-test-suite/tests/actor-conn-state.ts index 0054aceab..5cafd69df 100644 --- a/packages/core/src/driver-test-suite/tests/worker-conn-state.ts +++ b/packages/core/src/driver-test-suite/tests/actor-conn-state.ts @@ -2,14 +2,14 @@ import { describe, test, expect, vi } from "vitest"; import type { DriverTestConfig } from "../mod"; import { setupDriverTest } from "../utils"; -export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { - describe("Worker Connection State Tests", () => { +export function runActorConnStateTests(driverTestConfig: DriverTestConfig) { + describe("Actor Connection State Tests", () => { describe("Connection State Initialization", () => { test("should retrieve connection state", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Connect to the worker - const connection = client.connStateWorker.getOrCreate().connect(); + // Connect to the actor + const connection = client.connStateActor.getOrCreate().connect(); // Get the connection state const connState = await connection.getConnectionState(); @@ -29,7 +29,7 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { const { client } = await setupDriverTest(c, driverTestConfig); // Connect with custom parameters - const connection = client.connStateWorker + const connection = client.connStateActor .getOrCreate([], { params: { username: "testuser", @@ -55,13 +55,13 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { const { client } = await setupDriverTest(c, driverTestConfig); // Create multiple connections - const conn1 = client.connStateWorker + const conn1 = client.connStateActor .getOrCreate([], { params: { username: "user1" }, }) .connect(); - const conn2 = client.connStateWorker + const conn2 = client.connStateActor .getOrCreate([], { params: { username: "user2" }, }) @@ -90,14 +90,14 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { const { client } = await setupDriverTest(c, driverTestConfig); // Create two connections - const handle = client.connStateWorker.getOrCreate(); + const handle = client.connStateActor.getOrCreate(); const conn1 = handle.connect(); const conn2 = handle.connect(); // Get state1 for reference const state1 = await conn1.getConnectionState(); - // Get connection IDs tracked by the worker + // Get connection IDs tracked by the actor const connectionIds = await conn1.getConnectionIds(); // There should be at least 2 connections tracked @@ -111,11 +111,11 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { await conn2.dispose(); }); - test("should identify different connections in the same worker", async (c) => { + test("should identify different connections in the same actor", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Create two connections to the same worker - const handle = client.connStateWorker.getOrCreate(); + // Create two connections to the same actor + const handle = client.connStateActor.getOrCreate(); const conn1 = handle.connect(); const conn2 = handle.connect(); @@ -141,7 +141,7 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { const { client } = await setupDriverTest(c, driverTestConfig); // Create a connection - const handle = client.connStateWorker.getOrCreate(); + const handle = client.connStateActor.getOrCreate(); const conn = handle.connect(); // Get the connection state @@ -175,7 +175,7 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { const { client } = await setupDriverTest(c, driverTestConfig); // Create a connection - const conn = client.connStateWorker.getOrCreate().connect(); + const conn = client.connStateActor.getOrCreate().connect(); // Get the initial state const initialState = await conn.getConnectionState(); @@ -206,7 +206,7 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { const { client } = await setupDriverTest(c, driverTestConfig); // Create two connections - const handle = client.connStateWorker.getOrCreate(); + const handle = client.connStateActor.getOrCreate(); const conn1 = handle.connect(); const conn2 = handle.connect(); diff --git a/packages/core/src/driver-test-suite/tests/worker-conn.ts b/packages/core/src/driver-test-suite/tests/actor-conn.ts similarity index 92% rename from packages/core/src/driver-test-suite/tests/worker-conn.ts rename to packages/core/src/driver-test-suite/tests/actor-conn.ts index 743ccae58..8da9485d5 100644 --- a/packages/core/src/driver-test-suite/tests/worker-conn.ts +++ b/packages/core/src/driver-test-suite/tests/actor-conn.ts @@ -2,13 +2,13 @@ import { describe, test, expect, vi } from "vitest"; import type { DriverTestConfig } from "../mod"; import { setupDriverTest } from "../utils"; -export function runWorkerConnTests(driverTestConfig: DriverTestConfig) { - describe("Worker Connection Tests", () => { +export function runActorConnTests(driverTestConfig: DriverTestConfig) { + describe("Actor Connection Tests", () => { describe("Connection Methods", () => { test("should connect using .get().connect()", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Create worker + // Create actor await client.counter.create(["test-get"]); // Get a handle and connect @@ -26,15 +26,15 @@ export function runWorkerConnTests(driverTestConfig: DriverTestConfig) { test("should connect using .getForId().connect()", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Create a worker first to get its ID + // Create a actor first to get its ID const handle = client.counter.getOrCreate([ "test-get-for-id", ]); await handle.increment(3); - const workerId = await handle.resolve(); + const actorId = await handle.resolve(); - // Get a new handle using the worker ID and connect - const idHandle = client.counter.getForId(workerId); + // Get a new handle using the actor ID and connect + const idHandle = client.counter.getForId(actorId); const connection = idHandle.connect(); // Verify connection works and state is preserved @@ -48,7 +48,7 @@ export function runWorkerConnTests(driverTestConfig: DriverTestConfig) { test("should connect using .getOrCreate().connect()", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Get or create worker and connect + // Get or create actor and connect const handle = client.counter.getOrCreate([ "test-get-or-create", ]); @@ -65,7 +65,7 @@ export function runWorkerConnTests(driverTestConfig: DriverTestConfig) { test("should connect using (await create()).connect()", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Create worker and connect + // Create actor and connect const handle = await client.counter.create(["test-create"]); const connection = handle.connect(); @@ -82,7 +82,7 @@ export function runWorkerConnTests(driverTestConfig: DriverTestConfig) { test("should receive events via broadcast", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Create worker and connect + // Create actor and connect const handle = client.counter.getOrCreate(["test-broadcast"]); const connection = handle.connect(); @@ -107,7 +107,7 @@ export function runWorkerConnTests(driverTestConfig: DriverTestConfig) { test("should handle one-time events with once()", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Create worker and connect + // Create actor and connect const handle = client.counter.getOrCreate(["test-once"]); const connection = handle.connect(); @@ -132,7 +132,7 @@ export function runWorkerConnTests(driverTestConfig: DriverTestConfig) { test("should unsubscribe from events", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Create worker and connect + // Create actor and connect const handle = client.counter.getOrCreate([ "test-unsubscribe", ]); diff --git a/packages/core/src/driver-test-suite/tests/actor-driver.ts b/packages/core/src/driver-test-suite/tests/actor-driver.ts new file mode 100644 index 000000000..9417b47e3 --- /dev/null +++ b/packages/core/src/driver-test-suite/tests/actor-driver.ts @@ -0,0 +1,16 @@ +import { describe } from "vitest"; +import type { DriverTestConfig } from "../mod"; +import { runActorStateTests } from "./actor-state"; +import { runActorScheduleTests } from "./actor-schedule"; + +export function runActorDriverTests( + driverTestConfig: DriverTestConfig +) { + describe("Actor Driver Tests", () => { + // Run state persistence tests + runActorStateTests(driverTestConfig); + + // Run scheduled alarms tests + runActorScheduleTests(driverTestConfig); + }); +} diff --git a/packages/core/src/driver-test-suite/tests/worker-error-handling.ts b/packages/core/src/driver-test-suite/tests/actor-error-handling.ts similarity index 82% rename from packages/core/src/driver-test-suite/tests/worker-error-handling.ts rename to packages/core/src/driver-test-suite/tests/actor-error-handling.ts index bf4753c2c..1a2913e1a 100644 --- a/packages/core/src/driver-test-suite/tests/worker-error-handling.ts +++ b/packages/core/src/driver-test-suite/tests/actor-error-handling.ts @@ -1,16 +1,16 @@ import { describe, test, expect } from "vitest"; import type { DriverTestConfig } from "../mod"; import { setupDriverTest } from "../utils"; -import { assertUnreachable } from "@/worker/utils"; +import { assertUnreachable } from "@/actor/utils"; import { INTERNAL_ERROR_CODE, INTERNAL_ERROR_DESCRIPTION, -} from "@/worker/errors"; +} from "@/actor/errors"; -export function runWorkerErrorHandlingTests( +export function runActorErrorHandlingTests( driverTestConfig: DriverTestConfig, ) { - describe("Worker Error Handling Tests", () => { + describe("Actor Error Handling Tests", () => { describe("UserError Handling", () => { test("should handle simple UserError with message", async (c) => { const { client } = await setupDriverTest( @@ -20,7 +20,7 @@ export function runWorkerErrorHandlingTests( ); // Try to call an action that throws a simple UserError - const handle = client.errorHandlingWorker.getOrCreate(); + const handle = client.errorHandlingActor.getOrCreate(); try { await handle.throwSimpleError(); @@ -44,7 +44,7 @@ export function runWorkerErrorHandlingTests( ); // Try to call an action that throws a detailed UserError - const handle = client.errorHandlingWorker.getOrCreate(); + const handle = client.errorHandlingActor.getOrCreate(); try { await handle.throwDetailedError(); @@ -70,7 +70,7 @@ export function runWorkerErrorHandlingTests( ); // Try to call an action that throws an internal error - const handle = client.errorHandlingWorker.getOrCreate(); + const handle = client.errorHandlingActor.getOrCreate(); try { await handle.throwInternalError(); @@ -103,9 +103,9 @@ export function runWorkerErrorHandlingTests( ); // Call an action that should time out - const handle = client.errorHandlingWorker.getOrCreate(); + const handle = client.errorHandlingActor.getOrCreate(); - // This should throw a timeout error because errorHandlingWorker has + // This should throw a timeout error because errorHandlingActor has // a 500ms timeout and this action tries to run for much longer const timeoutPromise = handle.timeoutAction(); @@ -127,27 +127,27 @@ export function runWorkerErrorHandlingTests( ); // Call an action with a delay shorter than the timeout - const handle = client.errorHandlingWorker.getOrCreate(); + const handle = client.errorHandlingActor.getOrCreate(); // This should succeed because 200ms < 500ms timeout const result = await handle.delayedAction(200); expect(result).toBe("Completed after 200ms"); }); - test("should respect different timeouts for different workers", async (c) => { + test("should respect different timeouts for different actors", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, ); - // The following workers have different timeout settings: - // customTimeoutWorker: 200ms timeout - // standardTimeoutWorker: default timeout (much longer) + // The following actors have different timeout settings: + // customTimeoutActor: 200ms timeout + // standardTimeoutActor: default timeout (much longer) // This should fail - 300ms delay with 200ms timeout try { - await client.customTimeoutWorker.getOrCreate().slowAction(); + await client.customTimeoutActor.getOrCreate().slowAction(); // Should not reach here expect(true).toBe(false); } catch (error: any) { @@ -155,7 +155,7 @@ export function runWorkerErrorHandlingTests( } // This should succeed - 50ms delay with 200ms timeout - const quickResult = await client.customTimeoutWorker + const quickResult = await client.customTimeoutActor .getOrCreate() .quickAction(); expect(quickResult).toBe("Quick action completed"); @@ -170,7 +170,7 @@ export function runWorkerErrorHandlingTests( ); - const handle = client.errorHandlingWorker.getOrCreate(); + const handle = client.errorHandlingActor.getOrCreate(); // Trigger an error try { @@ -179,7 +179,7 @@ export function runWorkerErrorHandlingTests( // Ignore error } - // Worker should still work after error + // Actor should still work after error const result = await handle.successfulAction(); expect(result).toBe("success"); }); diff --git a/packages/core/src/driver-test-suite/tests/worker-handle.ts b/packages/core/src/driver-test-suite/tests/actor-handle.ts similarity index 83% rename from packages/core/src/driver-test-suite/tests/worker-handle.ts rename to packages/core/src/driver-test-suite/tests/actor-handle.ts index 61595e24c..3d7842ee0 100644 --- a/packages/core/src/driver-test-suite/tests/worker-handle.ts +++ b/packages/core/src/driver-test-suite/tests/actor-handle.ts @@ -2,13 +2,13 @@ import { describe, test, expect, vi } from "vitest"; import type { DriverTestConfig } from "../mod"; import { setupDriverTest, waitFor } from "../utils"; -export function runWorkerHandleTests(driverTestConfig: DriverTestConfig) { - describe("Worker Handle Tests", () => { +export function runActorHandleTests(driverTestConfig: DriverTestConfig) { + describe("Actor Handle Tests", () => { describe("Access Methods", () => { - test("should use .get() to access a worker", async (c) => { + test("should use .get() to access a actor", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Create worker first + // Create actor first await client.counter.create(["test-get-handle"]); // Access using get @@ -22,16 +22,16 @@ export function runWorkerHandleTests(driverTestConfig: DriverTestConfig) { expect(retrievedCount).toBe(5); }); - test("should use .getForId() to access a worker by ID", async (c) => { + test("should use .getForId() to access a actor by ID", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Create a worker first to get its ID + // Create a actor first to get its ID const handle = client.counter.getOrCreate(["test-get-for-id-handle"]); await handle.increment(3); - const workerId = await handle.resolve(); + const actorId = await handle.resolve(); // Access using getForId - const idHandle = client.counter.getForId(workerId); + const idHandle = client.counter.getForId(actorId); // Verify Action works and state is preserved const count = await idHandle.getCount(); @@ -41,10 +41,10 @@ export function runWorkerHandleTests(driverTestConfig: DriverTestConfig) { expect(newCount).toBe(7); }); - test("should use .getOrCreate() to access or create a worker", async (c) => { + test("should use .getOrCreate() to access or create a actor", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Access using getOrCreate - should create the worker + // Access using getOrCreate - should create the actor const handle = client.counter.getOrCreate([ "test-get-or-create-handle", ]); @@ -53,7 +53,7 @@ export function runWorkerHandleTests(driverTestConfig: DriverTestConfig) { const count = await handle.increment(7); expect(count).toBe(7); - // Get the same worker again - should retrieve existing worker + // Get the same actor again - should retrieve existing actor const sameHandle = client.counter.getOrCreate([ "test-get-or-create-handle", ]); @@ -64,7 +64,7 @@ export function runWorkerHandleTests(driverTestConfig: DriverTestConfig) { test("should use (await create()) to create and return a handle", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Create worker and get handle + // Create actor and get handle const handle = await client.counter.create(["test-create-handle"]); // Verify Action works @@ -93,10 +93,10 @@ export function runWorkerHandleTests(driverTestConfig: DriverTestConfig) { expect(retrievedCount).toBe(8); }); - test("should handle independent handles to the same worker", async (c) => { + test("should handle independent handles to the same actor", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Create two handles to the same worker + // Create two handles to the same actor const handle1 = client.counter.getOrCreate(["test-multiple-handles"]); const handle2 = client.counter.get(["test-multiple-handles"]); @@ -114,33 +114,33 @@ export function runWorkerHandleTests(driverTestConfig: DriverTestConfig) { expect(checkCount).toBe(7); }); - test("should resolve a worker's ID", async (c) => { + test("should resolve a actor's ID", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); const handle = client.counter.getOrCreate(["test-resolve-id"]); - // Call an action to ensure worker exists + // Call an action to ensure actor exists await handle.increment(1); // Resolve the ID - const workerId = await handle.resolve(); + const actorId = await handle.resolve(); // Verify we got a valid ID (string) - expect(typeof workerId).toBe("string"); - expect(workerId).not.toBe(""); + expect(typeof actorId).toBe("string"); + expect(actorId).not.toBe(""); - // Verify we can use this ID to get the worker - const idHandle = client.counter.getForId(workerId); + // Verify we can use this ID to get the actor + const idHandle = client.counter.getForId(actorId); const count = await idHandle.getCount(); expect(count).toBe(1); }); }); describe("Lifecycle Hooks", () => { - test("should trigger lifecycle hooks on worker creation", async (c) => { + test("should trigger lifecycle hooks on actor creation", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig); - // Get or create a new worker - this should trigger onStart + // Get or create a new actor - this should trigger onStart const handle = client.counterWithLifecycle.getOrCreate([ "test-lifecycle-handle", ]); @@ -149,13 +149,13 @@ export function runWorkerHandleTests(driverTestConfig: DriverTestConfig) { const initialEvents = await handle.getEvents(); expect(initialEvents).toContain("onStart"); - // Create a separate handle to the same worker + // Create a separate handle to the same actor const sameHandle = client.counterWithLifecycle.getOrCreate([ "test-lifecycle-handle", ]); // Verify events still include onStart but don't duplicate it - // (onStart should only be called once when the worker is first created) + // (onStart should only be called once when the actor is first created) const events = await sameHandle.getEvents(); expect(events).toContain("onStart"); expect(events.filter((e) => e === "onStart").length).toBe(1); @@ -230,7 +230,7 @@ export function runWorkerHandleTests(driverTestConfig: DriverTestConfig) { "test-lifecycle-multi-handle", ]); - // Create two tracking handles to the same worker + // Create two tracking handles to the same actor const trackingHandle1 = client.counterWithLifecycle.getOrCreate( ["test-lifecycle-multi-handle"], { params: { trackLifecycle: true } }, diff --git a/packages/core/src/driver-test-suite/tests/worker-metadata.ts b/packages/core/src/driver-test-suite/tests/actor-metadata.ts similarity index 71% rename from packages/core/src/driver-test-suite/tests/worker-metadata.ts rename to packages/core/src/driver-test-suite/tests/actor-metadata.ts index 7b620ce73..377889e47 100644 --- a/packages/core/src/driver-test-suite/tests/worker-metadata.ts +++ b/packages/core/src/driver-test-suite/tests/actor-metadata.ts @@ -2,43 +2,43 @@ import { describe, test, expect } from "vitest"; import type { DriverTestConfig } from "../mod"; import { setupDriverTest } from "../utils"; -export function runWorkerMetadataTests( +export function runActorMetadataTests( driverTestConfig: DriverTestConfig ) { - describe("Worker Metadata Tests", () => { - describe("Worker Name", () => { - test("should provide access to worker name", async (c) => { + describe("Actor Metadata Tests", () => { + describe("Actor Name", () => { + test("should provide access to actor name", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, ); - // Get the worker name - const handle = client.metadataWorker.getOrCreate(); - const workerName = await handle.getWorkerName(); + // Get the actor name + const handle = client.metadataActor.getOrCreate(); + const actorName = await handle.getActorName(); // Verify it matches the expected name - expect(workerName).toBe("metadataWorker"); + expect(actorName).toBe("metadataActor"); }); - test("should preserve worker name in state during onStart", async (c) => { + test("should preserve actor name in state during onStart", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, ); - // Get the stored worker name - const handle = client.metadataWorker.getOrCreate(); - const storedName = await handle.getStoredWorkerName(); + // Get the stored actor name + const handle = client.metadataActor.getOrCreate(); + const storedName = await handle.getStoredActorName(); // Verify it was stored correctly - expect(storedName).toBe("metadataWorker"); + expect(storedName).toBe("metadataActor"); }); }); - describe("Worker Tags", () => { + describe("Actor Tags", () => { test("should provide access to tags", async (c) => { const { client } = await setupDriverTest( c, @@ -46,8 +46,8 @@ export function runWorkerMetadataTests( ); - // Create worker and set up test tags - const handle = client.metadataWorker.getOrCreate(); + // Create actor and set up test tags + const handle = client.metadataActor.getOrCreate(); await handle.setupTestTags({ "env": "test", "purpose": "metadata-test" @@ -70,10 +70,10 @@ export function runWorkerMetadataTests( ); - // Create worker and set up test tags - const handle = client.metadataWorker.getOrCreate(); + // Create actor and set up test tags + const handle = client.metadataActor.getOrCreate(); await handle.setupTestTags({ - "category": "test-worker", + "category": "test-actor", "version": "1.0" }); @@ -83,7 +83,7 @@ export function runWorkerMetadataTests( const nonexistent = await handle.getTag("nonexistent"); // Verify the tag values - expect(category).toBe("test-worker"); + expect(category).toBe("test-actor"); expect(version).toBe("1.0"); expect(nonexistent).toBeNull(); }); @@ -97,8 +97,8 @@ export function runWorkerMetadataTests( ); - // Create worker and set up test metadata - const handle = client.metadataWorker.getOrCreate(); + // Create actor and set up test metadata + const handle = client.metadataActor.getOrCreate(); await handle.setupTestTags({ "type": "metadata-test" }); await handle.setupTestRegion("us-west-1"); @@ -107,7 +107,7 @@ export function runWorkerMetadataTests( // Verify structure of metadata expect(metadata).toHaveProperty("name"); - expect(metadata.name).toBe("metadataWorker"); + expect(metadata.name).toBe("metadataActor"); expect(metadata).toHaveProperty("tags"); expect(metadata.tags).toHaveProperty("type"); @@ -127,8 +127,8 @@ export function runWorkerMetadataTests( ); - // Create worker and set up test region - const handle = client.metadataWorker.getOrCreate(); + // Create actor and set up test region + const handle = client.metadataActor.getOrCreate(); await handle.setupTestRegion("eu-central-1"); // Get the region diff --git a/packages/core/src/driver-test-suite/tests/worker-schedule.ts b/packages/core/src/driver-test-suite/tests/actor-schedule.ts similarity index 94% rename from packages/core/src/driver-test-suite/tests/worker-schedule.ts rename to packages/core/src/driver-test-suite/tests/actor-schedule.ts index 49e3b2852..fa7d323e3 100644 --- a/packages/core/src/driver-test-suite/tests/worker-schedule.ts +++ b/packages/core/src/driver-test-suite/tests/actor-schedule.ts @@ -2,10 +2,10 @@ import { describe, test, expect } from "vitest"; import type { DriverTestConfig } from "../mod"; import { setupDriverTest, waitFor } from "../utils"; -export function runWorkerScheduleTests( +export function runActorScheduleTests( driverTestConfig: DriverTestConfig ) { - describe("Worker Schedule Tests", () => { + describe("Actor Schedule Tests", () => { describe("Scheduled Alarms", () => { test("executes c.schedule.at() with specific timestamp", async (c) => { const { client } = await setupDriverTest( @@ -56,7 +56,7 @@ export function runWorkerScheduleTests( expect(scheduledCount).toBe(1); }); - test("scheduled tasks persist across worker restarts", async (c) => { + test("scheduled tasks persist across actor restarts", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -70,7 +70,7 @@ export function runWorkerScheduleTests( // Wait a little so the schedule is stored but hasn't triggered yet await waitFor(driverTestConfig, 50); - // Get a new reference to simulate worker restart + // Get a new reference to simulate actor restart const newInstance = client.scheduled.getOrCreate(); // Verify the schedule still exists but hasn't run yet diff --git a/packages/core/src/driver-test-suite/tests/worker-state.ts b/packages/core/src/driver-test-suite/tests/actor-state.ts similarity index 79% rename from packages/core/src/driver-test-suite/tests/worker-state.ts rename to packages/core/src/driver-test-suite/tests/actor-state.ts index abc6c59fc..a6c25390e 100644 --- a/packages/core/src/driver-test-suite/tests/worker-state.ts +++ b/packages/core/src/driver-test-suite/tests/actor-state.ts @@ -2,12 +2,12 @@ import { describe, test, expect } from "vitest"; import type { DriverTestConfig } from "../mod"; import { setupDriverTest } from "../utils"; -export function runWorkerStateTests( +export function runActorStateTests( driverTestConfig: DriverTestConfig ) { - describe("Worker State Tests", () => { + describe("Actor State Tests", () => { describe("State Persistence", () => { - test("persists state between worker instances", async (c) => { + test("persists state between actor instances", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -19,30 +19,30 @@ export function runWorkerStateTests( const initialCount = await counterInstance.increment(5); expect(initialCount).toBe(5); - // Get a fresh reference to the same worker and verify state persisted + // Get a fresh reference to the same actor and verify state persisted const sameInstance = client.counter.getOrCreate(); const persistedCount = await sameInstance.increment(3); expect(persistedCount).toBe(8); }); - test("restores state after worker disconnect/reconnect", async (c) => { + test("restores state after actor disconnect/reconnect", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, ); - // Create worker and set initial state + // Create actor and set initial state const counterInstance = client.counter.getOrCreate(); await counterInstance.increment(5); - // Reconnect to the same worker + // Reconnect to the same actor const reconnectedInstance = client.counter.getOrCreate(); const persistedCount = await reconnectedInstance.increment(0); expect(persistedCount).toBe(5); }); - test("maintains separate state for different workers", async (c) => { + test("maintains separate state for different actors", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, diff --git a/packages/core/src/driver-test-suite/tests/worker-vars.ts b/packages/core/src/driver-test-suite/tests/actor-vars.ts similarity index 77% rename from packages/core/src/driver-test-suite/tests/worker-vars.ts rename to packages/core/src/driver-test-suite/tests/actor-vars.ts index 95910e757..d51f2fbbf 100644 --- a/packages/core/src/driver-test-suite/tests/worker-vars.ts +++ b/packages/core/src/driver-test-suite/tests/actor-vars.ts @@ -2,8 +2,8 @@ import { describe, test, expect } from "vitest"; import type { DriverTestConfig } from "../mod"; import { setupDriverTest } from "../utils"; -export function runWorkerVarsTests(driverTestConfig: DriverTestConfig) { - describe("Worker Variables", () => { +export function runActorVarsTests(driverTestConfig: DriverTestConfig) { + describe("Actor Variables", () => { describe("Static vars", () => { test("should provide access to static vars", async (c) => { const { client } = await setupDriverTest( @@ -12,20 +12,20 @@ export function runWorkerVarsTests(driverTestConfig: DriverTestConfig) { ); - const instance = client.staticVarWorker.getOrCreate(); + const instance = client.staticVarActor.getOrCreate(); // Test accessing vars const result = await instance.getVars(); - expect(result).toEqual({ counter: 42, name: "test-worker" }); + expect(result).toEqual({ counter: 42, name: "test-actor" }); // Test accessing specific var property const name = await instance.getName(); - expect(name).toBe("test-worker"); + expect(name).toBe("test-actor"); }); }); describe("Deep cloning of static vars", () => { - test("should deep clone static vars between worker instances", async (c) => { + test("should deep clone static vars between actor instances", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -33,8 +33,8 @@ export function runWorkerVarsTests(driverTestConfig: DriverTestConfig) { ); // Create two separate instances - const instance1 = client.nestedVarWorker.getOrCreate(["instance1"]); - const instance2 = client.nestedVarWorker.getOrCreate(["instance2"]); + const instance1 = client.nestedVarActor.getOrCreate(["instance1"]); + const instance2 = client.nestedVarActor.getOrCreate(["instance2"]); // Modify vars in the first instance const modifiedVars = await instance1.modifyNested(); @@ -59,7 +59,7 @@ export function runWorkerVarsTests(driverTestConfig: DriverTestConfig) { ); // Create an instance - const instance = client.dynamicVarWorker.getOrCreate(); + const instance = client.dynamicVarActor.getOrCreate(); // Test accessing dynamically created vars const vars = await instance.getVars(); @@ -67,7 +67,7 @@ export function runWorkerVarsTests(driverTestConfig: DriverTestConfig) { expect(vars).toHaveProperty("computed"); expect(typeof vars.random).toBe("number"); expect(typeof vars.computed).toBe("string"); - expect(vars.computed).toMatch(/^Worker-\d+$/); + expect(vars.computed).toMatch(/^Actor-\d+$/); }); test("should create different vars for different instances", async (c) => { @@ -78,8 +78,8 @@ export function runWorkerVarsTests(driverTestConfig: DriverTestConfig) { ); // Create two separate instances - const instance1 = client.uniqueVarWorker.getOrCreate(["test1"]); - const instance2 = client.uniqueVarWorker.getOrCreate(["test2"]); + const instance1 = client.uniqueVarActor.getOrCreate(["test1"]); + const instance2 = client.uniqueVarActor.getOrCreate(["test2"]); // Get vars from both instances const vars1 = await instance1.getVars(); @@ -99,7 +99,7 @@ export function runWorkerVarsTests(driverTestConfig: DriverTestConfig) { ); // Create an instance - const instance = client.driverCtxWorker.getOrCreate(); + const instance = client.driverCtxActor.getOrCreate(); // Test accessing driver context through vars const vars = await instance.getVars(); diff --git a/packages/core/src/driver-test-suite/tests/manager-driver.ts b/packages/core/src/driver-test-suite/tests/manager-driver.ts index 3b690d171..e2ce7641f 100644 --- a/packages/core/src/driver-test-suite/tests/manager-driver.ts +++ b/packages/core/src/driver-test-suite/tests/manager-driver.ts @@ -1,28 +1,28 @@ import { describe, test, expect, vi } from "vitest"; import { setupDriverTest } from "../utils"; -import { WorkerError } from "@/client/mod"; +import { ActorError } from "@/client/mod"; import { DriverTestConfig } from "../mod"; export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { describe("Manager Driver Tests", () => { describe("Client Connection Methods", () => { - test("connect() - finds or creates a worker", async (c) => { + test("connect() - finds or creates a actor", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, ); - // Basic connect() with no parameters creates a default worker + // Basic connect() with no parameters creates a default actor const counterA = client.counter.getOrCreate(); await counterA.increment(5); - // Get the same worker again to verify state persisted + // Get the same actor again to verify state persisted const counterAAgain = client.counter.getOrCreate(); const count = await counterAAgain.increment(0); expect(count).toBe(5); - // Connect with key creates a new worker with specific parameters + // Connect with key creates a new actor with specific parameters const counterB = client.counter.getOrCreate(["counter-b", "testing"]); await counterB.increment(10); @@ -30,72 +30,72 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { expect(countB).toBe(10); }); - test("throws WorkerAlreadyExists when creating duplicate workers", async (c) => { + test("throws ActorAlreadyExists when creating duplicate actors", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, ); - // Create a unique worker with specific key - const uniqueKey = ["duplicate-worker-test", crypto.randomUUID()]; + // Create a unique actor with specific key + const uniqueKey = ["duplicate-actor-test", crypto.randomUUID()]; const counter = client.counter.getOrCreate(uniqueKey); await counter.increment(5); - // Expect duplicate worker + // Expect duplicate actor try { await client.counter.create(uniqueKey); expect.fail("did not error on duplicate create"); } catch (err) { - expect((err as WorkerError).code).toBe("worker_already_exists"); + expect((err as ActorError).code).toBe("actor_already_exists"); } - // Verify the original worker still works and has its state + // Verify the original actor still works and has its state const count = await counter.increment(0); expect(count).toBe(5); }); }); describe("Connection Options", () => { - test("get without create prevents worker creation", async (c) => { + test("get without create prevents actor creation", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, ); - // Try to get a nonexistent worker with no create + // Try to get a nonexistent actor with no create const nonexistentId = `nonexistent-${crypto.randomUUID()}`; - // Should fail when worker doesn't exist + // Should fail when actor doesn't exist try { await client.counter.get([nonexistentId]).resolve(); expect.fail("did not error for get"); } catch (err) { - expect((err as WorkerError).code).toBe("worker_not_found"); + expect((err as ActorError).code).toBe("actor_not_found"); } - // Create the worker + // Create the actor const createdCounter = client.counter.getOrCreate(nonexistentId); await createdCounter.increment(3); - // Now no create should work since the worker exists + // Now no create should work since the actor exists const retrievedCounter = client.counter.get(nonexistentId); const count = await retrievedCounter.increment(0); expect(count).toBe(3); }); - test("connection params are passed to workers", async (c) => { + test("connection params are passed to actors", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, ); - // Create a worker with connection params - // Note: In a real test we'd verify these are received by the worker, - // but our simple counter worker doesn't use connection params. + // Create a actor with connection params + // Note: In a real test we'd verify these are received by the actor, + // but our simple counter actor doesn't use connection params. // This test just ensures the params are accepted by the driver. const counter = client.counter.getOrCreate(undefined, { params: { @@ -111,8 +111,8 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { }); }); - describe("Worker Creation & Retrieval", () => { - test("creates and retrieves workers by ID", async (c) => { + describe("Actor Creation & Retrieval", () => { + test("creates and retrieves actors by ID", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -122,17 +122,17 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { // Create a unique ID for this test const uniqueId = `test-counter-${crypto.randomUUID()}`; - // Create worker with specific ID + // Create actor with specific ID const counter = client.counter.getOrCreate([uniqueId]); await counter.increment(10); - // Retrieve the same worker by ID and verify state + // Retrieve the same actor by ID and verify state const retrievedCounter = client.counter.getOrCreate([uniqueId]); const count = await retrievedCounter.increment(0); // Get current value expect(count).toBe(10); }); - test("passes input to worker during creation", async (c) => { + test("passes input to actor during creation", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -141,18 +141,18 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { // Test data to pass as input const testInput = { - name: "test-worker", + name: "test-actor", value: 42, nested: { foo: "bar" }, }; - // Create worker with input - const worker = await client.inputWorker.create(undefined, { + // Create actor with input + const actor = await client.inputActor.create(undefined, { input: testInput, }); // Verify both createState and onCreate received the input - const inputs = await worker.getInputs(); + const inputs = await actor.getInputs(); // Input should be available in createState expect(inputs.initialInput).toEqual(testInput); @@ -168,11 +168,11 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { ); - // Create worker without providing input - const worker = await client.inputWorker.create(); + // Create actor without providing input + const actor = await client.inputActor.create(); // Get inputs and verify they're undefined - const inputs = await worker.getInputs(); + const inputs = await actor.getInputs(); // Should be undefined in createState expect(inputs.initialInput).toBeUndefined(); @@ -181,7 +181,7 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { expect(inputs.onCreateInput).toBeUndefined(); }); - test("getOrCreate passes input to worker during creation", async (c) => { + test("getOrCreate passes input to actor during creation", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -199,12 +199,12 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { }; // Use getOrCreate with input - const worker = client.inputWorker.getOrCreate(uniqueKey, { + const actor = client.inputActor.getOrCreate(uniqueKey, { createWithInput: testInput, }); // Verify both createState and onCreate received the input - const inputs = await worker.getInputs(); + const inputs = await actor.getInputs(); // Input should be available in createState expect(inputs.initialInput).toEqual(testInput); @@ -213,9 +213,9 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { expect(inputs.onCreateInput).toEqual(testInput); // Verify that calling getOrCreate again with the same key - // returns the existing worker and doesn't create a new one - const existingWorker = client.inputWorker.getOrCreate(uniqueKey); - const existingInputs = await existingWorker.getInputs(); + // returns the existing actor and doesn't create a new one + const existingActor = client.inputActor.getOrCreate(uniqueKey); + const existingInputs = await existingActor.getInputs(); // Should still have the original inputs expect(existingInputs.initialInput).toEqual(testInput); @@ -223,13 +223,13 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { }); // TODO: Correctly test region for each provider - //test("creates and retrieves workers with region", async (c) => { + //test("creates and retrieves actors with region", async (c) => { // const { client } = await setupDriverTest(c, // driverTestConfig, // COUNTER_APP_PATH // ); // - // // Create worker with a specific region + // // Create actor with a specific region // const counter = client.counter.getOrCreate({ // create: { // key: ["metadata-test", "testing"], @@ -250,14 +250,14 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { }); describe("Key Matching", () => { - test("matches workers only with exactly the same keys", async (c) => { + test("matches actors only with exactly the same keys", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, ); - // Create worker with multiple keys + // Create actor with multiple keys const originalCounter = client.counter.getOrCreate([ "counter-match", "test", @@ -274,7 +274,7 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { const exactMatchCount = await exactMatchCounter.increment(0); expect(exactMatchCount).toBe(10); - // Should NOT match with subset of keys - should create new worker + // Should NOT match with subset of keys - should create new actor const subsetMatchCounter = client.counter.getOrCreate([ "counter-match", "test", @@ -282,7 +282,7 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { const subsetMatchCount = await subsetMatchCounter.increment(0); expect(subsetMatchCount).toBe(0); // Should be a new counter with 0 - // Should NOT match with just one key - should create new worker + // Should NOT match with just one key - should create new actor const singleKeyCounter = client.counter.getOrCreate(["counter-match"]); const singleKeyCount = await singleKeyCounter.increment(0); expect(singleKeyCount).toBe(0); // Should be a new counter with 0 @@ -295,7 +295,7 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { ); - // Create worker with string key + // Create actor with string key const stringKeyCounter = client.counter.getOrCreate("string-key-test"); await stringKeyCounter.increment(7); @@ -312,7 +312,7 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { ); - // Create worker with undefined key + // Create actor with undefined key const undefinedKeyCounter = client.counter.getOrCreate(undefined); await undefinedKeyCounter.increment(12); @@ -327,7 +327,7 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { expect(noKeyCount).toBe(12); }); - test("no keys does not match workers with keys", async (c) => { + test("no keys does not match actors with keys", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -347,7 +347,7 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { expect(count).toBe(10); }); - test("workers with keys match workers with no keys", async (c) => { + test("actors with keys match actors with no keys", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -370,9 +370,9 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { }); }); - describe("Multiple Worker Instances", () => { + describe("Multiple Actor Instances", () => { // TODO: This test is flakey https://github.com/rivet-gg/rivetkit/issues/873 - test("creates multiple worker instances of the same type", async (c) => { + test("creates multiple actor instances of the same type", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, diff --git a/packages/core/src/driver-test-suite/tests/worker-driver.ts b/packages/core/src/driver-test-suite/tests/worker-driver.ts deleted file mode 100644 index f2da28d93..000000000 --- a/packages/core/src/driver-test-suite/tests/worker-driver.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { describe } from "vitest"; -import type { DriverTestConfig } from "../mod"; -import { runWorkerStateTests } from "./worker-state"; -import { runWorkerScheduleTests } from "./worker-schedule"; - -export function runWorkerDriverTests( - driverTestConfig: DriverTestConfig -) { - describe("Worker Driver Tests", () => { - // Run state persistence tests - runWorkerStateTests(driverTestConfig); - - // Run scheduled alarms tests - runWorkerScheduleTests(driverTestConfig); - }); -} \ No newline at end of file diff --git a/packages/core/src/driver-test-suite/utils.ts b/packages/core/src/driver-test-suite/utils.ts index 14bf45c45..33d72f3de 100644 --- a/packages/core/src/driver-test-suite/utils.ts +++ b/packages/core/src/driver-test-suite/utils.ts @@ -1,7 +1,7 @@ import { type TestContext, vi } from "vitest"; import { createClient, type Client } from "@/client/mod"; import type { DriverTestConfig } from "./mod"; -import { assertUnreachable } from "@/worker/utils"; +import { assertUnreachable } from "@/actor/utils"; import { createClientWithDriver } from "@/client/client"; import { createTestInlineClientDriver } from "./test-inline-client-driver"; import { resolve } from "node:path"; diff --git a/packages/core/src/drivers/memory/actor.ts b/packages/core/src/drivers/memory/actor.ts new file mode 100644 index 000000000..5f4032f00 --- /dev/null +++ b/packages/core/src/drivers/memory/actor.ts @@ -0,0 +1,39 @@ +import type { ActorDriver, AnyActorInstance } from "@/driver-helpers/mod"; +import type { MemoryGlobalState } from "./global-state"; + +export type ActorDriverContext = Record; + +export class MemoryActorDriver implements ActorDriver { + #state: MemoryGlobalState; + + constructor(state: MemoryGlobalState) { + this.#state = state; + } + + getContext(_actorId: string): ActorDriverContext { + return {}; + } + + async readInput(actorId: string): Promise { + return this.#state.readInput(actorId); + } + + async readPersistedData(actorId: string): Promise { + return this.#state.readPersistedData(actorId); + } + + async writePersistedData(actorId: string, data: unknown): Promise { + this.#state.writePersistedData(actorId, data); + } + + async setAlarm(actor: AnyActorInstance, timestamp: number): Promise { + const delay = Math.max(timestamp - Date.now(), 0); + setTimeout(() => { + actor.onAlarm(); + }, delay); + } + + getDatabase(actorId: string): Promise { + return Promise.resolve(undefined); + } +} diff --git a/packages/core/src/drivers/memory/global-state.ts b/packages/core/src/drivers/memory/global-state.ts index 76cbb9739..ddfb449c4 100644 --- a/packages/core/src/drivers/memory/global-state.ts +++ b/packages/core/src/drivers/memory/global-state.ts @@ -1,72 +1,72 @@ -import type { WorkerKey } from "@/common/utils"; +import type { ActorKey } from "@/common/utils"; -export interface WorkerState { +export interface ActorState { id: string; name: string; - key: WorkerKey; + key: ActorKey; persistedData: unknown; input?: unknown; } export class MemoryGlobalState { - #workers: Map = new Map(); + #actors: Map = new Map(); - #getWorker(workerId: string): WorkerState { - const worker = this.#workers.get(workerId); - if (!worker) { - throw new Error(`Worker does not exist for ID: ${workerId}`); + #getActor(actorId: string): ActorState { + const actor = this.#actors.get(actorId); + if (!actor) { + throw new Error(`Actor does not exist for ID: ${actorId}`); } - return worker; + return actor; } - readInput(workerId: string): unknown | undefined { - return this.#getWorker(workerId).input; + readInput(actorId: string): unknown | undefined { + return this.#getActor(actorId).input; } - readPersistedData(workerId: string): unknown | undefined { - return this.#getWorker(workerId).persistedData; + readPersistedData(actorId: string): unknown | undefined { + return this.#getActor(actorId).persistedData; } - writePersistedData(workerId: string, data: unknown) { - this.#getWorker(workerId).persistedData = data; + writePersistedData(actorId: string, data: unknown) { + this.#getActor(actorId).persistedData = data; } - createWorker( - workerId: string, + createActor( + actorId: string, name: string, - key: WorkerKey, + key: ActorKey, input?: unknown, ): void { - // Create worker state if it doesn't exist - if (!this.#workers.has(workerId)) { - this.#workers.set(workerId, { - id: workerId, + // Create actor state if it doesn't exist + if (!this.#actors.has(actorId)) { + this.#actors.set(actorId, { + id: actorId, name, key, persistedData: undefined, input, }); } else { - throw new Error(`Worker already exists for ID: ${workerId}`); + throw new Error(`Actor already exists for ID: ${actorId}`); } } - findWorker( - filter: (worker: WorkerState) => boolean, - ): WorkerState | undefined { - for (const worker of this.#workers.values()) { - if (filter(worker)) { - return worker; + findActor( + filter: (actor: ActorState) => boolean, + ): ActorState | undefined { + for (const actor of this.#actors.values()) { + if (filter(actor)) { + return actor; } } return undefined; } - getWorker(workerId: string): WorkerState | undefined { - return this.#workers.get(workerId); + getActor(actorId: string): ActorState | undefined { + return this.#actors.get(actorId); } - getAllWorkers(): WorkerState[] { - return Array.from(this.#workers.values()); + getAllActors(): ActorState[] { + return Array.from(this.#actors.values()); } } diff --git a/packages/core/src/drivers/memory/manager.ts b/packages/core/src/drivers/memory/manager.ts index 63bcb5d91..23d10ae91 100644 --- a/packages/core/src/drivers/memory/manager.ts +++ b/packages/core/src/drivers/memory/manager.ts @@ -3,10 +3,10 @@ import type { GetForIdInput, GetWithKeyInput, GetOrCreateWithKeyInput, - WorkerOutput, + ActorOutput, ManagerDriver, } from "@/driver-helpers/mod"; -import { WorkerAlreadyExists } from "@/worker/errors"; +import { ActorAlreadyExists } from "@/actor/errors"; import type { MemoryGlobalState } from "./global-state"; import * as crypto from "node:crypto"; @@ -14,8 +14,8 @@ export class MemoryManagerDriver implements ManagerDriver { #state: MemoryGlobalState; // inspector: ManagerInspector = new ManagerInspector(this, { - // getAllWorkers: () => this.#state.getAllWorkers(), - // getAllTypesOfWorkers: () => Object.keys(this.registry.config.workers), + // getAllActors: () => this.#state.getAllActors(), + // getAllTypesOfActors: () => Object.keys(this.registry.config.actors), // }); constructor(state: MemoryGlobalState) { @@ -23,51 +23,51 @@ export class MemoryManagerDriver implements ManagerDriver { } async getForId({ - workerId, - }: GetForIdInput): Promise { - // Validate the worker exists - const worker = this.#state.getWorker(workerId); - if (!worker) { + actorId, + }: GetForIdInput): Promise { + // Validate the actor exists + const actor = this.#state.getActor(actorId); + if (!actor) { return undefined; } return { - workerId: worker.id, - name: worker.name, - key: worker.key, + actorId: actor.id, + name: actor.name, + key: actor.key, }; } async getWithKey({ name, key, - }: GetWithKeyInput): Promise { - // NOTE: This is a slow implementation that checks each worker individually. + }: GetWithKeyInput): Promise { + // NOTE: This is a slow implementation that checks each actor individually. // This can be optimized with an index in the future. - // Search through all workers to find a match - const worker = this.#state.findWorker((worker) => { - if (worker.name !== name) return false; + // Search through all actors to find a match + const actor = this.#state.findActor((actor) => { + if (actor.name !== name) return false; - // If worker doesn't have a key, it's not a match - if (!worker.key || worker.key.length !== key.length) { + // If actor doesn't have a key, it's not a match + if (!actor.key || actor.key.length !== key.length) { return false; } - // Check if all elements in key are in worker.key + // Check if all elements in key are in actor.key for (let i = 0; i < key.length; i++) { - if (key[i] !== worker.key[i]) { + if (key[i] !== actor.key[i]) { return false; } } return true; }); - if (worker) { + if (actor) { return { - workerId: worker.id, + actorId: actor.id, name, - key: worker.key, + key: actor.key, }; } @@ -76,27 +76,27 @@ export class MemoryManagerDriver implements ManagerDriver { async getOrCreateWithKey( input: GetOrCreateWithKeyInput, - ): Promise { + ): Promise { const getOutput = await this.getWithKey(input); if (getOutput) { return getOutput; } else { - return await this.createWorker(input); + return await this.createActor(input); } } - async createWorker({ name, key, input }: CreateInput): Promise { - // Check if worker with the same name and key already exists - const existingWorker = await this.getWithKey({ name, key }); - if (existingWorker) { - throw new WorkerAlreadyExists(name, key); + async createActor({ name, key, input }: CreateInput): Promise { + // Check if actor with the same name and key already exists + const existingActor = await this.getWithKey({ name, key }); + if (existingActor) { + throw new ActorAlreadyExists(name, key); } - const workerId = crypto.randomUUID(); - this.#state.createWorker(workerId, name, key, input); + const actorId = crypto.randomUUID(); + this.#state.createActor(actorId, name, key, input); - // this.inspector.onWorkersChange(this.#state.getAllWorkers()); + // this.inspector.onActorsChange(this.#state.getAllActors()); - return { workerId, name, key }; + return { actorId, name, key }; } } diff --git a/packages/core/src/drivers/memory/mod.ts b/packages/core/src/drivers/memory/mod.ts index 0d8b70f1c..07a87f28b 100644 --- a/packages/core/src/drivers/memory/mod.ts +++ b/packages/core/src/drivers/memory/mod.ts @@ -1,13 +1,13 @@ import type { DriverConfig } from "@/registry/run-config"; import { MemoryManagerDriver } from "./manager"; import { MemoryGlobalState } from "./global-state"; -import { MemoryWorkerDriver } from "./worker"; +import { MemoryActorDriver } from "./actor"; export function createMemoryDriver(): DriverConfig { const state = new MemoryGlobalState(); return { topology: "standalone", manager: new MemoryManagerDriver(state), - worker: new MemoryWorkerDriver(state), + actor: new MemoryActorDriver(state), }; } diff --git a/packages/core/src/drivers/memory/worker.ts b/packages/core/src/drivers/memory/worker.ts deleted file mode 100644 index c4e404edc..000000000 --- a/packages/core/src/drivers/memory/worker.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { WorkerDriver, AnyWorkerInstance } from "@/driver-helpers/mod"; -import type { MemoryGlobalState } from "./global-state"; - -export type WorkerDriverContext = Record; - -export class MemoryWorkerDriver implements WorkerDriver { - #state: MemoryGlobalState; - - constructor(state: MemoryGlobalState) { - this.#state = state; - } - - getContext(_workerId: string): WorkerDriverContext { - return {}; - } - - async readInput(workerId: string): Promise { - return this.#state.readInput(workerId); - } - - async readPersistedData(workerId: string): Promise { - return this.#state.readPersistedData(workerId); - } - - async writePersistedData(workerId: string, data: unknown): Promise { - this.#state.writePersistedData(workerId, data); - } - - async setAlarm(worker: AnyWorkerInstance, timestamp: number): Promise { - const delay = Math.max(timestamp - Date.now(), 0); - setTimeout(() => { - worker.onAlarm(); - }, delay); - } - - getDatabase(workerId: string): Promise { - return Promise.resolve(undefined); - } -} diff --git a/packages/core/src/drivers/rivet/worker-driver.ts b/packages/core/src/drivers/rivet/actor-driver.ts similarity index 63% rename from packages/core/src/drivers/rivet/worker-driver.ts rename to packages/core/src/drivers/rivet/actor-driver.ts index f7d8a6a2d..5bdb4c7f0 100644 --- a/packages/core/src/drivers/rivet/worker-driver.ts +++ b/packages/core/src/drivers/rivet/actor-driver.ts @@ -1,22 +1,22 @@ import { ActorContext } from "@rivet-gg/actor-core"; -import type { WorkerDriver, AnyWorkerInstance } from "@/driver-helpers/mod"; +import type { ActorDriver, AnyActorInstance } from "@/driver-helpers/mod"; -export interface WorkerDriverContext { +export interface ActorDriverContext { ctx: ActorContext; } -export class RivetWorkerDriver implements WorkerDriver { +export class RivetActorDriver implements ActorDriver { #ctx: ActorContext; constructor(ctx: ActorContext) { this.#ctx = ctx; } - getContext(_workerId: string): WorkerDriverContext { + getContext(_actorId: string): ActorDriverContext { return { ctx: this.#ctx }; } - async readInput(_workerId: string): Promise { + async readInput(_actorId: string): Promise { // Read input // // We need to have a separate exists flag in order to represent `undefined` @@ -32,28 +32,28 @@ export class RivetWorkerDriver implements WorkerDriver { } } - async readPersistedData(_workerId: string): Promise { + async readPersistedData(_actorId: string): Promise { let data = await this.#ctx.kv.get(["rivetkit", "data"]); - // HACK: Modify to be undefined if null. This will be fixed in Workers v2. + // HACK: Modify to be undefined if null. This will be fixed in Actors v2. if (data === null) data = undefined; return data; } - async writePersistedData(_workerId: string, data: unknown): Promise { + async writePersistedData(_actorId: string, data: unknown): Promise { // Use "state" as the key for persisted data await this.#ctx.kv.put(["rivetkit", "data"], data); } - async setAlarm(worker: AnyWorkerInstance, timestamp: number): Promise { + async setAlarm(actor: AnyActorInstance, timestamp: number): Promise { const timeout = Math.max(0, timestamp - Date.now()); setTimeout(() => { - worker.onAlarm(); + actor.onAlarm(); }, timeout); } - getDatabase(_workerId: string): Promise { + getDatabase(_actorId: string): Promise { // TODO: Implement database access return Promise.resolve(undefined); } diff --git a/packages/core/src/drivers/rivet/worker-meta.ts b/packages/core/src/drivers/rivet/actor-meta.ts similarity index 59% rename from packages/core/src/drivers/rivet/worker-meta.ts rename to packages/core/src/drivers/rivet/actor-meta.ts index f55ee17f5..2c225b679 100644 --- a/packages/core/src/drivers/rivet/worker-meta.ts +++ b/packages/core/src/drivers/rivet/actor-meta.ts @@ -3,63 +3,63 @@ import { RivetActor, RivetClientConfig, rivetRequest } from "./rivet-client"; import { deserializeKeyFromTag, convertKeyToRivetTags } from "./util"; import invariant from "invariant"; -interface WorkerMeta { +interface ActorMeta { name: string; key: string[]; endpoint: string; } -interface WorkerMetaWithId extends WorkerMeta { - workerId: string; +interface ActorMetaWithId extends ActorMeta { + actorId: string; } // TODO: Implement LRU cache -// Cache for worker ID -> worker meta -const WORKER_META_CACHE = new Map>(); +// Cache for actor ID -> actor meta +const ACTOR_META_CACHE = new Map>(); // TODO: Implement LRU cache -// Cache for worker name+key -> worker ID -const WORKER_KEY_CACHE = new Map>(); +// Cache for actor name+key -> actor ID +const ACTOR_KEY_CACHE = new Map>(); /** - * Creates a cache key for worker name and key combination. + * Creates a cache key for actor name and key combination. */ function createKeysCacheKey(name: string, key: string[]): string { return `${name}:${JSON.stringify(key)}`; } /** - * Returns worker metadata with an in-memory cache. + * Returns actor metadata with an in-memory cache. */ -export async function getWorkerMeta( +export async function getActorMeta( clientConfig: RivetClientConfig, - workerId: string, -): Promise { - // TODO: This does not refresh cache when workers are destroyed. This - // will be replaced with hot pulls from the Rivet API once (a) worker + actorId: string, +): Promise { + // TODO: This does not refresh cache when actors are destroyed. This + // will be replaced with hot pulls from the Rivet API once (a) actor // IDs include the datacenter in order to build endpoints without // hitting the API and (b) we update the API to hit the regional // endpoints. - const workerMetaPromise = WORKER_META_CACHE.get(workerId); - if (workerMetaPromise) { - return await workerMetaPromise; + const actorMetaPromise = ACTOR_META_CACHE.get(actorId); + if (actorMetaPromise) { + return await actorMetaPromise; } else { // Fetch meta const promise = (async () => { const { actor } = await rivetRequest( clientConfig, "GET", - `/actors/${encodeURIComponent(workerId)}`, + `/actors/${encodeURIComponent(actorId)}`, ); return convertActorToMeta(actor); })(); - WORKER_META_CACHE.set(workerId, promise); + ACTOR_META_CACHE.set(actorId, promise); // Remove from cache on failure so it can be retried promise.catch(() => { - WORKER_META_CACHE.delete(workerId); + ACTOR_META_CACHE.delete(actorId); }); return await promise; @@ -67,30 +67,30 @@ export async function getWorkerMeta( } /** - * Returns worker metadata for a worker with the given name and key. + * Returns actor metadata for a actor with the given name and key. */ -export async function getWorkerMetaWithKey( +export async function getActorMetaWithKey( clientConfig: RivetClientConfig, name: string, key: string[], -): Promise { +): Promise { const cacheKey = createKeysCacheKey(name, key); - // Check if we have the worker ID cached - const cachedWorkerIdPromise = WORKER_KEY_CACHE.get(cacheKey); - if (cachedWorkerIdPromise) { - const workerId = await cachedWorkerIdPromise; - if (workerId) { - // Try to get the worker metadata from the ID cache - const meta = await getWorkerMeta(clientConfig, workerId); + // Check if we have the actor ID cached + const cachedActorIdPromise = ACTOR_KEY_CACHE.get(cacheKey); + if (cachedActorIdPromise) { + const actorId = await cachedActorIdPromise; + if (actorId) { + // Try to get the actor metadata from the ID cache + const meta = await getActorMeta(clientConfig, actorId); if (meta) { return { ...meta, - workerId, + actorId, }; } // If metadata is not available, remove from key cache and continue with fresh lookup - WORKER_KEY_CACHE.delete(cacheKey); + ACTOR_KEY_CACHE.delete(cacheKey); } } @@ -106,7 +106,7 @@ export async function getWorkerMetaWithKey( `/actors?tags_json=${encodeURIComponent(JSON.stringify(rivetTags))}`, ); - // Filter workers to ensure they're valid + // Filter actors to ensure they're valid const validActors = actors.filter((a: RivetActor) => { // Verify all ports have hostname and port for (const portName in a.network.ports) { @@ -119,7 +119,7 @@ export async function getWorkerMetaWithKey( if (validActors.length === 0) { // Remove from cache if not found since we might create an actor // with this key - WORKER_KEY_CACHE.delete(cacheKey); + ACTOR_KEY_CACHE.delete(cacheKey); return undefined; } @@ -137,71 +137,71 @@ export async function getWorkerMetaWithKey( return actor.id; })(); - WORKER_KEY_CACHE.set(cacheKey, promise); + ACTOR_KEY_CACHE.set(cacheKey, promise); // Remove from cache on failure so it can be retried promise.catch(() => { - WORKER_KEY_CACHE.delete(cacheKey); + ACTOR_KEY_CACHE.delete(cacheKey); }); - const workerId = await promise; - if (!workerId) { + const actorId = await promise; + if (!actorId) { return undefined; } - const meta = await getWorkerMeta(clientConfig, workerId); - invariant(meta, "worker metadata should be available after populating cache"); + const meta = await getActorMeta(clientConfig, actorId); + invariant(meta, "actor metadata should be available after populating cache"); return { ...meta, - workerId, + actorId, }; } /** * Preemptively adds an entry to the cache. */ -export function populateCache(actor: RivetActor): WorkerMeta | undefined { +export function populateCache(actor: RivetActor): ActorMeta | undefined { const meta = convertActorToMeta(actor); if (meta) { - // Populate the worker ID -> metadata cache - WORKER_META_CACHE.set(actor.id, Promise.resolve(meta)); + // Populate the actor ID -> metadata cache + ACTOR_META_CACHE.set(actor.id, Promise.resolve(meta)); - // Populate the name+key -> worker ID cache + // Populate the name+key -> actor ID cache const cacheKey = createKeysCacheKey(meta.name, meta.key); - WORKER_KEY_CACHE.set(cacheKey, Promise.resolve(actor.id)); + ACTOR_KEY_CACHE.set(cacheKey, Promise.resolve(actor.id)); } return meta; } /** - * Converts actor data from the Rivet API to worker metadata. + * Converts actor data from the Rivet API to actor metadata. */ -function convertActorToMeta(actor: RivetActor): WorkerMeta | undefined { - // Check if worker exists and not destroyed +function convertActorToMeta(actor: RivetActor): ActorMeta | undefined { + // Check if actor exists and not destroyed if (actor.destroyedAt) { return undefined; } - // Ensure worker has required tags + // Ensure actor has required tags if (!("name" in actor.tags)) { - throw new Error(`Worker ${actor.id} missing 'name' in tags.`); + throw new Error(`Actor ${actor.id} missing 'name' in tags.`); } - if (actor.tags.role !== "worker") { - throw new Error(`Worker ${actor.id} does not have a worker role.`); + if (actor.tags.role !== "actor") { + throw new Error(`Actor ${actor.id} does not have a actor role.`); } if (actor.tags.framework !== "rivetkit") { - throw new Error(`Worker ${actor.id} is not an RivetKit worker.`); + throw new Error(`Actor ${actor.id} is not an RivetKit actor.`); } return { name: actor.tags.name, key: deserializeKeyFromTag(actor.tags.key), - endpoint: buildWorkerEndpoint(actor), + endpoint: buildActorEndpoint(actor), }; } -function buildWorkerEndpoint(actor: RivetActor): string { +function buildActorEndpoint(actor: RivetActor): string { // Fetch port const httpPort = actor.network.ports.http; if (!httpPort) throw new Error("missing http port"); @@ -235,6 +235,6 @@ function buildWorkerEndpoint(actor: RivetActor): string { } export function flushCache() { - WORKER_META_CACHE.clear(); - WORKER_KEY_CACHE.clear(); + ACTOR_META_CACHE.clear(); + ACTOR_KEY_CACHE.clear(); } diff --git a/packages/core/src/drivers/rivet/worker.ts b/packages/core/src/drivers/rivet/actor.ts similarity index 86% rename from packages/core/src/drivers/rivet/worker.ts rename to packages/core/src/drivers/rivet/actor.ts index 2e2c9a256..2deb5055c 100644 --- a/packages/core/src/drivers/rivet/worker.ts +++ b/packages/core/src/drivers/rivet/actor.ts @@ -1,8 +1,8 @@ import { setupLogging } from "@/common/log"; import { logger } from "./log"; import { deserializeKeyFromTag, type RivetHandler } from "./util"; -import { PartitionTopologyWorker } from "@/topologies/partition/mod"; -import { RivetWorkerDriver } from "./worker-driver"; +import { PartitionTopologyActor } from "@/topologies/partition/mod"; +import { RivetActorDriver } from "./actor-driver"; import invariant from "invariant"; import type { ActorContext } from "@rivet-gg/actor-core"; import { Registry, RunConfig } from "@/registry/mod"; @@ -11,7 +11,7 @@ import { stringifyError } from "@/common/utils"; import { RivetManagerDriver } from "./manager-driver"; import { getRivetClientConfig, RivetClientConfig } from "./rivet-client"; -export function createWorkerHandler( +export function createActorHandler( registry: Registry, inputConfig?: InputConfig ): RivetHandler { @@ -26,16 +26,16 @@ export function createWorkerHandler( return { async start(ctx: ActorContext) { const role = ctx.metadata.actor.tags.role; - if (role === "worker") { - await startWorker(ctx, registry, config); + if (role === "actor") { + await startActor(ctx, registry, config); } else { - throw new Error(`Unexpected role (must be worker): ${role}`); + throw new Error(`Unexpected role (must be actor): ${role}`); } }, }; } -async function startWorker( +async function startActor( ctx: ActorContext, registry: Registry, config: Config, @@ -59,7 +59,7 @@ async function startWorker( driver: { topology: "partition", manager: new RivetManagerDriver(clientConfig), - worker: new RivetWorkerDriver(ctx), + actor: new RivetActorDriver(ctx), }, getUpgradeWebSocket: () => upgradeWebSocket, ...config, @@ -121,14 +121,14 @@ async function startWorker( // }, //}; - // Create worker topology - const workerTopology = new PartitionTopologyWorker( + // Create actor topology + const actorTopology = new PartitionTopologyActor( registry.config, runConfig, ); // Set a catch-all route - const router = workerTopology.router; + const router = actorTopology.router; // TODO: This needs to be secured // TODO: This needs to assert this has only been called once @@ -171,16 +171,16 @@ async function startWorker( // Assert name exists if (!("name" in ctx.metadata.actor.tags)) { throw new Error( - `Tags for worker ${ctx.metadata.actor.id} do not contain property name: ${JSON.stringify(ctx.metadata.actor.tags)}`, + `Tags for actor ${ctx.metadata.actor.id} do not contain property name: ${JSON.stringify(ctx.metadata.actor.tags)}`, ); } // Extract key from Rivet's tag format const key = extractKeyFromRivetTags(ctx.metadata.actor.tags); - // Start worker after initialized + // Start actor after initialized await initializedPromise.promise; - await workerTopology.start( + await actorTopology.start( ctx.metadata.actor.id, ctx.metadata.actor.tags.name, key, diff --git a/packages/core/src/drivers/rivet/conn-routing-handler.ts b/packages/core/src/drivers/rivet/conn-routing-handler.ts index 17986334a..ed561d01f 100644 --- a/packages/core/src/drivers/rivet/conn-routing-handler.ts +++ b/packages/core/src/drivers/rivet/conn-routing-handler.ts @@ -1,8 +1,8 @@ import { logger } from "./log"; import { type RivetClientConfig } from "./rivet-client"; -import { getWorkerMeta } from "./worker-meta"; +import { getActorMeta } from "./actor-meta"; import invariant from "invariant"; -import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; import { HEADER_AUTH_DATA, HEADER_CONN_PARAMS, @@ -18,26 +18,26 @@ export function createRivetConnRoutingHandler( ): ConnRoutingHandler { return { custom: { - sendRequest: async (workerId, workerRequest) => { - const meta = await getWorkerMeta(clientConfig, workerId); - invariant(meta, "worker should exist"); + sendRequest: async (actorId, actorRequest) => { + const meta = await getActorMeta(clientConfig, actorId); + invariant(meta, "actor should exist"); - const parsedRequestUrl = new URL(workerRequest.url); - const workerUrl = `${meta.endpoint}${parsedRequestUrl.pathname}${parsedRequestUrl.search}`; + const parsedRequestUrl = new URL(actorRequest.url); + const actorUrl = `${meta.endpoint}${parsedRequestUrl.pathname}${parsedRequestUrl.search}`; - logger().debug("proxying request to rivet worker", { - method: workerRequest.method, - url: workerUrl, + logger().debug("proxying request to rivet actor", { + method: actorRequest.method, + url: actorUrl, }); - const proxyRequest = new Request(workerUrl, workerRequest); + const proxyRequest = new Request(actorUrl, actorRequest); return await fetch(proxyRequest); }, - openWebSocket: async (workerId, encodingKind, params: unknown) => { + openWebSocket: async (actorId, encodingKind, params: unknown) => { const WebSocket = await importWebSocket(); - const meta = await getWorkerMeta(clientConfig, workerId); - invariant(meta, "worker should exist"); + const meta = await getActorMeta(clientConfig, actorId); + invariant(meta, "actor should exist"); const wsEndpoint = meta.endpoint.replace(/^http/, "ws"); const url = `${wsEndpoint}/connect/websocket`; @@ -52,44 +52,44 @@ export function createRivetConnRoutingHandler( headers[HEADER_CONN_PARAMS] = JSON.stringify(params); } - logger().debug("opening websocket to worker", { - workerId, + logger().debug("opening websocket to actor", { + actorId, url, }); return new WebSocket(url, { headers }); }, - proxyRequest: async (c, workerRequest, workerId) => { - const meta = await getWorkerMeta(clientConfig, workerId); - invariant(meta, "worker should exist"); + proxyRequest: async (c, actorRequest, actorId) => { + const meta = await getActorMeta(clientConfig, actorId); + invariant(meta, "actor should exist"); - const parsedRequestUrl = new URL(workerRequest.url); - const workerUrl = `${meta.endpoint}${parsedRequestUrl.pathname}${parsedRequestUrl.search}`; + const parsedRequestUrl = new URL(actorRequest.url); + const actorUrl = `${meta.endpoint}${parsedRequestUrl.pathname}${parsedRequestUrl.search}`; - logger().debug("proxying request to rivet worker", { - method: workerRequest.method, - url: workerUrl, + logger().debug("proxying request to rivet actor", { + method: actorRequest.method, + url: actorUrl, }); - const proxyRequest = new Request(workerUrl, workerRequest); + const proxyRequest = new Request(actorUrl, actorRequest); return await proxy(proxyRequest); }, proxyWebSocket: async ( c, path, - workerId, + actorId, encoding, connParmas, authData, upgradeWebSocket, ) => { - const meta = await getWorkerMeta(clientConfig, workerId); - invariant(meta, "worker should exist"); + const meta = await getActorMeta(clientConfig, actorId); + invariant(meta, "actor should exist"); - const workerUrl = `${meta.endpoint}${path}`; + const actorUrl = `${meta.endpoint}${path}`; - logger().debug("proxying websocket to rivet worker", { - url: workerUrl, + logger().debug("proxying websocket to rivet actor", { + url: actorUrl, }); // Build headers @@ -104,7 +104,7 @@ export function createRivetConnRoutingHandler( headers[HEADER_AUTH_DATA] = JSON.stringify(authData); } - const handlers = await createWebSocketProxy(workerUrl, headers); + const handlers = await createWebSocketProxy(actorUrl, headers); // upgradeWebSocket is middleware, so we need to pass fake handlers invariant(upgradeWebSocket, "missing upgradeWebSocket"); diff --git a/packages/core/src/drivers/rivet/manager-driver.ts b/packages/core/src/drivers/rivet/manager-driver.ts index ec3f1b70f..7ca3efc9d 100644 --- a/packages/core/src/drivers/rivet/manager-driver.ts +++ b/packages/core/src/drivers/rivet/manager-driver.ts @@ -1,10 +1,10 @@ import { assertUnreachable } from "@/common/utils"; -import { WorkerAlreadyExists, InternalError } from "@/worker/errors"; +import { ActorAlreadyExists, InternalError } from "@/actor/errors"; import type { ManagerDriver, GetForIdInput, GetWithKeyInput, - WorkerOutput, + ActorOutput, GetOrCreateWithKeyInput, CreateInput, } from "@/driver-helpers/mod"; @@ -17,23 +17,23 @@ import { import { convertKeyToRivetTags } from "./util"; import { flushCache, - getWorkerMeta, - getWorkerMetaWithKey, + getActorMeta, + getActorMetaWithKey, populateCache, -} from "./worker-meta"; +} from "./actor-meta"; import invariant from "invariant"; import { getEnvUniversal } from "@/utils"; -import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; import { createRivetConnRoutingHandler } from "./conn-routing-handler"; import { Hono } from "hono"; import { Registry, RegistryConfig } from "@/registry/mod"; -export interface WorkerState { +export interface ActorState { key: string[]; destroyedAt?: number; } -export interface GetWorkerMeta { +export interface GetActorMeta { endpoint: string; } @@ -49,14 +49,14 @@ export class RivetManagerDriver implements ManagerDriver { } async getForId({ - workerId, - }: GetForIdInput): Promise { + actorId, + }: GetForIdInput): Promise { try { - const meta = await getWorkerMeta(this.#clientConfig, workerId); + const meta = await getActorMeta(this.#clientConfig, actorId); if (!meta) return undefined; return { - workerId, + actorId, name: meta.name, key: meta.key, }; @@ -69,12 +69,12 @@ export class RivetManagerDriver implements ManagerDriver { async getWithKey({ name, key, - }: GetWithKeyInput): Promise { - const meta = await getWorkerMetaWithKey(this.#clientConfig, name, key); + }: GetWithKeyInput): Promise { + const meta = await getActorMetaWithKey(this.#clientConfig, name, key); if (!meta) return undefined; return { - workerId: meta.workerId, + actorId: meta.actorId, name: meta.name, key: meta.key, }; @@ -82,35 +82,35 @@ export class RivetManagerDriver implements ManagerDriver { async getOrCreateWithKey( input: GetOrCreateWithKeyInput, - ): Promise { + ): Promise { const getOutput = await this.getWithKey(input); if (getOutput) { return getOutput; } else { - return await this.createWorker(input); + return await this.createActor(input); } } - async createWorker({ + async createActor({ name, key, region, input, - }: CreateInput): Promise { - // Check if worker with the same name and key already exists - const existingWorker = await this.getWithKey({ name, key }); - if (existingWorker) { - throw new WorkerAlreadyExists(name, key); + }: CreateInput): Promise { + // Check if actor with the same name and key already exists + const existingActor = await this.getWithKey({ name, key }); + if (existingActor) { + throw new ActorAlreadyExists(name, key); } - // Create the worker request - let workerLogLevel: string | undefined = - getEnvUniversal("_WORKER_LOG_LEVEL"); + // Create the actor request + let actorLogLevel: string | undefined = + getEnvUniversal("_ACTOR_LOG_LEVEL"); const createRequest = { tags: convertKeyToRivetTags(name, key), build_tags: { - role: "worker", + role: "actor", framework: "rivetkit", current: "true", }, @@ -130,7 +130,7 @@ export class RivetManagerDriver implements ManagerDriver { RIVET_SERVICE_TOKEN: this.#clientConfig.token, RIVET_PROJECT: this.#clientConfig.project, RIVET_ENVIRONMENT: this.#clientConfig.environment, - ...(workerLogLevel ? { _LOG_LEVEL: workerLogLevel } : {}), + ...(actorLogLevel ? { _LOG_LEVEL: actorLogLevel } : {}), }, }, lifecycle: { @@ -140,7 +140,7 @@ export class RivetManagerDriver implements ManagerDriver { logger().info("creating actor", { ...createRequest }); - // Create the worker + // Create the actor const { actor } = await rivetRequest< typeof createRequest, { actor: RivetActor } @@ -149,10 +149,10 @@ export class RivetManagerDriver implements ManagerDriver { const meta = populateCache(actor); invariant(meta, "actor just created, should not be destroyed"); - // Initialize the worker + // Initialize the actor try { const url = `${meta.endpoint}/initialize`; - logger().debug("initializing worker", { + logger().debug("initializing actor", { url, input: JSON.stringify(input), }); @@ -166,17 +166,17 @@ export class RivetManagerDriver implements ManagerDriver { }); if (!res.ok) { throw new InternalError( - `Worker initialize request failed (${res.status}):\n${await res.text()}`, + `Actor initialize request failed (${res.status}):\n${await res.text()}`, ); } } catch (error) { - logger().error("failed to initialize worker, destroying worker", { - workerId: actor.id, + logger().error("failed to initialize actor, destroying actor", { + actorId: actor.id, error, }); - // Destroy the worker since it failed to initialize - await rivetRequest( + // Destroy the actor since it failed to initialize + await rivetRequest( this.#clientConfig, "DELETE", `/actors/${actor.id}`, @@ -186,7 +186,7 @@ export class RivetManagerDriver implements ManagerDriver { } return { - workerId: actor.id, + actorId: actor.id, name: meta.name, key: meta.key, }; diff --git a/packages/core/src/drivers/rivet/manager.ts b/packages/core/src/drivers/rivet/manager.ts index a114f8f73..2bb1ef670 100644 --- a/packages/core/src/drivers/rivet/manager.ts +++ b/packages/core/src/drivers/rivet/manager.ts @@ -2,12 +2,12 @@ import { setupLogging } from "@/common/log"; import { serve as honoServe } from "@hono/node-server"; import { createNodeWebSocket, NodeWebSocket } from "@hono/node-ws"; import { logger } from "./log"; -import { GetWorkerMeta, RivetManagerDriver } from "./manager-driver"; +import { GetActorMeta, RivetManagerDriver } from "./manager-driver"; import type { RivetClientConfig } from "./rivet-client"; import { PartitionTopologyManager } from "@/topologies/partition/mod"; import { ConfigSchema, InputConfig } from "./config"; import type { Registry, RunConfig } from "@/registry/mod"; -import { flushCache, getWorkerMeta } from "./worker-meta"; +import { flushCache, getActorMeta } from "./actor-meta"; export async function startManager( registry: Registry, @@ -46,8 +46,8 @@ export async function startManager( driver: { topology: "partition", manager: new RivetManagerDriver(clientConfig), - // HACK: We can't build the worker driver until we're inside the worker - worker: undefined as any, + // HACK: We can't build the actor driver until we're inside the actor + actor: undefined as any, }, // Setup WebSocket routing for Node // diff --git a/packages/core/src/drivers/rivet/mod.ts b/packages/core/src/drivers/rivet/mod.ts index 4218e500c..1e97718ff 100644 --- a/packages/core/src/drivers/rivet/mod.ts +++ b/packages/core/src/drivers/rivet/mod.ts @@ -8,8 +8,8 @@ export function createRivetManagerDriver(): DriverConfig { topology: "partition", manager: new RivetManagerDriver(clientConfig), // We don't have access to `ActorContext`, so we can't construct this - worker: undefined as any, + actor: undefined as any, }; } -export { createWorkerHandler } from "./worker"; +export { createActorHandler } from "./actor"; diff --git a/packages/core/src/drivers/rivet/util.ts b/packages/core/src/drivers/rivet/util.ts index 841db8a93..78fab796d 100644 --- a/packages/core/src/drivers/rivet/util.ts +++ b/packages/core/src/drivers/rivet/util.ts @@ -89,7 +89,7 @@ export function convertKeyToRivetTags( return { name, key: serializeKeyForTag(key), - role: "worker", + role: "actor", framework: "rivetkit", }; } diff --git a/packages/core/src/inline-client-driver/fake-websocket.ts b/packages/core/src/inline-client-driver/fake-websocket.ts index 61a6bcb96..b067e4cf4 100644 --- a/packages/core/src/inline-client-driver/fake-websocket.ts +++ b/packages/core/src/inline-client-driver/fake-websocket.ts @@ -1,9 +1,9 @@ import { WSContext } from "hono/ws"; import { logger } from "@/registry/log"; -import type { ConnectWebSocketOutput } from "@/worker/router-endpoints"; -import type * as messageToServer from "@/worker/protocol/message/to-server"; -import { parseMessage } from "@/worker/protocol/message/mod"; -import type { InputData } from "@/worker/protocol/serde"; +import type { ConnectWebSocketOutput } from "@/actor/router-endpoints"; +import type * as messageToServer from "@/actor/protocol/message/to-server"; +import { parseMessage } from "@/actor/protocol/message/mod"; +import type { InputData } from "@/actor/protocol/serde"; import type { Event, CloseEvent, diff --git a/packages/core/src/inline-client-driver/mod.ts b/packages/core/src/inline-client-driver/mod.ts index 2ff18e0ca..a518ca4bd 100644 --- a/packages/core/src/inline-client-driver/mod.ts +++ b/packages/core/src/inline-client-driver/mod.ts @@ -1,9 +1,9 @@ -import * as errors from "@/worker/errors"; -import * as protoHttpAction from "@/worker/protocol/http/action"; +import * as errors from "@/actor/errors"; +import * as protoHttpAction from "@/actor/protocol/http/action"; import { logger } from "./log"; import type { EventSource } from "eventsource"; -import type * as wsToServer from "@/worker/protocol/message/to-server"; -import { type Encoding, serialize } from "@/worker/protocol/serde"; +import type * as wsToServer from "@/actor/protocol/message/to-server"; +import { type Encoding, serialize } from "@/actor/protocol/serde"; import { ConnectWebSocketOutput, HEADER_CONN_PARAMS, @@ -12,24 +12,24 @@ import { HEADER_CONN_TOKEN, type ConnectionHandlers, HEADER_EXPOSE_INTERNAL_ERROR, -} from "@/worker/router-endpoints"; +} from "@/actor/router-endpoints"; import type { SSEStreamingApi } from "hono/streaming"; import { HonoRequest, type Context as HonoContext, type Next } from "hono"; import invariant from "invariant"; import { ClientDriver } from "@/client/client"; import { ManagerDriver } from "@/manager/driver"; -import { WorkerQuery } from "@/manager/protocol/query"; -import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { ActorQuery } from "@/manager/protocol/query"; +import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; import { sendHttpRequest, serializeWithEncoding } from "@/client/utils"; -import { ActionRequest, ActionResponse } from "@/worker/protocol/http/action"; -import { assertUnreachable } from "@/worker/utils"; +import { ActionRequest, ActionResponse } from "@/actor/protocol/http/action"; +import { assertUnreachable } from "@/actor/utils"; import { FakeWebSocket } from "./fake-websocket"; import { FakeEventSource } from "./fake-event-source"; import { importWebSocket } from "@/common/websocket"; import { importEventSource } from "@/common/eventsource"; import onChange from "on-change"; import { httpUserAgent } from "@/utils"; -import { WorkerError as ClientWorkerError } from "@/client/errors"; +import { ActorError as ClientActorError } from "@/client/errors"; import { deconstructError } from "@/common/utils"; import type { WebSocket } from "ws"; @@ -51,7 +51,7 @@ export function createInlineClientDriver( const driver: ClientDriver = { action: async = unknown[], Response = unknown>( c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encoding: Encoding, params: unknown, actionName: string, @@ -59,10 +59,10 @@ export function createInlineClientDriver( opts: { signal?: AbortSignal }, ): Promise => { try { - // Get the worker ID - const { workerId } = await queryWorker(c, workerQuery, managerDriver); - logger().debug("found worker for action", { workerId }); - invariant(workerId, "Missing worker ID"); + // Get the actor ID + const { actorId } = await queryActor(c, actorQuery, managerDriver); + logger().debug("found actor for action", { actorId }); + invariant(actorId, "Missing actor ID"); // Invoke the action logger().debug("handling action", { actionName, encoding }); @@ -72,7 +72,7 @@ export function createInlineClientDriver( params, actionName, actionArgs: args, - workerId, + actorId, // No auth data since this is from internal authData: undefined, }); @@ -85,7 +85,7 @@ export function createInlineClientDriver( // without mutating the main state return structuredClone(output) as Response; } catch (err) { - // HACK: If we return a value that references the worker state (i.e. an on-change value), + // HACK: If we return a value that references the actor state (i.e. an on-change value), // this will throw an error. We fall back to `DataCloneError`. if (err instanceof DOMException && err.name === "DataCloneError") { logger().trace( @@ -101,7 +101,7 @@ export function createInlineClientDriver( ActionRequest, ActionResponse >({ - url: `http://worker/action/${encodeURIComponent(actionName)}`, + url: `http://actor/action/${encodeURIComponent(actionName)}`, method: "POST", headers: { [HEADER_ENCODING]: encoding, @@ -114,7 +114,7 @@ export function createInlineClientDriver( encoding: encoding, customFetch: routingHandler.custom.sendRequest.bind( undefined, - workerId, + actorId, ), signal: opts?.signal, }); @@ -124,44 +124,44 @@ export function createInlineClientDriver( assertUnreachable(routingHandler); } } catch (err) { - // Standardize to ClientWorkerError instead of the native backend error + // Standardize to ClientActorError instead of the native backend error const { code, message, metadata } = deconstructError( err, logger(), {}, true, ); - const x = new ClientWorkerError(code, message, metadata); - throw new ClientWorkerError(code, message, metadata); + const x = new ClientActorError(code, message, metadata); + throw new ClientActorError(code, message, metadata); } }, - resolveWorkerId: async ( + resolveActorId: async ( c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, _encodingKind: Encoding, ): Promise => { - // Get the worker ID - const { workerId } = await queryWorker(c, workerQuery, managerDriver); - logger().debug("resolved worker", { workerId }); - invariant(workerId, "missing worker ID"); + // Get the actor ID + const { actorId } = await queryActor(c, actorQuery, managerDriver); + logger().debug("resolved actor", { actorId }); + invariant(actorId, "missing actor ID"); - return workerId; + return actorId; }, connectWebSocket: async ( c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encodingKind: Encoding, params?: unknown, ): Promise => { - // Get the worker ID - const { workerId } = await queryWorker(c, workerQuery, managerDriver); - logger().debug("found worker for action", { workerId }); - invariant(workerId, "Missing worker ID"); + // Get the actor ID + const { actorId } = await queryActor(c, actorQuery, managerDriver); + logger().debug("found actor for action", { actorId }); + invariant(actorId, "Missing actor ID"); // Invoke the action - logger().debug("opening websocket", { workerId, encoding: encodingKind }); + logger().debug("opening websocket", { actorId, encoding: encodingKind }); if ("inline" in routingHandler) { invariant( routingHandler.inline.handlers.onConnectWebSocket, @@ -169,7 +169,7 @@ export function createInlineClientDriver( ); logger().debug("calling onConnectWebSocket handler", { - workerId, + actorId, encoding: encodingKind, }); @@ -177,7 +177,7 @@ export function createInlineClientDriver( const output = await routingHandler.inline.handlers.onConnectWebSocket({ req: c?.req, encoding: encodingKind, - workerId, + actorId, params, // No auth data since this is from internal authData: undefined, @@ -194,7 +194,7 @@ export function createInlineClientDriver( } else if ("custom" in routingHandler) { // Open WebSocket const ws = await routingHandler.custom.openWebSocket( - workerId, + actorId, encodingKind, params, ); @@ -208,17 +208,17 @@ export function createInlineClientDriver( connectSse: async ( c: HonoContext | undefined, - workerQuery: WorkerQuery, + actorQuery: ActorQuery, encodingKind: Encoding, params: unknown, ): Promise => { - // Get the worker ID - const { workerId } = await queryWorker(c, workerQuery, managerDriver); - logger().debug("found worker for sse connection", { workerId }); - invariant(workerId, "Missing worker ID"); + // Get the actor ID + const { actorId } = await queryActor(c, actorQuery, managerDriver); + logger().debug("found actor for sse connection", { actorId }); + invariant(actorId, "Missing actor ID"); logger().debug("opening sse connection", { - workerId, + actorId, encoding: encodingKind, }); @@ -229,7 +229,7 @@ export function createInlineClientDriver( ); logger().debug("calling onConnectSse handler", { - workerId, + actorId, encoding: encodingKind, }); @@ -238,7 +238,7 @@ export function createInlineClientDriver( req: c?.req, encoding: encodingKind, params, - workerId, + actorId, // No auth data since this is from internal authData: undefined, }); @@ -261,7 +261,7 @@ export function createInlineClientDriver( } else if ("custom" in routingHandler) { const EventSourceClass = await importEventSource(); - const eventSource = new EventSourceClass("http://worker/connect/sse", { + const eventSource = new EventSourceClass("http://actor/connect/sse", { fetch: (input, init) => { return fetch(input, { ...init, @@ -286,13 +286,13 @@ export function createInlineClientDriver( sendHttpMessage: async ( c: HonoContext | undefined, - workerId: string, + actorId: string, encoding: Encoding, connectionId: string, connectionToken: string, message: wsToServer.ToServer, ): Promise => { - logger().debug("sending http message", { workerId, connectionId }); + logger().debug("sending http message", { actorId, connectionId }); if ("inline" in routingHandler) { invariant( @@ -306,7 +306,7 @@ export function createInlineClientDriver( connId: connectionId, connToken: connectionToken, message, - workerId, + actorId, }); // Return empty response @@ -318,7 +318,7 @@ export function createInlineClientDriver( } else if ("custom" in routingHandler) { // Send an HTTP request to the connections endpoint return sendHttpRequest({ - url: "http://worker/connections/message", + url: "http://actor/connections/message", method: "POST", headers: { [HEADER_ENCODING]: encoding, @@ -331,7 +331,7 @@ export function createInlineClientDriver( skipParseResponse: true, customFetch: routingHandler.custom.sendRequest.bind( undefined, - workerId, + actorId, ), }); } else { @@ -344,34 +344,34 @@ export function createInlineClientDriver( } /** - * Query the manager driver to get or create a worker based on the provided query + * Query the manager driver to get or create a actor based on the provided query */ -export async function queryWorker( +export async function queryActor( c: HonoContext | undefined, - query: WorkerQuery, + query: ActorQuery, driver: ManagerDriver, -): Promise<{ workerId: string }> { - logger().debug("querying worker", { query }); - let workerOutput: { workerId: string }; +): Promise<{ actorId: string }> { + logger().debug("querying actor", { query }); + let actorOutput: { actorId: string }; if ("getForId" in query) { const output = await driver.getForId({ c, - workerId: query.getForId.workerId, + actorId: query.getForId.actorId, }); - if (!output) throw new errors.WorkerNotFound(query.getForId.workerId); - workerOutput = output; + if (!output) throw new errors.ActorNotFound(query.getForId.actorId); + actorOutput = output; } else if ("getForKey" in query) { - const existingWorker = await driver.getWithKey({ + const existingActor = await driver.getWithKey({ c, name: query.getForKey.name, key: query.getForKey.key, }); - if (!existingWorker) { - throw new errors.WorkerNotFound( + if (!existingActor) { + throw new errors.ActorNotFound( `${query.getForKey.name}:${JSON.stringify(query.getForKey.key)}`, ); } - workerOutput = existingWorker; + actorOutput = existingActor; } else if ("getOrCreateForKey" in query) { const getOrCreateOutput = await driver.getOrCreateWithKey({ c, @@ -380,28 +380,28 @@ export async function queryWorker( input: query.getOrCreateForKey.input, region: query.getOrCreateForKey.region, }); - workerOutput = { - workerId: getOrCreateOutput.workerId, + actorOutput = { + actorId: getOrCreateOutput.actorId, }; } else if ("create" in query) { - const createOutput = await driver.createWorker({ + const createOutput = await driver.createActor({ c, name: query.create.name, key: query.create.key, input: query.create.input, region: query.create.region, }); - workerOutput = { - workerId: createOutput.workerId, + actorOutput = { + actorId: createOutput.actorId, }; } else { throw new errors.InvalidRequest("Invalid query format"); } - logger().debug("worker query result", { - workerId: workerOutput.workerId, + logger().debug("actor query result", { + actorId: actorOutput.actorId, }); - return { workerId: workerOutput.workerId }; + return { actorId: actorOutput.actorId }; } /** diff --git a/packages/core/src/inspector/worker.ts b/packages/core/src/inspector/actor.ts similarity index 54% rename from packages/core/src/inspector/worker.ts rename to packages/core/src/inspector/actor.ts index 087002c90..159a2c8ce 100644 --- a/packages/core/src/inspector/worker.ts +++ b/packages/core/src/inspector/actor.ts @@ -1,14 +1,14 @@ -// import type { AnyWorkerInstance } from "@/worker/instance"; -// import type { AnyConn, ConnId } from "@/worker/connection"; -// import { throttle } from "@/worker/utils"; +// import type { AnyActorInstance } from "@/actor/instance"; +// import type { AnyConn, ConnId } from "@/actor/connection"; +// import { throttle } from "@/actor/utils"; // import type { UpgradeWebSocket } from "hono/ws"; -// import * as errors from "@/worker/errors"; +// import * as errors from "@/actor/errors"; // import { // type ToClient, // type ToServer, // ToServerSchema, -// } from "@/inspector/protocol/worker/mod"; -// import { logger } from "@/worker/log"; +// } from "@/inspector/protocol/actor/mod"; +// import { logger } from "@/actor/log"; // import { // createInspectorRoute, // Inspector, @@ -17,18 +17,18 @@ // } from "./common"; // import type { InspectorConfig } from "./config"; // -// export type WorkerInspectorConnHandler = InspectorConnHandler; +// export type ActorInspectorConnHandler = InspectorConnHandler; // // /** -// * Create a router for the worker inspector. +// * Create a router for the actor inspector. // * @internal // */ -// export function createWorkerInspectorRouter( +// export function createActorInspectorRouter( // upgradeWebSocket: UpgradeWebSocket | undefined, -// onConnect: WorkerInspectorConnHandler | undefined, +// onConnect: ActorInspectorConnHandler | undefined, // config: InspectorConfig, // ) { -// return createInspectorRoute({ +// return createInspectorRoute({ // upgradeWebSocket, // onConnect, // config, @@ -38,23 +38,23 @@ // } // // /** -// * Represents a connection to a worker. +// * Represents a connection to a actor. // * @internal // */ -// export type WorkerInspectorConnection = InspectorConnection; +// export type ActorInspectorConnection = InspectorConnection; // // /** -// * Provides a unified interface for inspecting worker external and internal state. +// * Provides a unified interface for inspecting actor external and internal state. // */ -// export class WorkerInspector extends Inspector { +// export class ActorInspector extends Inspector { // /** -// * Inspected worker instance. +// * Inspected actor instance. // * @internal // */ -// readonly worker: AnyWorkerInstance; +// readonly actor: AnyActorInstance; // // /** -// * Notify all inspector listeners of a worker's state change. +// * Notify all inspector listeners of a actor's state change. // * @param state - The new state. // * @internal // */ @@ -64,7 +64,7 @@ // // /** // * -// * Notify all inspector listeners of a worker's connections change. +// * Notify all inspector listeners of a actor's connections change. // * @param connections - The new connections. // * @internal // */ @@ -72,22 +72,22 @@ // this.broadcast(this.#createInfoMessage()); // }, 500); // -// constructor(worker: AnyWorkerInstance) { +// constructor(actor: AnyActorInstance) { // super(); -// this.worker = worker; +// this.actor = actor; // } // // /** // * Process a message from a connection. // * @internal // */ -// processMessage(connection: WorkerInspectorConnection, message: ToServer) { +// processMessage(connection: ActorInspectorConnection, message: ToServer) { // super.processMessage(connection, message); // if (message.type === "info") { // return connection.send(this.#createInfoMessage()); // } // if (message.type === "setState") { -// this.worker.state = message.state; +// this.actor.state = message.state; // return; // } // @@ -100,7 +100,7 @@ // #createInfoMessage(): ToClient { // return { // type: "info", -// connections: Array.from(this.worker.conns).map(([id, connection]) => ({ +// connections: Array.from(this.actor.conns).map(([id, connection]) => ({ // id, // parameters: connection.params, // state: { @@ -108,10 +108,10 @@ // enabled: connection._stateEnabled, // }, // })), -// actions: this.worker.actions, +// actions: this.actor.actions, // state: { -// value: this.worker.stateEnabled ? this.worker.state : undefined, -// enabled: this.worker.stateEnabled, +// value: this.actor.stateEnabled ? this.actor.state : undefined, +// enabled: this.actor.stateEnabled, // }, // }; // } diff --git a/packages/core/src/inspector/common.ts b/packages/core/src/inspector/common.ts index c3390f6d8..76925bb62 100644 --- a/packages/core/src/inspector/common.ts +++ b/packages/core/src/inspector/common.ts @@ -1,10 +1,10 @@ -// import type { ConnId } from "@/worker/connection"; +// import type { ConnId } from "@/actor/connection"; // import { deconstructError, safeStringify } from "@/common/utils"; // import { Hono, type HonoRequest } from "hono"; // import type { UpgradeWebSocket, WSContext } from "hono/ws"; // import type { InspectorConfig } from "./config"; // import type { Logger } from "@/common/log"; -// import * as errors from "@/worker/errors"; +// import * as errors from "@/actor/errors"; // import type { ZodSchema } from "zod"; // // interface ConnectInspectorOpts { @@ -22,7 +22,7 @@ // ) => Promise>; // // /** -// * Represents a connection to a worker. +// * Represents a connection to a actor. // * @internal // */ // export class InspectorConnection { @@ -47,7 +47,7 @@ // } // // /** -// * Provides a unified interface for inspecting worker and managers. +// * Provides a unified interface for inspecting actor and managers. // */ // export class Inspector { // /** diff --git a/packages/core/src/inspector/manager.ts b/packages/core/src/inspector/manager.ts index 6307a518c..92acd2af8 100644 --- a/packages/core/src/inspector/manager.ts +++ b/packages/core/src/inspector/manager.ts @@ -5,7 +5,7 @@ // ToServerSchema, // } from "@/inspector/protocol/manager/mod"; // import { logger } from "@/manager/log"; -// import * as errors from "@/worker/errors"; +// import * as errors from "@/actor/errors"; // import { // createInspectorRoute, // Inspector, @@ -14,11 +14,11 @@ // } from "./common"; // import type { InspectorConfig } from "./config"; // import type { ManagerDriver } from "@/manager/driver"; -// import { throttle } from "@/worker/utils"; +// import { throttle } from "@/actor/utils"; // // export type ManagerInspectorConnHandler = InspectorConnHandler; // -// interface Worker { +// interface Actor { // id: string; // name: string; // key: string[]; @@ -46,34 +46,34 @@ // } // // /** -// * Represents a connection to a worker. +// * Represents a connection to a actor. // * @internal // */ // export type ManagerInspectorConnection = InspectorConnection; // // /** -// * Provides a unified interface for inspecting worker external and internal state. +// * Provides a unified interface for inspecting actor external and internal state. // */ // export class ManagerInspector extends Inspector { // /** -// * Inspected worker instance. +// * Inspected actor instance. // * @internal // */ // readonly driver: ManagerDriver; // // /** -// * Notify all inspector listeners of a worker's state change. +// * Notify all inspector listeners of a actor's state change. // * @param state - The new state. // */ -// public onWorkersChange = throttle((workers: Worker[]) => { -// this.broadcast({ type: "workers", workers }); +// public onActorsChange = throttle((actors: Actor[]) => { +// this.broadcast({ type: "actors", actors }); // }, 500); // // constructor( // driver: ManagerDriver, // private readonly hooks: { -// getAllWorkers: () => Worker[]; -// getAllTypesOfWorkers: () => string[]; +// getAllActors: () => Actor[]; +// getAllTypesOfActors: () => string[]; // }, // ) { // super(); @@ -99,8 +99,8 @@ // if (message.type === "info") { // return connection.send({ // type: "info", -// workers: this.hooks.getAllWorkers(), -// types: this.hooks.getAllTypesOfWorkers(), +// actors: this.hooks.getAllActors(), +// types: this.hooks.getAllTypesOfActors(), // }); // } // diff --git a/packages/core/src/inspector/mod.ts b/packages/core/src/inspector/mod.ts index dd9909013..c2def1797 100644 --- a/packages/core/src/inspector/mod.ts +++ b/packages/core/src/inspector/mod.ts @@ -1,2 +1,2 @@ // export { ManagerInspector } from "./manager"; -// export { WorkerInspector } from "./worker"; +// export { ActorInspector } from "./actor"; diff --git a/packages/core/src/inspector/protocol/worker/mod.ts b/packages/core/src/inspector/protocol/actor/mod.ts similarity index 100% rename from packages/core/src/inspector/protocol/worker/mod.ts rename to packages/core/src/inspector/protocol/actor/mod.ts diff --git a/packages/core/src/inspector/protocol/worker/to-client.ts b/packages/core/src/inspector/protocol/actor/to-client.ts similarity index 100% rename from packages/core/src/inspector/protocol/worker/to-client.ts rename to packages/core/src/inspector/protocol/actor/to-client.ts diff --git a/packages/core/src/inspector/protocol/worker/to-server.ts b/packages/core/src/inspector/protocol/actor/to-server.ts similarity index 100% rename from packages/core/src/inspector/protocol/worker/to-server.ts rename to packages/core/src/inspector/protocol/actor/to-server.ts diff --git a/packages/core/src/inspector/protocol/manager/to-client.ts b/packages/core/src/inspector/protocol/manager/to-client.ts index 3fbb9d1e8..39125e6b1 100644 --- a/packages/core/src/inspector/protocol/manager/to-client.ts +++ b/packages/core/src/inspector/protocol/manager/to-client.ts @@ -1,22 +1,22 @@ // import { z } from "zod"; // -// const WorkerSchema = z.object({ +// const ActorSchema = z.object({ // id: z.string(), // name: z.string(), // key: z.array(z.string()), // }); // -// export type Worker = z.infer; +// export type Actor = z.infer; // // export const ToClientSchema = z.discriminatedUnion("type", [ // z.object({ // type: z.literal("info"), -// workers: z.array(WorkerSchema), +// actors: z.array(ActorSchema), // types: z.array(z.string()), // }), // z.object({ -// type: z.literal("workers"), -// workers: z.array(WorkerSchema), +// type: z.literal("actors"), +// actors: z.array(ActorSchema), // }), // z.object({ // type: z.literal("error"), diff --git a/packages/core/src/inspector/protocol/manager/to-server.ts b/packages/core/src/inspector/protocol/manager/to-server.ts index 783d3b662..0cab677bb 100644 --- a/packages/core/src/inspector/protocol/manager/to-server.ts +++ b/packages/core/src/inspector/protocol/manager/to-server.ts @@ -6,7 +6,7 @@ // }), // z.object({ // type: z.literal("destroy"), -// workerId: z.string(), +// actorId: z.string(), // }), // ]); // diff --git a/packages/core/src/manager/auth.ts b/packages/core/src/manager/auth.ts index 80d3fbed1..3b7d24c7d 100644 --- a/packages/core/src/manager/auth.ts +++ b/packages/core/src/manager/auth.ts @@ -1,17 +1,17 @@ -import * as errors from "@/worker/errors"; +import * as errors from "@/actor/errors"; import type { Context as HonoContext } from "hono"; -import type { WorkerQuery } from "./protocol/query"; -import type { AuthIntent } from "@/worker/config"; -import type { AnyWorkerDefinition } from "@/worker/definition"; +import type { ActorQuery } from "./protocol/query"; +import type { AuthIntent } from "@/actor/config"; +import type { AnyActorDefinition } from "@/actor/definition"; import type { RegistryConfig } from "@/registry/config"; import { ManagerDriver } from "./driver"; import { stringifyError } from "@/utils"; import { logger } from "./log"; /** - * Get authentication intents from a worker query + * Get authentication intents from a actor query */ -export function getIntentsFromQuery(query: WorkerQuery): Set { +export function getIntentsFromQuery(query: ActorQuery): Set { const intents = new Set(); if ("getForId" in query) { @@ -29,20 +29,20 @@ export function getIntentsFromQuery(query: WorkerQuery): Set { } /** - * Get worker name from a worker query + * Get actor name from a actor query */ -export async function getWorkerNameFromQuery( +export async function getActorNameFromQuery( c: HonoContext, driver: ManagerDriver, - query: WorkerQuery, + query: ActorQuery, ): Promise { if ("getForId" in query) { - // TODO: This will have a duplicate call to getForId between this and queryWorker + // TODO: This will have a duplicate call to getForId between this and queryActor const output = await driver.getForId({ c, - workerId: query.getForId.workerId, + actorId: query.getForId.actorId, }); - if (!output) throw new errors.WorkerNotFound(query.getForId.workerId); + if (!output) throw new errors.ActorNotFound(query.getForId.actorId); return output.name; } else if ("getForKey" in query) { return query.getForKey.name; @@ -56,22 +56,22 @@ export async function getWorkerNameFromQuery( } /** - * Authenticate a request using the worker's onAuth function + * Authenticate a request using the actor's onAuth function */ export async function authenticateRequest( c: HonoContext, - workerDefinition: AnyWorkerDefinition, + actorDefinition: AnyActorDefinition, intents: Set, params: unknown, ): Promise { - if (!workerDefinition.config.onAuth) { + if (!actorDefinition.config.onAuth) { throw new errors.Forbidden( - "Worker requires authentication but no onAuth handler is defined", + "Actor requires authentication but no onAuth handler is defined", ); } try { - const dataOrPromise = workerDefinition.config.onAuth({ + const dataOrPromise = actorDefinition.config.onAuth({ req: c.req.raw, intents, params, @@ -83,7 +83,7 @@ export async function authenticateRequest( } } catch (error) { logger().info("authentication error", { error: stringifyError(error) }); - if (errors.WorkerError.isWorkerError(error)) { + if (errors.ActorError.isActorError(error)) { throw error; } throw new errors.Forbidden("Authentication failed"); @@ -97,7 +97,7 @@ export async function authenticateEndpoint( c: HonoContext, driver: ManagerDriver, registryConfig: RegistryConfig, - query: WorkerQuery, + query: ActorQuery, additionalIntents: AuthIntent[], params: unknown, ): Promise { @@ -109,13 +109,13 @@ export async function authenticateEndpoint( intents.add(intent); } - // Get worker definition - const workerName = await getWorkerNameFromQuery(c, driver, query); - const workerDefinition = registryConfig.workers[workerName]; - if (!workerDefinition) { - throw new errors.WorkerNotFound(workerName); + // Get actor definition + const actorName = await getActorNameFromQuery(c, driver, query); + const actorDefinition = registryConfig.actors[actorName]; + if (!actorDefinition) { + throw new errors.ActorNotFound(actorName); } // Authenticate - return await authenticateRequest(c, workerDefinition, intents, params); + return await authenticateRequest(c, actorDefinition, intents, params); } diff --git a/packages/core/src/manager/driver.ts b/packages/core/src/manager/driver.ts index 5d887fec0..9a3fec698 100644 --- a/packages/core/src/manager/driver.ts +++ b/packages/core/src/manager/driver.ts @@ -1,14 +1,14 @@ import { ClientDriver } from "@/client/client"; -import type { WorkerKey } from "@/common/utils"; +import type { ActorKey } from "@/common/utils"; import { RegistryConfig } from "@/registry/config"; -import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; import type { Env, Hono, Context as HonoContext } from "hono"; export interface ManagerDriver { - getForId(input: GetForIdInput): Promise; - getWithKey(input: GetWithKeyInput): Promise; - getOrCreateWithKey(input: GetOrCreateWithKeyInput): Promise; - createWorker(input: CreateInput): Promise; + getForId(input: GetForIdInput): Promise; + getWithKey(input: GetWithKeyInput): Promise; + getOrCreateWithKey(input: GetOrCreateWithKeyInput): Promise; + createActor(input: CreateInput): Promise; readonly connRoutingHandler?: ConnRoutingHandler; @@ -18,19 +18,19 @@ export interface ManagerDriver { } export interface GetForIdInput { c?: HonoContext | undefined; - workerId: string; + actorId: string; } export interface GetWithKeyInput { c?: HonoContext | undefined; name: string; - key: WorkerKey; + key: ActorKey; } export interface GetOrCreateWithKeyInput { c?: HonoContext | undefined; name: string; - key: WorkerKey; + key: ActorKey; input?: unknown; region?: string; } @@ -38,13 +38,13 @@ export interface GetOrCreateWithKeyInput { export interface CreateInput { c?: HonoContext | undefined; name: string; - key: WorkerKey; + key: ActorKey; input?: unknown; region?: string; } -export interface WorkerOutput { - workerId: string; +export interface ActorOutput { + actorId: string; name: string; - key: WorkerKey; + key: ActorKey; } diff --git a/packages/core/src/manager/log.ts b/packages/core/src/manager/log.ts index 94947c9e8..41beaa5b8 100644 --- a/packages/core/src/manager/log.ts +++ b/packages/core/src/manager/log.ts @@ -1,6 +1,6 @@ import { getLogger } from "@/common//log"; -export const LOGGER_NAME = "worker-manager"; +export const LOGGER_NAME = "actor-manager"; export function logger() { return getLogger(LOGGER_NAME); diff --git a/packages/core/src/manager/protocol/mod.ts b/packages/core/src/manager/protocol/mod.ts index e6dc7c9d8..8ef6b6fa9 100644 --- a/packages/core/src/manager/protocol/mod.ts +++ b/packages/core/src/manager/protocol/mod.ts @@ -1,14 +1,14 @@ import { z } from "zod"; -import { WorkerQuerySchema } from "./query"; -import { TransportSchema } from "@/worker/protocol/message/mod"; +import { ActorQuerySchema } from "./query"; +import { TransportSchema } from "@/actor/protocol/message/mod"; export * from "./query"; -export const WorkersRequestSchema = z.object({ - query: WorkerQuerySchema, +export const ActorsRequestSchema = z.object({ + query: ActorQuerySchema, }); -export const WorkersResponseSchema = z.object({ - workerId: z.string(), +export const ActorsResponseSchema = z.object({ + actorId: z.string(), supportedTransports: z.array(TransportSchema), }); @@ -18,7 +18,7 @@ export const WorkersResponseSchema = z.object({ // environment: z.string().optional(), //}); -export type WorkersRequest = z.infer; -export type WorkersResponse = z.infer; +export type ActorsRequest = z.infer; +export type ActorsResponse = z.infer; //export type RivetConfigResponse = z.infer; diff --git a/packages/core/src/manager/protocol/query.ts b/packages/core/src/manager/protocol/query.ts index d15df3226..a89632a9f 100644 --- a/packages/core/src/manager/protocol/query.ts +++ b/packages/core/src/manager/protocol/query.ts @@ -1,38 +1,38 @@ -import { WorkerKeySchema } from "@/common//utils"; +import { ActorKeySchema } from "@/common//utils"; import { z } from "zod"; -import { EncodingSchema } from "@/worker/protocol/serde"; +import { EncodingSchema } from "@/actor/protocol/serde"; import { - HEADER_WORKER_ID, + HEADER_ACTOR_ID, HEADER_CONN_ID, HEADER_CONN_PARAMS, HEADER_CONN_TOKEN, HEADER_ENCODING, - HEADER_WORKER_QUERY, -} from "@/worker/router-endpoints"; + HEADER_ACTOR_QUERY, +} from "@/actor/router-endpoints"; export const CreateRequestSchema = z.object({ name: z.string(), - key: WorkerKeySchema, + key: ActorKeySchema, input: z.unknown().optional(), region: z.string().optional(), }); export const GetForKeyRequestSchema = z.object({ name: z.string(), - key: WorkerKeySchema, + key: ActorKeySchema, }); export const GetOrCreateRequestSchema = z.object({ name: z.string(), - key: WorkerKeySchema, + key: ActorKeySchema, input: z.unknown().optional(), region: z.string().optional(), }); -export const WorkerQuerySchema = z.union([ +export const ActorQuerySchema = z.union([ z.object({ getForId: z.object({ - workerId: z.string(), + actorId: z.string(), }), }), z.object({ @@ -47,34 +47,34 @@ export const WorkerQuerySchema = z.union([ ]); export const ConnectRequestSchema = z.object({ - query: WorkerQuerySchema.describe(HEADER_WORKER_QUERY), + query: ActorQuerySchema.describe(HEADER_ACTOR_QUERY), encoding: EncodingSchema.describe(HEADER_ENCODING), connParams: z.string().optional().describe(HEADER_CONN_PARAMS), }); export const ConnectWebSocketRequestSchema = z.object({ - query: WorkerQuerySchema.describe("query"), + query: ActorQuerySchema.describe("query"), encoding: EncodingSchema.describe("encoding"), connParams: z.unknown().optional().describe("conn_params"), }); export const ConnMessageRequestSchema = z.object({ - workerId: z.string().describe(HEADER_WORKER_ID), + actorId: z.string().describe(HEADER_ACTOR_ID), connId: z.string().describe(HEADER_CONN_ID), encoding: EncodingSchema.describe(HEADER_ENCODING), connToken: z.string().describe(HEADER_CONN_TOKEN), }); export const ResolveRequestSchema = z.object({ - query: WorkerQuerySchema.describe(HEADER_WORKER_QUERY), + query: ActorQuerySchema.describe(HEADER_ACTOR_QUERY), connParams: z.string().optional().describe(HEADER_CONN_PARAMS), }); -export type WorkerQuery = z.infer; +export type ActorQuery = z.infer; export type GetForKeyRequest = z.infer; export type GetOrCreateRequest = z.infer; export type ConnectQuery = z.infer; /** - * Interface representing a request to create a worker. + * Interface representing a request to create a actor. */ export type CreateRequest = z.infer; diff --git a/packages/core/src/manager/router.ts b/packages/core/src/manager/router.ts index 5a55b8427..e8bb9d429 100644 --- a/packages/core/src/manager/router.ts +++ b/packages/core/src/manager/router.ts @@ -1,12 +1,12 @@ -import * as errors from "@/worker/errors"; +import * as errors from "@/actor/errors"; import * as cbor from "cbor-x"; -import type * as protoHttpResolve from "@/worker/protocol/http/resolve"; -import type { ToClient } from "@/worker/protocol/message/to-client"; +import type * as protoHttpResolve from "@/actor/protocol/http/resolve"; +import type { ToClient } from "@/actor/protocol/message/to-client"; import { type Encoding, EncodingSchema, serialize, -} from "@/worker/protocol/serde"; +} from "@/actor/protocol/serde"; import { type ConnectionHandlers, getRequestEncoding, @@ -14,17 +14,17 @@ import { handleAction, handleSseConnect, handleWebSocketConnect, - HEADER_WORKER_ID, + HEADER_ACTOR_ID, HEADER_CONN_ID, HEADER_CONN_PARAMS, HEADER_CONN_TOKEN, HEADER_ENCODING, - HEADER_WORKER_QUERY, + HEADER_ACTOR_QUERY, ALL_PUBLIC_HEADERS, getRequestQuery, HEADER_AUTH_DATA, -} from "@/worker/router-endpoints"; -import { assertUnreachable } from "@/worker/utils"; +} from "@/actor/router-endpoints"; +import { assertUnreachable } from "@/actor/utils"; import type { RegistryConfig } from "@/registry/config"; import { handleRouteError, @@ -52,11 +52,11 @@ import { ConnMessageRequestSchema, ResolveRequestSchema, } from "./protocol/query"; -import type { WorkerQuery } from "./protocol/query"; +import type { ActorQuery } from "./protocol/query"; import { VERSION } from "@/utils"; -import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; import { ClientDriver } from "@/client/client"; -import { Transport } from "@/worker/protocol/message/mod"; +import { Transport } from "@/actor/protocol/message/mod"; import { authenticateEndpoint } from "./auth"; import type { WebSocket, MessageEvent, CloseEvent } from "ws"; import { RunConfig } from "@/registry/run-config"; @@ -71,17 +71,17 @@ const OPENAPI_ENCODING = z.string().openapi({ example: "json", }); -const OPENAPI_WORKER_QUERY = z.string().openapi({ - description: "Worker query information", +const OPENAPI_ACTOR_QUERY = z.string().openapi({ + description: "Actor query information", }); const OPENAPI_CONN_PARAMS = z.string().openapi({ description: "Connection parameters", }); -const OPENAPI_WORKER_ID = z.string().openapi({ - description: "Worker ID (used in some endpoints)", - example: "worker-123456", +const OPENAPI_ACTOR_ID = z.string().openapi({ + description: "Actor ID (used in some endpoints)", + example: "actor-123456", }); const OPENAPI_CONN_ID = z.string().openapi({ @@ -135,7 +135,7 @@ export function createManagerRouter( // HACK: This could be insecure if we had a varargs path. We have to check the path suffix for WS since we don't know the path that this router was mounted. const path = c.req.path; if ( - path.endsWith("/workers/connect/websocket") || + path.endsWith("/actors/connect/websocket") || path.endsWith("/inspect") ) { return next(); @@ -160,12 +160,12 @@ export function createManagerRouter( ); }); - // POST /workers/resolve + // POST /actors/resolve { const ResolveQuerySchema = z .object({ query: z.any().openapi({ - example: { getForId: { workerId: "worker-123" } }, + example: { getForId: { actorId: "actor-123" } }, }), }) .openapi("ResolveQuery"); @@ -173,14 +173,14 @@ export function createManagerRouter( const ResolveResponseSchema = z .object({ i: z.string().openapi({ - example: "worker-123", + example: "actor-123", }), }) .openapi("ResolveResponse"); const resolveRoute = createRoute({ method: "post", - path: "/workers/resolve", + path: "/actors/resolve", request: { body: { content: { @@ -190,7 +190,7 @@ export function createManagerRouter( }, }, headers: z.object({ - [HEADER_WORKER_QUERY]: OPENAPI_WORKER_QUERY, + [HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY, }), }, responses: buildOpenApiResponses(ResolveResponseSchema), @@ -201,11 +201,11 @@ export function createManagerRouter( ); } - // GET /workers/connect/websocket + // GET /actors/connect/websocket { // HACK: WebSockets don't work with mounts, so we need to dynamically match the trailing path router.use("*", (c, next) => { - if (c.req.path.endsWith("/workers/connect/websocket")) { + if (c.req.path.endsWith("/actors/connect/websocket")) { return handleWebSocketConnectRequest( c, upgradeWebSocket, @@ -222,7 +222,7 @@ export function createManagerRouter( // This route is a noop, just used to generate docs const wsRoute = createRoute({ method: "get", - path: "/workers/connect/websocket", + path: "/actors/connect/websocket", responses: { 101: { description: "WebSocket upgrade", @@ -235,15 +235,15 @@ export function createManagerRouter( }); } - // GET /workers/connect/sse + // GET /actors/connect/sse { const sseRoute = createRoute({ method: "get", - path: "/workers/connect/sse", + path: "/actors/connect/sse", request: { headers: z.object({ [HEADER_ENCODING]: OPENAPI_ENCODING, - [HEADER_WORKER_QUERY]: OPENAPI_WORKER_QUERY, + [HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY, [HEADER_CONN_PARAMS]: OPENAPI_CONN_PARAMS.optional(), }), }, @@ -264,7 +264,7 @@ export function createManagerRouter( ); } - // POST /workers/action/:action + // POST /actors/action/:action { const ActionParamsSchema = z .object({ @@ -281,7 +281,7 @@ export function createManagerRouter( const ActionRequestSchema = z .object({ query: z.any().openapi({ - example: { getForId: { workerId: "worker-123" } }, + example: { getForId: { actorId: "actor-123" } }, }), body: z .any() @@ -296,7 +296,7 @@ export function createManagerRouter( const actionRoute = createRoute({ method: "post", - path: "/workers/actions/{action}", + path: "/actors/actions/{action}", request: { params: ActionParamsSchema, body: { @@ -319,12 +319,12 @@ export function createManagerRouter( ); } - // POST /workers/message + // POST /actors/message { const ConnectionMessageRequestSchema = z .object({ message: z.any().openapi({ - example: { type: "message", content: "Hello, worker!" }, + example: { type: "message", content: "Hello, actor!" }, }), }) .openapi("ConnectionMessageRequest"); @@ -335,7 +335,7 @@ export function createManagerRouter( const messageRoute = createRoute({ method: "post", - path: "/workers/message", + path: "/actors/message", request: { body: { content: { @@ -345,7 +345,7 @@ export function createManagerRouter( }, }, headers: z.object({ - [HEADER_WORKER_ID]: OPENAPI_WORKER_ID, + [HEADER_ACTOR_ID]: OPENAPI_ACTOR_ID, [HEADER_CONN_ID]: OPENAPI_CONN_ID, [HEADER_ENCODING]: OPENAPI_ENCODING, [HEADER_CONN_TOKEN]: OPENAPI_CONN_TOKEN, @@ -407,28 +407,28 @@ export function createManagerRouter( ".test/inline-driver/connect-websocket", upgradeWebSocket(async (c: any) => { const { - workerQuery: workerQueryRaw, + actorQuery: actorQueryRaw, params: paramsRaw, encodingKind, } = c.req.query() as { - workerQuery: string; + actorQuery: string; params?: string; encodingKind: Encoding; }; - const workerQuery = JSON.parse(workerQueryRaw); + const actorQuery = JSON.parse(actorQueryRaw); const params = paramsRaw !== undefined ? JSON.parse(paramsRaw) : undefined; logger().debug("received test inline driver websocket", { - workerQuery, + actorQuery, params, encodingKind, }); - // Connect to the worker using the inline client driver - this returns a Promise + // Connect to the actor using the inline client driver - this returns a Promise const clientWsPromise = inlineClientDriver.connectWebSocket( undefined, - workerQuery, + actorQuery, encodingKind, params, undefined, @@ -597,34 +597,34 @@ export type TestInlineDriverCallResponse = }; /** - * Query the manager driver to get or create a worker based on the provided query + * Query the manager driver to get or create a actor based on the provided query */ -export async function queryWorker( +export async function queryActor( c: HonoContext, - query: WorkerQuery, + query: ActorQuery, driver: ManagerDriver, -): Promise<{ workerId: string }> { - logger().debug("querying worker", { query }); - let workerOutput: { workerId: string }; +): Promise<{ actorId: string }> { + logger().debug("querying actor", { query }); + let actorOutput: { actorId: string }; if ("getForId" in query) { const output = await driver.getForId({ c, - workerId: query.getForId.workerId, + actorId: query.getForId.actorId, }); - if (!output) throw new errors.WorkerNotFound(query.getForId.workerId); - workerOutput = output; + if (!output) throw new errors.ActorNotFound(query.getForId.actorId); + actorOutput = output; } else if ("getForKey" in query) { - const existingWorker = await driver.getWithKey({ + const existingActor = await driver.getWithKey({ c, name: query.getForKey.name, key: query.getForKey.key, }); - if (!existingWorker) { - throw new errors.WorkerNotFound( + if (!existingActor) { + throw new errors.ActorNotFound( `${query.getForKey.name}:${JSON.stringify(query.getForKey.key)}`, ); } - workerOutput = existingWorker; + actorOutput = existingActor; } else if ("getOrCreateForKey" in query) { const getOrCreateOutput = await driver.getOrCreateWithKey({ c, @@ -633,28 +633,28 @@ export async function queryWorker( input: query.getOrCreateForKey.input, region: query.getOrCreateForKey.region, }); - workerOutput = { - workerId: getOrCreateOutput.workerId, + actorOutput = { + actorId: getOrCreateOutput.actorId, }; } else if ("create" in query) { - const createOutput = await driver.createWorker({ + const createOutput = await driver.createActor({ c, name: query.create.name, key: query.create.key, input: query.create.input, region: query.create.region, }); - workerOutput = { - workerId: createOutput.workerId, + actorOutput = { + actorId: createOutput.actorId, }; } else { throw new errors.InvalidRequest("Invalid query format"); } - logger().debug("worker query result", { - workerId: workerOutput.workerId, + logger().debug("actor query result", { + actorId: actorOutput.actorId, }); - return { workerId: workerOutput.workerId }; + return { actorId: actorOutput.actorId }; } /** @@ -702,10 +702,10 @@ async function handleSseConnectRequest( connParams, ); - // Get the worker ID - const { workerId } = await queryWorker(c, query, driver); - invariant(workerId, "Missing worker ID"); - logger().debug("sse connection to worker", { workerId }); + // Get the actor ID + const { actorId } = await queryActor(c, query, driver); + invariant(actorId, "Missing actor ID"); + logger().debug("sse connection to actor", { actorId }); // Handle based on mode if ("inline" in handler.routingHandler) { @@ -716,12 +716,12 @@ async function handleSseConnectRequest( registryConfig, runConfig, handler.routingHandler.inline.handlers.onConnectSse, - workerId, + actorId, authData, ); } else if ("custom" in handler.routingHandler) { logger().debug("using custom proxy mode for sse connection"); - const url = new URL("http://worker/connect/sse"); + const url = new URL("http://actor/connect/sse"); // Always build fresh request to prevent forwarding unwanted headers const proxyRequest = new Request(url); @@ -735,7 +735,7 @@ async function handleSseConnectRequest( return await handler.routingHandler.custom.proxyRequest( c, proxyRequest, - workerId, + actorId, ); } else { assertUnreachable(handler.routingHandler); @@ -890,12 +890,12 @@ async function handleWebSocketConnectRequest( connParamsRaw, ); - // Get the worker ID - const { workerId } = await queryWorker(c, params.data.query, driver); - logger().debug("found worker for websocket connection", { - workerId, + // Get the actor ID + const { actorId } = await queryActor(c, params.data.query, driver); + logger().debug("found actor for websocket connection", { + actorId, }); - invariant(workerId, "missing worker id"); + invariant(actorId, "missing actor id"); if ("inline" in handler.routingHandler) { logger().debug("using inline proxy mode for websocket connection"); @@ -912,7 +912,7 @@ async function handleWebSocketConnectRequest( registryConfig, runConfig, onConnectWebSocket, - workerId, + actorId, params.data.encoding, params.data.connParams, authData, @@ -921,16 +921,16 @@ async function handleWebSocketConnectRequest( } else if ("custom" in handler.routingHandler) { logger().debug("using custom proxy mode for websocket connection"); - // Proxy the WebSocket connection to the worker + // Proxy the WebSocket connection to the actor // // The proxyWebSocket handler will: // 1. Validate the WebSocket upgrade request - // 2. Forward the request to the worker with the appropriate path - // 3. Handle the WebSocket pair and proxy messages between client and worker + // 2. Forward the request to the actor with the appropriate path + // 3. Handle the WebSocket pair and proxy messages between client and actor return await handler.routingHandler.custom.proxyWebSocket( c, "/connect/websocket", - workerId, + actorId, params.data.encoding, params.data.connParams, authData, @@ -985,7 +985,7 @@ async function handleWebSocketConnectRequest( } /** - * Handle a connection message request to a worker + * Handle a connection message request to a actor * * There is no authentication handler on this request since the connection * token is used to authenticate the message. @@ -999,7 +999,7 @@ async function handleMessageRequest( logger().debug("connection message request received"); try { const params = ConnMessageRequestSchema.safeParse({ - workerId: c.req.header(HEADER_WORKER_ID), + actorId: c.req.header(HEADER_ACTOR_ID), connId: c.req.header(HEADER_CONN_ID), encoding: c.req.header(HEADER_ENCODING), connToken: c.req.header(HEADER_CONN_TOKEN), @@ -1010,23 +1010,23 @@ async function handleMessageRequest( }); throw new errors.InvalidRequest(params.error); } - const { workerId, connId, encoding, connToken } = params.data; + const { actorId, connId, encoding, connToken } = params.data; - // TODO: This endpoint can be used to exhause resources (DoS attack) on an worker if you know the worker ID: - // 1. Get the worker ID (usually this is reasonably secure, but we don't assume worker ID is sensitive) - // 2. Spam messages to the worker (the conn token can be invalid) - // 3. The worker will be exhausted processing messages — even if the token is invalid + // TODO: This endpoint can be used to exhause resources (DoS attack) on an actor if you know the actor ID: + // 1. Get the actor ID (usually this is reasonably secure, but we don't assume actor ID is sensitive) + // 2. Spam messages to the actor (the conn token can be invalid) + // 3. The actor will be exhausted processing messages — even if the token is invalid // // The solution is we need to move the authorization of the connection token to this request handler - // AND include the worker ID in the connection token so we can verify that it has permission to send - // a message to that worker. This would require changing the token to a JWT so we can include a secure + // AND include the actor ID in the connection token so we can verify that it has permission to send + // a message to that actor. This would require changing the token to a JWT so we can include a secure // payload, but this requires managing a private key & managing key rotations. // - // All other solutions (e.g. include the worker name as a header or include the worker name in the worker ID) - // have exploits that allow the caller to send messages to arbitrary workers. + // All other solutions (e.g. include the actor name as a header or include the actor name in the actor ID) + // have exploits that allow the caller to send messages to arbitrary actors. // // Currently, we assume this is not a critical problem because requests will likely get rate - // limited before enough messages are passed to the worker to exhaust resources. + // limited before enough messages are passed to the actor to exhaust resources. // Handle based on mode if ("inline" in handler.routingHandler) { @@ -1039,11 +1039,11 @@ async function handleMessageRequest( handler.routingHandler.inline.handlers.onConnMessage, connId, connToken as string, - workerId, + actorId, ); } else if ("custom" in handler.routingHandler) { logger().debug("using custom proxy mode for connection message"); - const url = new URL("http://worker/connections/message"); + const url = new URL("http://actor/connections/message"); // Always build fresh request to prevent forwarding unwanted headers const proxyRequest = new Request(url, { @@ -1057,7 +1057,7 @@ async function handleMessageRequest( return await handler.routingHandler.custom.proxyRequest( c, proxyRequest, - workerId, + actorId, ); } else { assertUnreachable(handler.routingHandler); @@ -1065,8 +1065,8 @@ async function handleMessageRequest( } catch (error) { logger().error("error proxying connection message", { error }); - // Use ProxyError if it's not already an WorkerError - if (!errors.WorkerError.isWorkerError(error)) { + // Use ProxyError if it's not already an ActorError + if (!errors.ActorError.isActorError(error)) { throw new errors.ProxyError("connection message", error); } else { throw error; @@ -1075,7 +1075,7 @@ async function handleMessageRequest( } /** - * Handle an action request to a worker + * Handle an action request to a actor */ async function handleActionRequest( c: HonoContext, @@ -1116,10 +1116,10 @@ async function handleActionRequest( connParams, ); - // Get the worker ID - const { workerId } = await queryWorker(c, params.data.query, driver); - logger().debug("found worker for action", { workerId }); - invariant(workerId, "Missing worker ID"); + // Get the actor ID + const { actorId } = await queryActor(c, params.data.query, driver); + logger().debug("found actor for action", { actorId }); + invariant(actorId, "Missing actor ID"); // Handle based on mode if ("inline" in handler.routingHandler) { @@ -1131,14 +1131,14 @@ async function handleActionRequest( runConfig, handler.routingHandler.inline.handlers.onAction, actionName, - workerId, + actorId, authData, ); } else if ("custom" in handler.routingHandler) { logger().debug("using custom proxy mode for action call"); const url = new URL( - `http://worker/action/${encodeURIComponent(actionName)}`, + `http://actor/action/${encodeURIComponent(actionName)}`, ); // Always build fresh request to prevent forwarding unwanted headers @@ -1157,7 +1157,7 @@ async function handleActionRequest( return await handler.routingHandler.custom.proxyRequest( c, proxyRequest, - workerId, + actorId, ); } else { assertUnreachable(handler.routingHandler); @@ -1165,8 +1165,8 @@ async function handleActionRequest( } catch (error) { logger().error("error in action handler", { error }); - // Use ProxyError if it's not already an WorkerError - if (!errors.WorkerError.isWorkerError(error)) { + // Use ProxyError if it's not already an ActorError + if (!errors.ActorError.isActorError(error)) { throw new errors.ProxyError("Action call", error); } else { throw error; @@ -1175,7 +1175,7 @@ async function handleActionRequest( } /** - * Handle the resolve request to get a worker ID from a query + * Handle the resolve request to get a actor ID from a query */ async function handleResolveRequest( c: HonoContext, @@ -1206,14 +1206,14 @@ async function handleResolveRequest( // Authenticate the request await authenticateEndpoint(c, driver, registryConfig, query, [], connParams); - // Get the worker ID - const { workerId } = await queryWorker(c, query, driver); - logger().debug("resolved worker", { workerId }); - invariant(workerId, "Missing worker ID"); + // Get the actor ID + const { actorId } = await queryActor(c, query, driver); + logger().debug("resolved actor", { actorId }); + invariant(actorId, "Missing actor ID"); // Format response according to protocol const response: protoHttpResolve.ResolveResponse = { - i: workerId, + i: actorId, }; const serialized = serialize(response, encoding); return c.body(serialized); diff --git a/packages/core/src/mod.ts b/packages/core/src/mod.ts index a327cf4ba..997c65b09 100644 --- a/packages/core/src/mod.ts +++ b/packages/core/src/mod.ts @@ -1,3 +1,3 @@ export * from "@/registry/mod"; -export * from "@/worker/mod"; +export * from "@/actor/mod"; export * from "@/topologies/mod"; diff --git a/packages/core/src/registry/config.ts b/packages/core/src/registry/config.ts index 833b3c1e2..2759ba639 100644 --- a/packages/core/src/registry/config.ts +++ b/packages/core/src/registry/config.ts @@ -1,20 +1,20 @@ -//! These configs configs hold anything that's not platform-specific about running workers. +//! These configs configs hold anything that's not platform-specific about running actors. -import { AnyWorkerDefinition, WorkerDefinition } from "@/worker/definition"; +import { AnyActorDefinition, ActorDefinition } from "@/actor/definition"; import { z } from "zod"; -export const WorkersSchema = z.record( +export const ActorsSchema = z.record( z.string(), - z.custom>(), + z.custom>(), ); -export type RegistryWorkers = z.infer; +export type RegistryActors = z.infer; export const TestConfigSchema = z.object({ enabled: z.boolean() }); export type TestConfig = z.infer; -/** Base config used for the worker config across all platforms. */ +/** Base config used for the actor config across all platforms. */ export const RegistryConfigSchema = z.object({ - workers: z.record(z.string(), z.custom()), + actors: z.record(z.string(), z.custom()), // TODO: Find a better way of passing around the test config /** @@ -26,7 +26,7 @@ export const RegistryConfigSchema = z.object({ test: TestConfigSchema.optional().default({ enabled: false }), }); export type RegistryConfig = z.infer; -export type RegistryConfigInput = Omit< +export type RegistryConfigInput = Omit< z.input, - "workers" -> & { workers: A }; + "actors" +> & { actors: A }; diff --git a/packages/core/src/registry/mod.ts b/packages/core/src/registry/mod.ts index b282e4cc1..5c58118c3 100644 --- a/packages/core/src/registry/mod.ts +++ b/packages/core/src/registry/mod.ts @@ -1,6 +1,6 @@ import { Client, ClientDriver, createClientWithDriver } from "@/client/client"; import { - type RegistryWorkers, + type RegistryActors, type RegistryConfig, type RegistryConfigInput, RegistryConfigSchema, @@ -15,7 +15,7 @@ import { StandaloneTopology } from "@/topologies/standalone/mod"; import invariant from "invariant"; import { Hono } from "hono"; import { assertUnreachable } from "@/utils"; -import { PartitionTopologyManager, PartitionTopologyWorker } from "@/mod"; +import { PartitionTopologyManager, PartitionTopologyActor } from "@/mod"; import { logger } from "./log"; import { crossPlatformServe } from "./serve"; @@ -26,13 +26,13 @@ interface ServerOutput> { serve: (hono?: Hono) => void; } -interface WorkerNodeOutput { +interface ActorNodeOutput { hono: Hono; handler: (req: Request) => Promise; serve: (hono?: Hono) => void; } -export class Registry { +export class Registry { #config: RegistryConfig; public get config(): RegistryConfig { @@ -88,20 +88,20 @@ export class Registry { } /** - * Runs the registry for a worker node. + * Runs the registry for a actor node. */ - public workerNode(inputConfig?: RunConfigInput): WorkerNodeOutput { + public actorNode(inputConfig?: RunConfigInput): ActorNodeOutput { const config = RunConfigSchema.parse(inputConfig); // Setup topology let hono: Hono; if (config.driver.topology === "standalone") { - invariant(false, "cannot run worker node for standalone topology"); + invariant(false, "cannot run actor node for standalone topology"); } else if (config.driver.topology === "partition") { - const topology = new PartitionTopologyWorker(this.#config, config); + const topology = new PartitionTopologyActor(this.#config, config); hono = topology.router; } else if (config.driver.topology === "coordinate") { - invariant(false, "cannot run worker node for coordinate topology"); + invariant(false, "cannot run actor node for coordinate topology"); } else { assertUnreachable(config.driver.topology); } @@ -114,20 +114,20 @@ export class Registry { } /** - * Runs the standalone worker node. + * Runs the standalone actor node. */ - public async runWorkerNode(inputConfig?: RunConfigInput) { - const { serve } = this.workerNode(inputConfig); + public async runActorNode(inputConfig?: RunConfigInput) { + const { serve } = this.actorNode(inputConfig); serve(); } } -export function setup( +export function setup( input: RegistryConfigInput, ): Registry { const config = RegistryConfigSchema.parse(input); return new Registry(config); } -export type { RegistryConfig, RegistryWorkers, RunConfig, DriverConfig }; +export type { RegistryConfig, RegistryActors, RunConfig, DriverConfig }; export { RegistryConfigSchema }; diff --git a/packages/core/src/registry/run-config.ts b/packages/core/src/registry/run-config.ts index b86066f03..60d6e03b9 100644 --- a/packages/core/src/registry/run-config.ts +++ b/packages/core/src/registry/run-config.ts @@ -2,16 +2,16 @@ import { z } from "zod"; import type { Hono } from "hono"; import type { CoordinateDriver } from "@/topologies/coordinate/driver"; import type { ManagerDriver } from "@/manager/driver"; -import type { WorkerDriver } from "@/worker/driver"; +import type { ActorDriver } from "@/actor/driver"; import type { UpgradeWebSocket } from "@/utils"; import type { cors } from "hono/cors"; import { createMemoryDriver } from "@/drivers/memory/mod"; type CorsOptions = NonNullable[0]>; -export const WorkerPeerConfigSchema = z.object({ +export const ActorPeerConfigSchema = z.object({ /** - * How long the worker leader holds a lease for. + * How long the actor leader holds a lease for. * * Milliseconds **/ @@ -41,7 +41,7 @@ export const WorkerPeerConfigSchema = z.object({ */ messageAckTimeout: z.number().optional().default(1000), }); -export type WorkerPeerConfig = z.infer; +export type ActorPeerConfig = z.infer; export const TopologySchema = z.enum(["standalone", "partition", "coordinate"]); export type Topology = z.infer; @@ -51,13 +51,13 @@ export type GetUpgradeWebSocket = (router: Hono) => UpgradeWebSocket; export const DriverConfigSchema = z.object({ topology: TopologySchema, manager: z.custom(), - worker: z.custom(), + actor: z.custom(), coordinate: z.custom().optional(), }); export type DriverConfig = z.infer; -/** Base config used for the worker config across all platforms. */ +/** Base config used for the actor config across all platforms. */ export const RunConfigSchema = z .object({ driver: DriverConfigSchema.optional().default(() => createMemoryDriver()), @@ -71,7 +71,7 @@ export const RunConfigSchema = z maxIncomingMessageSize: z.number().optional().default(65_536), /** Peer configuration for coordinated topology. */ - workerPeer: WorkerPeerConfigSchema.optional().default({}), + actorPeer: ActorPeerConfigSchema.optional().default({}), // inspector: InspectorConfigSchema.optional().default({ enabled: false }), }).default({}); diff --git a/packages/core/src/test/driver/actor.ts b/packages/core/src/test/driver/actor.ts new file mode 100644 index 000000000..0bcc9631f --- /dev/null +++ b/packages/core/src/test/driver/actor.ts @@ -0,0 +1,44 @@ +import type { ActorDriver, AnyActorInstance } from "@/driver-helpers/mod"; +import type { TestGlobalState } from "./global-state"; + +export interface ActorDriverContext { + // Used to test that the actor context works from tests + isTest: boolean; +} + +export class TestActorDriver implements ActorDriver { + #state: TestGlobalState; + + constructor(state: TestGlobalState) { + this.#state = state; + } + + getContext(_actorId: string): ActorDriverContext { + return { + isTest: true, + }; + } + + async readInput(actorId: string): Promise { + return this.#state.readInput(actorId); + } + + async readPersistedData(actorId: string): Promise { + return this.#state.readPersistedData(actorId); + } + + async writePersistedData(actorId: string, data: unknown): Promise { + this.#state.writePersistedData(actorId, data); + } + + async setAlarm(actor: AnyActorInstance, timestamp: number): Promise { + const delay = Math.max(timestamp - Date.now(), 0); + setTimeout(() => { + actor.onAlarm(); + }, delay); + } + + getDatabase(actorId: string): Promise { + return Promise.resolve(undefined); + } +} diff --git a/packages/core/src/test/driver/global-state.ts b/packages/core/src/test/driver/global-state.ts index ffe33a381..0f3d69fb0 100644 --- a/packages/core/src/test/driver/global-state.ts +++ b/packages/core/src/test/driver/global-state.ts @@ -1,70 +1,70 @@ -import type { WorkerKey } from "@/mod"; +import type { ActorKey } from "@/mod"; -export interface WorkerState { +export interface ActorState { id: string; name: string; - key: WorkerKey; + key: ActorKey; persistedData: unknown; input?: unknown; } export class TestGlobalState { - #workers: Map = new Map(); + #actors: Map = new Map(); - #getWorker(workerId: string): WorkerState { - const worker = this.#workers.get(workerId); - if (!worker) { - throw new Error(`Worker does not exist for ID: ${workerId}`); + #getActor(actorId: string): ActorState { + const actor = this.#actors.get(actorId); + if (!actor) { + throw new Error(`Actor does not exist for ID: ${actorId}`); } - return worker; + return actor; } - readInput(workerId: string): unknown | undefined { - return this.#getWorker(workerId).input; + readInput(actorId: string): unknown | undefined { + return this.#getActor(actorId).input; } - readPersistedData(workerId: string): unknown | undefined { - return this.#getWorker(workerId).persistedData; + readPersistedData(actorId: string): unknown | undefined { + return this.#getActor(actorId).persistedData; } - writePersistedData(workerId: string, data: unknown) { - this.#getWorker(workerId).persistedData = data; + writePersistedData(actorId: string, data: unknown) { + this.#getActor(actorId).persistedData = data; } - createWorker( - workerId: string, + createActor( + actorId: string, name: string, - key: WorkerKey, + key: ActorKey, input?: unknown, ): void { - // Create worker state if it doesn't exist - if (!this.#workers.has(workerId)) { - this.#workers.set(workerId, { - id: workerId, + // Create actor state if it doesn't exist + if (!this.#actors.has(actorId)) { + this.#actors.set(actorId, { + id: actorId, name, key, persistedData: undefined, input, }); } else { - throw new Error(`Worker already exists for ID: ${workerId}`); + throw new Error(`Actor already exists for ID: ${actorId}`); } } - findWorker(filter: (worker: WorkerState) => boolean): WorkerState | undefined { - for (const worker of this.#workers.values()) { - if (filter(worker)) { - return worker; + findActor(filter: (actor: ActorState) => boolean): ActorState | undefined { + for (const actor of this.#actors.values()) { + if (filter(actor)) { + return actor; } } return undefined; } - getWorker(workerId: string): WorkerState | undefined { - return this.#workers.get(workerId); + getActor(actorId: string): ActorState | undefined { + return this.#actors.get(actorId); } - getAllWorkers(): WorkerState[] { - return Array.from(this.#workers.values()); + getAllActors(): ActorState[] { + return Array.from(this.#actors.values()); } } diff --git a/packages/core/src/test/driver/manager.ts b/packages/core/src/test/driver/manager.ts index 12d83f1c9..668fdced5 100644 --- a/packages/core/src/test/driver/manager.ts +++ b/packages/core/src/test/driver/manager.ts @@ -5,17 +5,17 @@ import type { ManagerDriver, CreateInput, } from "@/driver-helpers/mod"; -import { WorkerAlreadyExists } from "@/worker/errors"; +import { ActorAlreadyExists } from "@/actor/errors"; import type { TestGlobalState } from "./global-state"; import * as crypto from "node:crypto"; -import { WorkerOutput } from "@/manager/driver"; +import { ActorOutput } from "@/manager/driver"; export class TestManagerDriver implements ManagerDriver { #state: TestGlobalState; // inspector: ManagerInspector = new ManagerInspector(this, { - // getAllWorkers: () => this.#state.getAllWorkers(), - // getAllTypesOfWorkers: () => Object.keys(this.registry.config.workers), + // getAllActors: () => this.#state.getAllActors(), + // getAllTypesOfActors: () => Object.keys(this.registry.config.actors), // }); constructor( @@ -24,48 +24,48 @@ export class TestManagerDriver implements ManagerDriver { this.#state = state; } - async getForId({ workerId }: GetForIdInput): Promise { - // Validate the worker exists - const worker = this.#state.getWorker(workerId); - if (!worker) { + async getForId({ actorId }: GetForIdInput): Promise { + // Validate the actor exists + const actor = this.#state.getActor(actorId); + if (!actor) { return undefined; } return { - workerId, - name: worker.name, - key: worker.key, + actorId, + name: actor.name, + key: actor.key, }; } async getWithKey({ name, key, - }: GetWithKeyInput): Promise { - // NOTE: This is a slow implementation that checks each worker individually. + }: GetWithKeyInput): Promise { + // NOTE: This is a slow implementation that checks each actor individually. // This can be optimized with an index in the future. - const worker = this.#state.findWorker((worker) => { - if (worker.name !== name) { + const actor = this.#state.findActor((actor) => { + if (actor.name !== name) { return false; } // handle empty key if (key === null || key === undefined) { - return worker.key === null || worker.key === undefined; + return actor.key === null || actor.key === undefined; } // handle array if (Array.isArray(key)) { - if (!Array.isArray(worker.key)) { + if (!Array.isArray(actor.key)) { return false; } - if (key.length !== worker.key.length) { + if (key.length !== actor.key.length) { return false; } - // Check if all elements in key are in worker.key + // Check if all elements in key are in actor.key for (let i = 0; i < key.length; i++) { - if (key[i] !== worker.key[i]) { + if (key[i] !== actor.key[i]) { return false; } } @@ -74,18 +74,18 @@ export class TestManagerDriver implements ManagerDriver { // Handle object if (typeof key === "object" && !Array.isArray(key)) { - if (typeof worker.key !== "object" || Array.isArray(worker.key)) { + if (typeof actor.key !== "object" || Array.isArray(actor.key)) { return false; } - if (worker.key === null) { + if (actor.key === null) { return false; } - // Check if all keys in key are in worker.key + // Check if all keys in key are in actor.key const keyObj = key as Record; - const workerKeyObj = worker.key as unknown as Record; + const actorKeyObj = actor.key as unknown as Record; for (const k in keyObj) { - if (!(k in workerKeyObj) || keyObj[k] !== workerKeyObj[k]) { + if (!(k in actorKeyObj) || keyObj[k] !== actorKeyObj[k]) { return false; } } @@ -93,14 +93,14 @@ export class TestManagerDriver implements ManagerDriver { } // handle scalar - return key === worker.key; + return key === actor.key; }); - if (worker) { + if (actor) { return { - workerId: worker.id, + actorId: actor.id, name, - key: worker.key, + key: actor.key, }; } @@ -109,29 +109,29 @@ export class TestManagerDriver implements ManagerDriver { async getOrCreateWithKey( input: GetOrCreateWithKeyInput, - ): Promise { + ): Promise { const getOutput = await this.getWithKey(input); if (getOutput) { return getOutput; } else { - return await this.createWorker(input); + return await this.createActor(input); } } - async createWorker({ name, key, input }: CreateInput): Promise { - // Check if worker with the same name and key already exists - const existingWorker = await this.getWithKey({ name, key }); - if (existingWorker) { - throw new WorkerAlreadyExists(name, key); + async createActor({ name, key, input }: CreateInput): Promise { + // Check if actor with the same name and key already exists + const existingActor = await this.getWithKey({ name, key }); + if (existingActor) { + throw new ActorAlreadyExists(name, key); } - const workerId = crypto.randomUUID(); - this.#state.createWorker(workerId, name, key, input); + const actorId = crypto.randomUUID(); + this.#state.createActor(actorId, name, key, input); - // this.inspector.onWorkersChange(this.#state.getAllWorkers()); + // this.inspector.onActorsChange(this.#state.getAllActors()); return { - workerId, + actorId, name, key, }; diff --git a/packages/core/src/test/driver/mod.ts b/packages/core/src/test/driver/mod.ts index 33d6d88e2..b3276ffb8 100644 --- a/packages/core/src/test/driver/mod.ts +++ b/packages/core/src/test/driver/mod.ts @@ -1,10 +1,10 @@ import { DriverConfig } from "@/mod"; import { TestGlobalState } from "./global-state"; import { TestManagerDriver } from "./manager"; -import { TestWorkerDriver } from "./worker"; +import { TestActorDriver } from "./actor"; export { TestGlobalState } from "./global-state"; -export { TestWorkerDriver } from "./worker"; +export { TestActorDriver } from "./actor"; export { TestManagerDriver } from "./manager"; export function createTestDriver(): DriverConfig { @@ -12,6 +12,6 @@ export function createTestDriver(): DriverConfig { return { topology: "standalone", manager: new TestManagerDriver(state), - worker: new TestWorkerDriver(state), + actor: new TestActorDriver(state), }; } diff --git a/packages/core/src/test/driver/worker.ts b/packages/core/src/test/driver/worker.ts deleted file mode 100644 index 1140896b4..000000000 --- a/packages/core/src/test/driver/worker.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { WorkerDriver, AnyWorkerInstance } from "@/driver-helpers/mod"; -import type { TestGlobalState } from "./global-state"; - -export interface WorkerDriverContext { - // Used to test that the worker context works from tests - isTest: boolean; -} - -export class TestWorkerDriver implements WorkerDriver { - #state: TestGlobalState; - - constructor(state: TestGlobalState) { - this.#state = state; - } - - getContext(_workerId: string): WorkerDriverContext { - return { - isTest: true, - }; - } - - async readInput(workerId: string): Promise { - return this.#state.readInput(workerId); - } - - async readPersistedData(workerId: string): Promise { - return this.#state.readPersistedData(workerId); - } - - async writePersistedData(workerId: string, data: unknown): Promise { - this.#state.writePersistedData(workerId, data); - } - - async setAlarm(worker: AnyWorkerInstance, timestamp: number): Promise { - const delay = Math.max(timestamp - Date.now(), 0); - setTimeout(() => { - worker.onAlarm(); - }, delay); - } - - getDatabase(workerId: string): Promise { - return Promise.resolve(undefined); - } -} diff --git a/packages/core/src/test/mod.ts b/packages/core/src/test/mod.ts index b1742dbec..74fe4879f 100644 --- a/packages/core/src/test/mod.ts +++ b/packages/core/src/test/mod.ts @@ -8,7 +8,7 @@ import { StandaloneTopology, type Registry } from "@/mod"; import { TestGlobalState, TestManagerDriver, - TestWorkerDriver, + TestActorDriver, } from "./driver/mod"; import { type InputConfig, ConfigSchema } from "./config"; import { type TestContext, vi } from "vitest"; @@ -31,7 +31,7 @@ function createRouter( config.driver = { topology: "standalone", manager: new TestManagerDriver(memoryState), - worker: new TestWorkerDriver(memoryState), + actor: new TestActorDriver(memoryState), }; } @@ -87,7 +87,7 @@ function serve(registry: Registry, inputConfig?: InputConfig): ServerType { export interface SetupTestResult> { client: Client; mockDriver: { - workerDriver: { + actorDriver: { setCreateVarsContext: (ctx: any) => void; }; }; @@ -122,7 +122,7 @@ export async function setupTest>( return { client, mockDriver: { - workerDriver: { + actorDriver: { setCreateVarsContext: setDriverContextFn, }, }, diff --git a/packages/core/src/topologies/common/generic-conn-driver.ts b/packages/core/src/topologies/common/generic-conn-driver.ts index a9e107835..ba5153ad0 100644 --- a/packages/core/src/topologies/common/generic-conn-driver.ts +++ b/packages/core/src/topologies/common/generic-conn-driver.ts @@ -1,19 +1,19 @@ -import type { AnyWorkerInstance } from "@/worker/instance"; -import { AnyConn, Conn } from "@/worker/connection"; +import type { AnyActorInstance } from "@/actor/instance"; +import { AnyConn, Conn } from "@/actor/connection"; import { logger } from "./log"; -import { CachedSerializer, Encoding } from "@/worker/protocol/serde"; -import { ConnDriver } from "@/worker/driver"; -import * as messageToClient from "@/worker/protocol/message/to-client"; -import { encodeDataToString } from "@/worker/protocol/serde"; +import { CachedSerializer, Encoding } from "@/actor/protocol/serde"; +import { ConnDriver } from "@/actor/driver"; +import * as messageToClient from "@/actor/protocol/message/to-client"; +import { encodeDataToString } from "@/actor/protocol/serde"; import { WSContext } from "hono/ws"; import { SSEStreamingApi } from "hono/streaming"; import type { WebSocket } from "ws"; -// This state is different than `PersistedConn` state since the connection-specific state is persisted & must be serializable. This is also part of the connection driver, not part of the core worker. +// This state is different than `PersistedConn` state since the connection-specific state is persisted & must be serializable. This is also part of the connection driver, not part of the core actor. // // This holds the actual connections, which are not serializable. // -// This is scoped to each worker. Do not share between multiple workers. +// This is scoped to each actor. Do not share between multiple actors. export class GenericConnGlobalState { websockets = new Map(); sseStreams = new Map(); @@ -44,7 +44,7 @@ export function createGenericWebSocketDriver( ): ConnDriver { return { sendMessage: ( - _worker: AnyWorkerInstance, + _actor: AnyActorInstance, conn: AnyConn, state: GenericWebSocketDriverState, message: CachedSerializer, @@ -58,7 +58,7 @@ export function createGenericWebSocketDriver( }, disconnect: async ( - _worker: AnyWorkerInstance, + _actor: AnyActorInstance, conn: AnyConn, _state: GenericWebSocketDriverState, reason?: string, @@ -97,7 +97,7 @@ export interface GenericSseDriverState { export function createGenericSseDriver(globalState: GenericConnGlobalState) { return { sendMessage: ( - _worker: AnyWorkerInstance, + _actor: AnyActorInstance, conn: AnyConn, state: GenericSseDriverState, message: CachedSerializer, @@ -115,7 +115,7 @@ export function createGenericSseDriver(globalState: GenericConnGlobalState) { }, disconnect: async ( - _worker: AnyWorkerInstance, + _actor: AnyActorInstance, conn: AnyConn, _state: GenericSseDriverState, _reason?: string, diff --git a/packages/core/src/topologies/coordinate/worker-peer.ts b/packages/core/src/topologies/coordinate/actor-peer.ts similarity index 57% rename from packages/core/src/topologies/coordinate/worker-peer.ts rename to packages/core/src/topologies/coordinate/actor-peer.ts index 6e8d1ee7f..7ca65902a 100644 --- a/packages/core/src/topologies/coordinate/worker-peer.ts +++ b/packages/core/src/topologies/coordinate/actor-peer.ts @@ -1,9 +1,9 @@ import type { GlobalState } from "@/topologies/coordinate/topology"; import { logger } from "./log"; import type { CoordinateDriver } from "./driver"; -import type { WorkerInstance, AnyWorkerInstance } from "@/worker/instance"; -import type { WorkerKey } from "@/common/utils"; -import { WorkerDriver } from "@/worker/driver"; +import type { ActorInstance, AnyActorInstance } from "@/actor/instance"; +import type { ActorKey } from "@/common/utils"; +import { ActorDriver } from "@/actor/driver"; import { CONN_DRIVER_COORDINATE_RELAY, createCoordinateRelayDriver, @@ -11,25 +11,25 @@ import { import { RegistryConfig, RegistryConfigSchema } from "@/registry/config"; import { DriverConfig, RunConfig } from "@/registry/run-config"; -export class WorkerPeer { +export class ActorPeer { #registryConfig: RegistryConfig; #runConfig: RunConfig; #coordinateDriver: CoordinateDriver; - #workerDriver: WorkerDriver; + #actorDriver: ActorDriver; #globalState: GlobalState; - #workerId: string; - #workerName?: string; - #workerKey?: WorkerKey; + #actorId: string; + #actorName?: string; + #actorKey?: ActorKey; #isDisposed = false; - /** Connections that hold a reference to this worker. If this set is empty, the worker should be shut down. */ + /** Connections that hold a reference to this actor. If this set is empty, the actor should be shut down. */ #referenceConnections = new Set(); - /** Node ID that's the leader for this worker. */ + /** Node ID that's the leader for this actor. */ #leaderNodeId?: string; - /** Holds the insantiated worker class if is leader. */ - #loadedWorker?: AnyWorkerInstance; + /** Holds the insantiated actor class if is leader. */ + #loadedActor?: AnyActorInstance; #heartbeatTimeout?: NodeJS.Timeout; @@ -46,48 +46,48 @@ export class WorkerPeer { registryConfig: RegistryConfig, runConfig: RunConfig, CoordinateDriver: CoordinateDriver, - workerDriver: WorkerDriver, + actorDriver: ActorDriver, globalState: GlobalState, - workerId: string, + actorId: string, ) { this.#registryConfig = registryConfig; this.#runConfig = runConfig; this.#coordinateDriver = CoordinateDriver; - this.#workerDriver = workerDriver; + this.#actorDriver = actorDriver; this.#globalState = globalState; - this.#workerId = workerId; + this.#actorId = actorId; } - /** Acquires a `WorkerPeer` for a connection and includes the connection ID in the references. */ + /** Acquires a `ActorPeer` for a connection and includes the connection ID in the references. */ static async acquire( registryConfig: RegistryConfig, runConfig: RunConfig, - workerDriver: WorkerDriver, + actorDriver: ActorDriver, CoordinateDriver: CoordinateDriver, globalState: GlobalState, - workerId: string, + actorId: string, connId: string, - ): Promise { - let peer = globalState.workerPeers.get(workerId); + ): Promise { + let peer = globalState.actorPeers.get(actorId); // Create peer if needed if (!peer) { - peer = new WorkerPeer( + peer = new ActorPeer( registryConfig, runConfig, CoordinateDriver, - workerDriver, + actorDriver, globalState, - workerId, + actorId, ); - globalState.workerPeers.set(workerId, peer); + globalState.actorPeers.set(actorId, peer); await peer.#start(); } peer.#referenceConnections.add(connId); - logger().debug("added worker reference", { - workerId, + logger().debug("added actor reference", { + actorId, connId, newReferenceCount: peer.#referenceConnections.size, }); @@ -95,17 +95,17 @@ export class WorkerPeer { return peer; } - static getLeaderWorker( + static getLeaderActor( globalState: GlobalState, - workerId: string, - ): AnyWorkerInstance | undefined { - const peer = globalState.workerPeers.get(workerId); + actorId: string, + ): AnyActorInstance | undefined { + const peer = globalState.actorPeers.get(actorId); if (!peer) return undefined; if (peer.#isLeader) { - const worker = peer.#loadedWorker; - if (!worker) - throw new Error("Worker is leader, but loadedWorker is undefined"); - return worker; + const actor = peer.#loadedActor; + if (!actor) + throw new Error("Actor is leader, but loadedActor is undefined"); + return actor; } else { return undefined; } @@ -122,7 +122,7 @@ export class WorkerPeer { // TODO: Use a global Redis connection // TODO: Add TTL to connections // TODO: Maybe use queue for leader instead of pubsub so the P2P is durable - // TODO: Remove worker from globalState + // TODO: Remove actor from globalState // TODO: Add back NX // Acquire lease @@ -130,41 +130,41 @@ export class WorkerPeer { // TODO: Do this in 1 round trip with a Lua script // Acquire initial information - const { worker } = await this.#coordinateDriver.startWorkerAndAcquireLease( - this.#workerId, + const { actor } = await this.#coordinateDriver.startActorAndAcquireLease( + this.#actorId, this.#globalState.nodeId, - this.#runConfig.workerPeer.leaseDuration, + this.#runConfig.actorPeer.leaseDuration, ); // Log - logger().debug("starting worker peer", { - worker, + logger().debug("starting actor peer", { + actor, }); - // Validate worker exists - if (!worker) { - throw new Error("Worker does not exist"); + // Validate actor exists + if (!actor) { + throw new Error("Actor does not exist"); } // Parse tags - this.#workerName = worker.name; - this.#workerKey = worker.key; + this.#actorName = actor.name; + this.#actorKey = actor.key; // Handle leadership - this.#leaderNodeId = worker.leaderNodeId; - if (worker.leaderNodeId === this.#globalState.nodeId) { - logger().debug("worker peer is leader", { - workerId: this.#workerId, - leaderNodeId: worker.leaderNodeId, + this.#leaderNodeId = actor.leaderNodeId; + if (actor.leaderNodeId === this.#globalState.nodeId) { + logger().debug("actor peer is leader", { + actorId: this.#actorId, + leaderNodeId: actor.leaderNodeId, }); await this.#convertToLeader(); } else { - logger().debug("worker peer is follower", { - workerId: this.#workerId, - leaderNodeId: worker.leaderNodeId, + logger().debug("actor peer is follower", { + actorId: this.#actorId, + leaderNodeId: actor.leaderNodeId, }); - this.#leaderNodeId = worker.leaderNodeId; + this.#leaderNodeId = actor.leaderNodeId; } // Schedule first heartbeat @@ -189,44 +189,44 @@ export class WorkerPeer { let hbTimeout: number; if (this.#isLeader) { hbTimeout = - this.#runConfig.workerPeer.leaseDuration - - this.#runConfig.workerPeer.renewLeaseGrace; + this.#runConfig.actorPeer.leaseDuration - + this.#runConfig.actorPeer.renewLeaseGrace; } else { // TODO: Add jitter hbTimeout = - this.#runConfig.workerPeer.checkLeaseInterval + - Math.random() * this.#runConfig.workerPeer.checkLeaseJitter; + this.#runConfig.actorPeer.checkLeaseInterval + + Math.random() * this.#runConfig.actorPeer.checkLeaseJitter; } if (hbTimeout < 0) - throw new Error("Worker peer heartbeat timeout is negative, check config"); + throw new Error("Actor peer heartbeat timeout is negative, check config"); this.#heartbeatTimeout = setTimeout(this.#heartbeat.bind(this), hbTimeout); } async #convertToLeader() { - if (!this.#workerName || !this.#workerKey) throw new Error("missing name or key"); + if (!this.#actorName || !this.#actorKey) throw new Error("missing name or key"); - logger().debug("peer acquired leadership", { workerId: this.#workerId }); + logger().debug("peer acquired leadership", { actorId: this.#actorId }); - // Build worker - const workerName = this.#workerName; - const definition = this.#registryConfig.workers[workerName]; - if (!definition) throw new Error(`no worker definition for name ${definition}`); + // Build actor + const actorName = this.#actorName; + const definition = this.#registryConfig.actors[actorName]; + if (!definition) throw new Error(`no actor definition for name ${definition}`); - // Create leader worker - const worker = definition.instantiate(); - this.#loadedWorker = worker; + // Create leader actor + const actor = definition.instantiate(); + this.#loadedActor = actor; - await worker.start( + await actor.start( { [CONN_DRIVER_COORDINATE_RELAY]: createCoordinateRelayDriver( this.#globalState, this.#coordinateDriver, ), }, - this.#workerDriver, - this.#workerId, - this.#workerName, - this.#workerKey, + this.#actorDriver, + this.#actorId, + this.#actorName, + this.#actorKey, "unknown", ); } @@ -234,18 +234,18 @@ export class WorkerPeer { /** * Extends the lease if the current leader. Called on an interval for leaders leader. * - * If the lease has expired for any reason (e.g. connection latency or database purged), this will automatically shut down the worker. + * If the lease has expired for any reason (e.g. connection latency or database purged), this will automatically shut down the actor. */ async #extendLease() { const { leaseValid } = await this.#coordinateDriver.extendLease( - this.#workerId, + this.#actorId, this.#globalState.nodeId, - this.#runConfig.workerPeer.leaseDuration, + this.#runConfig.actorPeer.leaseDuration, ); if (leaseValid) { - logger().debug("lease is valid", { workerId: this.#workerId }); + logger().debug("lease is valid", { actorId: this.#actorId }); } else { - logger().debug("lease is invalid", { workerId: this.#workerId }); + logger().debug("lease is invalid", { actorId: this.#actorId }); // Shut down. SInce the lease is already lost, no need to clear it. await this.#dispose(false); @@ -258,9 +258,9 @@ export class WorkerPeer { async #attemptAcquireLease() { const { newLeaderNodeId } = await this.#coordinateDriver.attemptAcquireLease( - this.#workerId, + this.#actorId, this.#globalState.nodeId, - this.#runConfig.workerPeer.leaseDuration, + this.#runConfig.actorPeer.leaseDuration, ); // Check if the lease was successfully acquired and promoted to leader @@ -282,14 +282,14 @@ export class WorkerPeer { const removed = this.#referenceConnections.delete(connId); if (removed) { - logger().debug("removed worker reference", { - workerId: this.#workerId, + logger().debug("removed actor reference", { + actorId: this.#actorId, connId, newReferenceCount: this.#referenceConnections.size, }); } else { - logger().warn("removed reference to worker that didn't exist", { - workerId: this.#workerId, + logger().warn("removed reference to actor that didn't exist", { + actorId: this.#actorId, connId, }); } @@ -303,27 +303,27 @@ export class WorkerPeer { if (this.#isDisposed) return; this.#isDisposed = true; - logger().info("worker shutting down", { workerId: this.#workerId }); + logger().info("actor shutting down", { actorId: this.#actorId }); // IMPORTANT: Do this before anything async clearTimeout(this.#heartbeatTimeout); - this.#globalState.workerPeers.delete(this.#workerId); + this.#globalState.actorPeers.delete(this.#actorId); - // Stop worker + // Stop actor // // We wait for this to finish to ensure that state is persisted safely to storage - if (this.#isLeader && this.#loadedWorker) { - await this.#loadedWorker.stop(); + if (this.#isLeader && this.#loadedActor) { + await this.#loadedActor.stop(); } // Clear the lease if needed if (this.#isLeader && releaseLease) { await this.#coordinateDriver.releaseLease( - this.#workerId, + this.#actorId, this.#globalState.nodeId, ); } - logger().info("worker shutdown success", { workerId: this.#workerId }); + logger().info("actor shutdown success", { actorId: this.#actorId }); } } diff --git a/packages/core/src/topologies/coordinate/conn/driver.ts b/packages/core/src/topologies/coordinate/conn/driver.ts index 88d169028..1e01aa079 100644 --- a/packages/core/src/topologies/coordinate/conn/driver.ts +++ b/packages/core/src/topologies/coordinate/conn/driver.ts @@ -1,9 +1,9 @@ -import type { ConnDriver } from "@/worker/driver"; +import type { ConnDriver } from "@/actor/driver"; import type { GlobalState } from "../topology"; -import type { AnyWorkerInstance } from "@/worker/instance"; -import type { AnyConn, Conn } from "@/worker/connection"; -import type { CachedSerializer } from "@/worker/protocol/serde"; -import type * as messageToClient from "@/worker/protocol/message/to-client"; +import type { AnyActorInstance } from "@/actor/instance"; +import type { AnyConn, Conn } from "@/actor/connection"; +import type { CachedSerializer } from "@/actor/protocol/serde"; +import type * as messageToClient from "@/actor/protocol/message/to-client"; import { logger } from "../log"; import type { NodeMessage } from "../node/protocol"; import type { CoordinateDriver } from "../driver"; @@ -20,14 +20,14 @@ export function createCoordinateRelayDriver( ): ConnDriver { return { sendMessage: ( - worker: AnyWorkerInstance, + actor: AnyActorInstance, conn: AnyConn, state: CoordinateRelayState, message: CachedSerializer, ) => { - const workerPeer = globalState.workerPeers.get(worker.id); - if (!workerPeer) { - logger().warn("missing worker for message", { workerId: worker.id }); + const actorPeer = globalState.actorPeers.get(actor.id); + if (!actorPeer) { + logger().warn("missing actor for message", { actorId: actor.id }); return; } @@ -43,16 +43,16 @@ export function createCoordinateRelayDriver( CoordinateDriver.publishToNode(state.nodeId, JSON.stringify(messageRaw)); }, disconnect: async ( - worker: AnyWorkerInstance, + actor: AnyActorInstance, conn: AnyConn, state: CoordinateRelayState, reason?: string, ) => { - if (worker.isStopping) return; + if (actor.isStopping) return; - const workerPeer = globalState.workerPeers.get(worker.id); - if (!workerPeer) { - logger().warn("missing worker for disconnect", { workerId: worker.id }); + const actorPeer = globalState.actorPeers.get(actor.id); + if (!actorPeer) { + logger().warn("missing actor for disconnect", { actorId: actor.id }); return; } diff --git a/packages/core/src/topologies/coordinate/conn/mod.ts b/packages/core/src/topologies/coordinate/conn/mod.ts index 08e7c6980..a40db21af 100644 --- a/packages/core/src/topologies/coordinate/conn/mod.ts +++ b/packages/core/src/topologies/coordinate/conn/mod.ts @@ -1,12 +1,12 @@ import type { GlobalState } from "@/topologies/coordinate/topology"; -import type * as messageToClient from "@/worker/protocol/message/to-client"; -import * as errors from "@/worker/errors"; +import type * as messageToClient from "@/actor/protocol/message/to-client"; +import * as errors from "@/actor/errors"; import type { CoordinateDriver } from "../driver"; import { logger } from "../log"; -import { WorkerPeer } from "../worker-peer"; +import { ActorPeer } from "../actor-peer"; import { publishMessageToLeader } from "../node/message"; -import { generateConnId, generateConnToken } from "@/worker/connection"; -import type { WorkerDriver } from "@/worker/driver"; +import { generateConnId, generateConnToken } from "@/actor/connection"; +import type { ActorDriver } from "@/actor/driver"; import { RegistryConfig } from "@/registry/config"; import { unknown } from "zod"; import { RunConfig } from "@/registry/run-config"; @@ -17,20 +17,20 @@ export interface RelayConnDriver { } /** - * This is different than `Connection`. `Connection` represents the data of the connection state on the worker itself, `RelayConnection` supports managing a connection for a worker running on another machine over pubsub. + * This is different than `Connection`. `Connection` represents the data of the connection state on the actor itself, `RelayConnection` supports managing a connection for a actor running on another machine over pubsub. */ export class RelayConn { #registryConfig: RegistryConfig; #runConfig: RunConfig; #coordinateDriver: CoordinateDriver; - #workerDriver: WorkerDriver; + #actorDriver: ActorDriver; #globalState: GlobalState; #driver: RelayConnDriver; - #workerId: string; + #actorId: string; #parameters: unknown; #authData: unknown; - #workerPeer?: WorkerPeer; + #actorPeer?: ActorPeer; #connId?: string; #connToken?: string; @@ -52,21 +52,21 @@ export class RelayConn { constructor( registryConfig: RegistryConfig, runConfig: RunConfig, - workerDriver: WorkerDriver, + actorDriver: ActorDriver, CoordinateDriver: CoordinateDriver, globalState: GlobalState, driver: RelayConnDriver, - workerId: string, + actorId: string, parameters: unknown, authData: unknown, ) { this.#registryConfig = registryConfig; this.#runConfig = runConfig; this.#coordinateDriver = CoordinateDriver; - this.#workerDriver = workerDriver; + this.#actorDriver = actorDriver; this.#driver = driver; this.#globalState = globalState; - this.#workerId = workerId; + this.#actorId = actorId; this.#parameters = parameters; this.#authData = authData; } @@ -81,18 +81,18 @@ export class RelayConn { this.#connToken = connToken; logger().info("starting relay connection", { - workerId: this.#workerId, + actorId: this.#actorId, connId: this.#connId, }); - // Create worker peer - this.#workerPeer = await WorkerPeer.acquire( + // Create actor peer + this.#actorPeer = await ActorPeer.acquire( this.#registryConfig, this.#runConfig, - this.#workerDriver, + this.#actorDriver, this.#coordinateDriver, this.#globalState, - this.#workerId, + this.#actorId, connId, ); @@ -104,11 +104,11 @@ export class RelayConn { this.#runConfig, this.#coordinateDriver, this.#globalState, - this.#workerId, + this.#actorId, { b: { lco: { - ai: this.#workerId, + ai: this.#actorId, ci: connId, ct: connToken, p: this.#parameters, @@ -147,18 +147,18 @@ export class RelayConn { this.#globalState.relayConns.delete(this.#connId); // Publish connection close - if (!fromLeader && this.#workerPeer?.leaderNodeId) { + if (!fromLeader && this.#actorPeer?.leaderNodeId) { // Publish connection close await publishMessageToLeader( this.#registryConfig, this.#runConfig, this.#coordinateDriver, this.#globalState, - this.#workerId, + this.#actorId, { b: { lcc: { - ai: this.#workerId, + ai: this.#actorId, ci: this.#connId, }, }, @@ -167,10 +167,10 @@ export class RelayConn { ); } - // Remove reference to worker (will shut down if no more references) + // Remove reference to actor (will shut down if no more references) // // IMPORTANT: Do this last since we need to send the connection close event - await this.#workerPeer?.removeConnectionReference(this.#connId); + await this.#actorPeer?.removeConnectionReference(this.#connId); } else { logger().warn("disposing connection without connection id"); } diff --git a/packages/core/src/topologies/coordinate/driver.ts b/packages/core/src/topologies/coordinate/driver.ts index 8b1324055..b569c5c39 100644 --- a/packages/core/src/topologies/coordinate/driver.ts +++ b/packages/core/src/topologies/coordinate/driver.ts @@ -1,19 +1,19 @@ -import type { WorkerKey } from "@/common/utils"; +import type { ActorKey } from "@/common/utils"; export type NodeMessageCallback = (message: string) => void; -export interface GetWorkerLeaderOutput { +export interface GetActorLeaderOutput { /** Undefined if not initialized. */ - worker?: { + actor?: { leaderNodeId?: string; }; } -export interface StartWorkerAndAcquireLeaseOutput { +export interface StartActorAndAcquireLeaseOutput { /** Undefined if not initialized. */ - worker?: { + actor?: { name?: string; - key?: WorkerKey; + key?: ActorKey; leaderNodeId?: string; }; } @@ -34,22 +34,22 @@ export interface CoordinateDriver { ): Promise; publishToNode(targetNodeId: string, message: string): Promise; - // MARK: Worker lifecycle - getWorkerLeader(workerId: string): Promise; - startWorkerAndAcquireLease( - workerId: string, + // MARK: Actor lifecycle + getActorLeader(actorId: string): Promise; + startActorAndAcquireLease( + actorId: string, selfNodeId: string, leaseDuration: number, - ): Promise; + ): Promise; extendLease( - workerId: string, + actorId: string, selfNodeId: string, leaseDuration: number, ): Promise; attemptAcquireLease( - workerId: string, + actorId: string, selfNodeId: string, leaseDuration: number, ): Promise; - releaseLease(workerId: string, nodeId: string): Promise; + releaseLease(actorId: string, nodeId: string): Promise; } \ No newline at end of file diff --git a/packages/core/src/topologies/coordinate/log.ts b/packages/core/src/topologies/coordinate/log.ts index 769291a65..c032a7727 100644 --- a/packages/core/src/topologies/coordinate/log.ts +++ b/packages/core/src/topologies/coordinate/log.ts @@ -1,6 +1,6 @@ import { getLogger } from "@/common/log"; -export const LOGGER_NAME = "worker-coordinate"; +export const LOGGER_NAME = "actor-coordinate"; export function logger() { return getLogger(LOGGER_NAME); diff --git a/packages/core/src/topologies/coordinate/node/message.ts b/packages/core/src/topologies/coordinate/node/message.ts index 4c31a4ede..91e0c8603 100644 --- a/packages/core/src/topologies/coordinate/node/message.ts +++ b/packages/core/src/topologies/coordinate/node/message.ts @@ -16,7 +16,7 @@ export async function publishMessageToLeader( runConfig: RunConfig, CoordinateDriver: CoordinateDriver, globalState: GlobalState, - workerId: string, + actorId: string, message: NodeMessage, signal?: AbortSignal, ) { @@ -35,7 +35,7 @@ export async function publishMessageToLeader( runConfig, CoordinateDriver, globalState, - workerId, + actorId, messageId, message, signal, @@ -59,23 +59,23 @@ async function publishMessageToLeaderInner( runConfig: RunConfig, CoordinateDriver: CoordinateDriver, globalState: GlobalState, - workerId: string, + actorId: string, messageId: string, message: NodeMessage, signal?: AbortSignal, ) { // Find the leader node - const { worker } = await CoordinateDriver.getWorkerLeader(workerId); + const { actor } = await CoordinateDriver.getActorLeader(actorId); // Validate initialized - if (!worker) throw new AbortError("Worker not initialized"); + if (!actor) throw new AbortError("Actor not initialized"); // Validate node - if (!worker.leaderNodeId) { - throw new Error("worker not leased, may be transferring leadership"); + if (!actor.leaderNodeId) { + throw new Error("actor not leased, may be transferring leadership"); } - logger().debug("found worker leader node", { nodeId: worker.leaderNodeId }); + logger().debug("found actor leader node", { nodeId: actor.leaderNodeId }); // Create ack resolver const { @@ -92,13 +92,13 @@ async function publishMessageToLeaderInner( // Throw error on timeout const timeoutId = setTimeout( () => ackReject(new Error("Ack timed out")), - runConfig.workerPeer.messageAckTimeout, + runConfig.actorPeer.messageAckTimeout, ); try { // Forward outgoing message await CoordinateDriver.publishToNode( - worker.leaderNodeId, + actor.leaderNodeId, JSON.stringify(message), ); diff --git a/packages/core/src/topologies/coordinate/node/mod.ts b/packages/core/src/topologies/coordinate/node/mod.ts index 1a2c417e1..a80223d3f 100644 --- a/packages/core/src/topologies/coordinate/node/mod.ts +++ b/packages/core/src/topologies/coordinate/node/mod.ts @@ -10,7 +10,7 @@ import { type ToLeaderConnectionOpen, type ToLeaderMessage, } from "./protocol"; -import { WorkerPeer } from "../worker-peer"; +import { ActorPeer } from "../actor-peer"; import type { CoordinateDriver } from "../driver"; import { CONN_DRIVER_COORDINATE_RELAY, type CoordinateRelayState } from "../conn/driver"; import { assertUnreachable } from "@/common/utils"; @@ -31,9 +31,9 @@ export class Node { // // We intentionally design this so there's only one topic for the subscriber to listen on in order to reduce chattiness to the pubsub server. // - // If we had a dedicated topic for each worker, we'd have to create a SUB for each leader & follower for each worker which is much more expensive than one for each node. + // If we had a dedicated topic for each actor, we'd have to create a SUB for each leader & follower for each actor which is much more expensive than one for each node. // - // Additionally, in most serverless environments, 1 node usually owns 1 worker, so this would double the RTT to create the required subscribers. + // Additionally, in most serverless environments, 1 node usually owns 1 actor, so this would double the RTT to create the required subscribers. await this.#CoordinateDriver.createNodeSubscriber( this.#globalState.nodeId, this.#onMessage.bind(this), @@ -44,7 +44,7 @@ export class Node { async #onMessage(message: string) { // TODO: try catch this - // TODO: Support multiple protocols for the worker + // TODO: Support multiple protocols for the actor // Parse message const { data, success, error } = NodeMessageSchema.safeParse( @@ -101,7 +101,7 @@ export class Node { async #onLeaderConnectionOpen( nodeId: string | undefined, { - ai: workerId, + ai: actorId, ci: connId, ct: connToken, p: connParams, @@ -113,21 +113,21 @@ export class Node { return; } - logger().debug("received connection open", { workerId, connId }); + logger().debug("received connection open", { actorId, connId }); // Connection open try { - const worker = WorkerPeer.getLeaderWorker(this.#globalState, workerId); - if (!worker) { - logger().warn("received message for nonexistent worker leader", { - workerId, + const actor = ActorPeer.getLeaderActor(this.#globalState, actorId); + if (!actor) { + logger().warn("received message for nonexistent actor leader", { + actorId, }); return; } - const connState = await worker.prepareConn(connParams); - await worker.createConn( + const connState = await actor.prepareConn(connParams); + await actor.createConn( connId, connToken, connParams, @@ -137,7 +137,7 @@ export class Node { authData, ); - // Connection init will be sent by `Worker` + // Connection init will be sent by `Actor` } catch (error) { logger().warn("failed to open connection", { error: `${error}` }); @@ -152,29 +152,29 @@ export class Node { // }, //}; //redis.publish( - // PUBSUB.WORKER.follower(workerId, followerId), + // PUBSUB.ACTOR.follower(actorId, followerId), // JSON.stringify(message), //); } } async #onLeaderConnectionClose({ - ai: workerId, + ai: actorId, ci: connId, }: ToLeaderConnectionClose) { - logger().debug("received connection close", { workerId }); + logger().debug("received connection close", { actorId }); - const worker = WorkerPeer.getLeaderWorker(this.#globalState, workerId); - if (!worker) { - logger().warn("received message for nonexistent worker leader", { - workerId, + const actor = ActorPeer.getLeaderActor(this.#globalState, actorId); + if (!actor) { + logger().warn("received message for nonexistent actor leader", { + actorId, }); return; } - const conn = worker.__getConnForId(connId); + const conn = actor.__getConnForId(connId); if (conn) { - worker.__removeConn(conn); + actor.__removeConn(conn); } else { logger().warn("received connection close for nonexisting connection", { connId, @@ -183,24 +183,24 @@ export class Node { } async #onLeaderMessage({ - ai: workerId, + ai: actorId, ci: connId, ct: connToken, m: message, }: ToLeaderMessage) { - logger().debug("received connection message", { workerId, connId }); + logger().debug("received connection message", { actorId, connId }); // Get leader - const worker = WorkerPeer.getLeaderWorker(this.#globalState, workerId); - if (!worker) { - logger().warn("received message for nonexistent worker leader", { - workerId, + const actor = ActorPeer.getLeaderActor(this.#globalState, actorId); + if (!actor) { + logger().warn("received message for nonexistent actor leader", { + actorId, }); return; } // Get connection - const conn = worker.__getConnForId(connId); + const conn = actor.__getConnForId(connId); if (conn) { // Validate token if (conn._token !== connToken) { @@ -208,7 +208,7 @@ export class Node { } // Process message - await worker.processMessage(message, conn); + await actor.processMessage(message, conn); } else { logger().warn("received message for nonexisting connection", { connId, diff --git a/packages/core/src/topologies/coordinate/node/protocol.ts b/packages/core/src/topologies/coordinate/node/protocol.ts index f33c15566..f80bb6e74 100644 --- a/packages/core/src/topologies/coordinate/node/protocol.ts +++ b/packages/core/src/topologies/coordinate/node/protocol.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import * as messageToServer from "@/worker/protocol/message/to-server" -import * as messageToClient from "@/worker/protocol/message/to-client" +import * as messageToServer from "@/actor/protocol/message/to-server" +import * as messageToClient from "@/actor/protocol/message/to-client" export const AckSchema = z.object({ // Message ID @@ -10,7 +10,7 @@ export const AckSchema = z.object({ export type Ack = z.infer; export const ToLeaderConnectionOpenSchema = z.object({ - // Worker ID + // Actor ID ai: z.string(), // Connection ID ci: z.string(), @@ -25,7 +25,7 @@ export const ToLeaderConnectionOpenSchema = z.object({ export type ToLeaderConnectionOpen = z.infer; export const ToLeaderConnectionCloseSchema = z.object({ - // Worker ID + // Actor ID ai: z.string(), // Connection ID ci: z.string(), @@ -34,7 +34,7 @@ export const ToLeaderConnectionCloseSchema = z.object({ export type ToLeaderConnectionClose = z.infer; export const ToLeaderMessageSchema = z.object({ - // Worker ID + // Actor ID ai: z.string(), // Connection ID ci: z.string(), diff --git a/packages/core/src/topologies/coordinate/router/sse.ts b/packages/core/src/topologies/coordinate/router/sse.ts index 779745ce8..9623166f1 100644 --- a/packages/core/src/topologies/coordinate/router/sse.ts +++ b/packages/core/src/topologies/coordinate/router/sse.ts @@ -1,20 +1,20 @@ import type { GlobalState } from "@/topologies/coordinate/topology"; import { logger } from "../log"; -import { encodeDataToString, serialize } from "@/worker/protocol/serde"; +import { encodeDataToString, serialize } from "@/actor/protocol/serde"; import type { CoordinateDriver } from "../driver"; import { RelayConn } from "../conn/mod"; -import type { WorkerDriver } from "@/worker/driver"; +import type { ActorDriver } from "@/actor/driver"; import { RegistryConfig } from "@/registry/config"; -import { ConnectSseOpts, ConnectSseOutput } from "@/worker/router-endpoints"; +import { ConnectSseOpts, ConnectSseOutput } from "@/actor/router-endpoints"; import { RunConfig } from "@/registry/run-config"; export async function serveSse( registryConfig: RegistryConfig, runConfig: RunConfig, - workerDriver: WorkerDriver, + actorDriver: ActorDriver, CoordinateDriver: CoordinateDriver, globalState: GlobalState, - workerId: string, + actorId: string, { encoding, params, authData }: ConnectSseOpts, ): Promise { let conn: RelayConn | undefined; @@ -23,7 +23,7 @@ export async function serveSse( conn = new RelayConn( registryConfig, runConfig, - workerDriver, + actorDriver, CoordinateDriver, globalState, { @@ -37,7 +37,7 @@ export async function serveSse( stream.close(); }, }, - workerId, + actorId, params, authData, ); diff --git a/packages/core/src/topologies/coordinate/router/websocket.ts b/packages/core/src/topologies/coordinate/router/websocket.ts index 8a38234e3..c62874a14 100644 --- a/packages/core/src/topologies/coordinate/router/websocket.ts +++ b/packages/core/src/topologies/coordinate/router/websocket.ts @@ -1,27 +1,27 @@ import type { GlobalState } from "@/topologies/coordinate/topology"; import type { WSContext } from "hono/ws"; import { logger } from "../log"; -import { serialize } from "@/worker/protocol/serde"; -import type * as messageToServer from "@/worker/protocol/message/to-server"; -import * as errors from "@/worker/errors"; +import { serialize } from "@/actor/protocol/serde"; +import type * as messageToServer from "@/actor/protocol/message/to-server"; +import * as errors from "@/actor/errors"; import type { CoordinateDriver } from "../driver"; import { RelayConn } from "../conn/mod"; import { publishMessageToLeader } from "../node/message"; -import type { WorkerDriver } from "@/worker/driver"; +import type { ActorDriver } from "@/actor/driver"; import type { RegistryConfig } from "@/registry/config"; import { ConnectWebSocketOpts, ConnectWebSocketOutput, -} from "@/worker/router-endpoints"; +} from "@/actor/router-endpoints"; import { DriverConfig, RunConfig } from "@/registry/run-config"; export async function serveWebSocket( registryConfig: RegistryConfig, runConfig: RunConfig, - workerDriver: WorkerDriver, + actorDriver: ActorDriver, CoordinateDriver: CoordinateDriver, globalState: GlobalState, - workerId: string, + actorId: string, { req, encoding, params, authData }: ConnectWebSocketOpts, ): Promise { let conn: RelayConn | undefined; @@ -30,7 +30,7 @@ export async function serveWebSocket( conn = new RelayConn( registryConfig, runConfig, - workerDriver, + actorDriver, CoordinateDriver, globalState, { @@ -42,7 +42,7 @@ export async function serveWebSocket( ws.close(); }, }, - workerId, + actorId, params, authData, ); @@ -58,11 +58,11 @@ export async function serveWebSocket( runConfig, CoordinateDriver, globalState, - workerId, + actorId, { b: { lm: { - ai: workerId, + ai: actorId, ci: conn.connId, ct: conn.connToken, m: message, diff --git a/packages/core/src/topologies/coordinate/topology.ts b/packages/core/src/topologies/coordinate/topology.ts index f0ade50dd..ce0810107 100644 --- a/packages/core/src/topologies/coordinate/topology.ts +++ b/packages/core/src/topologies/coordinate/topology.ts @@ -1,6 +1,6 @@ import { Node } from "./node/mod"; -import type { WorkerPeer } from "./worker-peer"; -import * as errors from "@/worker/errors"; +import type { ActorPeer } from "./actor-peer"; +import * as errors from "@/actor/errors"; import * as events from "node:events"; import { publishMessageToLeader } from "./node/message"; import type { RelayConn } from "./conn/mod"; @@ -17,19 +17,19 @@ import type { ConnectSseOutput, ActionOutput, ConnectionHandlers, -} from "@/worker/router-endpoints"; +} from "@/actor/router-endpoints"; import invariant from "invariant"; import { createInlineClientDriver } from "@/inline-client-driver/mod"; import { serveWebSocket } from "./router/websocket"; import { serveSse } from "./router/sse"; import { ClientDriver } from "@/client/client"; -import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; import { DriverConfig, RunConfig } from "@/registry/run-config"; export interface GlobalState { nodeId: string; - /** Workers currently running on this instance. */ - workerPeers: Map; + /** Actors currently running on this instance. */ + actorPeers: Map; /** Connections that are connected to this node. */ relayConns: Map; /** Resolvers for when a message is acknowledged by the peer. */ @@ -41,7 +41,7 @@ export class CoordinateTopology { public readonly router: Hono; constructor(registryConfig: RegistryConfig, runConfig: RunConfig) { - const { worker: workerDriver, coordinate: CoordinateDriver } = + const { actor: actorDriver, coordinate: CoordinateDriver } = runConfig.driver; if (!CoordinateDriver) throw new Error("config.driver.coordinate not defined."); @@ -52,7 +52,7 @@ export class CoordinateTopology { const globalState: GlobalState = { nodeId: crypto.randomUUID(), - workerPeers: new Map(), + actorPeers: new Map(), relayConns: new Map(), messageAckResolvers: new Map(), }; @@ -73,10 +73,10 @@ export class CoordinateTopology { return await serveWebSocket( registryConfig, runConfig, - workerDriver, + actorDriver, CoordinateDriver, globalState, - opts.workerId, + opts.actorId, opts, ); }, @@ -84,10 +84,10 @@ export class CoordinateTopology { return await serveSse( registryConfig, runConfig, - workerDriver, + actorDriver, CoordinateDriver, globalState, - opts.workerId, + opts.actorId, opts, ); }, @@ -101,11 +101,11 @@ export class CoordinateTopology { runConfig, CoordinateDriver, globalState, - opts.workerId, + opts.actorId, { b: { lm: { - ai: opts.workerId, + ai: opts.actorId, ci: opts.connId, ct: opts.connToken, m: opts.message, diff --git a/packages/core/src/topologies/partition/worker-router.ts b/packages/core/src/topologies/partition/actor-router.ts similarity index 88% rename from packages/core/src/topologies/partition/worker-router.ts rename to packages/core/src/topologies/partition/actor-router.ts index 1bde816ca..98e28bba8 100644 --- a/packages/core/src/topologies/partition/worker-router.ts +++ b/packages/core/src/topologies/partition/actor-router.ts @@ -26,9 +26,9 @@ import { HEADER_CONN_PARAMS, HEADER_AUTH_DATA, HEADER_ENCODING, -} from "@/worker/router-endpoints"; +} from "@/actor/router-endpoints"; import invariant from "invariant"; -import { EncodingSchema } from "@/worker/protocol/serde"; +import { EncodingSchema } from "@/actor/protocol/serde"; import { DriverConfig, RunConfig } from "@/registry/run-config"; export type { @@ -41,22 +41,22 @@ export type { ConnsMessageOpts, }; -export interface WorkerRouterHandler { - getWorkerId: () => Promise; +export interface ActorRouterHandler { + getActorId: () => Promise; // Connection handlers as a required subobject connectionHandlers: ConnectionHandlers; - // onConnectInspector?: WorkerInspectorConnHandler; + // onConnectInspector?: ActorInspectorConnHandler; } /** * Creates a router that runs on the partitioned instance. */ -export function createWorkerRouter( +export function createActorRouter( registryConfig: RegistryConfig, runConfig: RunConfig, - handler: WorkerRouterHandler, + handler: ActorRouterHandler, ): Hono { const router = new Hono({ strict: false }); @@ -81,7 +81,7 @@ export function createWorkerRouter( router.get( "/connect/websocket", upgradeWebSocket(async (c) => { - const workerId = await handler.getWorkerId(); + const actorId = await handler.getActorId(); const encodingRaw = c.req.header(HEADER_ENCODING); const connParamsRaw = c.req.header(HEADER_CONN_PARAMS); const authDataRaw = c.req.header(HEADER_AUTH_DATA); @@ -97,7 +97,7 @@ export function createWorkerRouter( registryConfig, runConfig, handlers.onConnectWebSocket!, - workerId, + actorId, encoding, connParams, authData, @@ -117,7 +117,7 @@ export function createWorkerRouter( if (!handlers.onConnectSse) { throw new Error("onConnectSse handler is required"); } - const workerId = await handler.getWorkerId(); + const actorId = await handler.getActorId(); const authDataRaw = c.req.header(HEADER_AUTH_DATA); let authData: unknown = undefined; @@ -130,7 +130,7 @@ export function createWorkerRouter( registryConfig, runConfig, handlers.onConnectSse, - workerId, + actorId, authData, ); }); @@ -140,7 +140,7 @@ export function createWorkerRouter( throw new Error("onAction handler is required"); } const actionName = c.req.param("action"); - const workerId = await handler.getWorkerId(); + const actorId = await handler.getActorId(); const authDataRaw = c.req.header(HEADER_AUTH_DATA); let authData: unknown = undefined; @@ -154,7 +154,7 @@ export function createWorkerRouter( runConfig, handlers.onAction, actionName, - workerId, + actorId, authData, ); }); @@ -165,7 +165,7 @@ export function createWorkerRouter( } const connId = c.req.header(HEADER_CONN_ID); const connToken = c.req.header(HEADER_CONN_TOKEN); - const workerId = await handler.getWorkerId(); + const actorId = await handler.getActorId(); if (!connId || !connToken) { throw new Error("Missing required parameters"); } @@ -176,14 +176,14 @@ export function createWorkerRouter( handlers.onConnMessage, connId, connToken, - workerId, + actorId, ); }); // if (registryConfig.inspector.enabled) { // router.route( // "/inspect", - // createWorkerInspectorRouter( + // createActorInspectorRouter( // upgradeWebSocket, // handler.onConnectInspector, // registryConfig.inspector, diff --git a/packages/core/src/topologies/partition/log.ts b/packages/core/src/topologies/partition/log.ts index b35019b8f..0177368e3 100644 --- a/packages/core/src/topologies/partition/log.ts +++ b/packages/core/src/topologies/partition/log.ts @@ -1,6 +1,6 @@ import { getLogger } from "@/common//log"; -export const LOGGER_NAME = "worker-partition"; +export const LOGGER_NAME = "actor-partition"; export function logger() { return getLogger(LOGGER_NAME); diff --git a/packages/core/src/topologies/partition/mod.ts b/packages/core/src/topologies/partition/mod.ts index 402980a57..ca533cc9a 100644 --- a/packages/core/src/topologies/partition/mod.ts +++ b/packages/core/src/topologies/partition/mod.ts @@ -1 +1 @@ -export { PartitionTopologyManager, PartitionTopologyWorker } from "./topology"; +export { PartitionTopologyManager, PartitionTopologyActor } from "./topology"; diff --git a/packages/core/src/topologies/partition/topology.ts b/packages/core/src/topologies/partition/topology.ts index 7e8b6a107..5fff363ab 100644 --- a/packages/core/src/topologies/partition/topology.ts +++ b/packages/core/src/topologies/partition/topology.ts @@ -1,14 +1,14 @@ import { Hono, HonoRequest } from "hono"; -import { createWorkerRouter } from "@/topologies/partition/worker-router"; -import type { AnyWorkerInstance } from "@/worker/instance"; -import * as errors from "@/worker/errors"; +import { createActorRouter } from "@/topologies/partition/actor-router"; +import type { AnyActorInstance } from "@/actor/instance"; +import * as errors from "@/actor/errors"; import { type AnyConn, generateConnId, generateConnToken, -} from "@/worker/connection"; +} from "@/actor/connection"; import { logger } from "./log"; -import { ActionContext } from "@/worker/action"; +import { ActionContext } from "@/actor/action"; import { CONN_DRIVER_GENERIC_HTTP, CONN_DRIVER_GENERIC_SSE, @@ -19,8 +19,8 @@ import { type GenericSseDriverState, type GenericWebSocketDriverState, } from "../common/generic-conn-driver"; -import type { ConnDriver } from "@/worker/driver"; -import type { WorkerKey } from "@/common/utils"; +import type { ConnDriver } from "@/actor/driver"; +import type { ActorKey } from "@/common/utils"; import type { RegistryConfig } from "@/registry/config"; import { createManagerRouter } from "@/manager/router"; import type { @@ -31,22 +31,22 @@ import type { ConnectWebSocketOutput, ConnectSseOutput, ActionOutput, -} from "@/worker/router-endpoints"; +} from "@/actor/router-endpoints"; import { ClientDriver } from "@/client/client"; import { createInlineClientDriver } from "@/inline-client-driver/mod"; -import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; import invariant from "invariant"; import type { WebSocket } from "ws"; import type { RunConfig } from "@/registry/run-config"; export type SendRequestHandler = ( - workerRequest: Request, - workerId: string, + actorRequest: Request, + actorId: string, ) => Promise; export type OpenWebSocketHandler = ( path: string, - workerId: string, + actorId: string, ) => Promise; export class PartitionTopologyManager { @@ -100,24 +100,24 @@ export class PartitionTopologyManager { } } -/** Manages the worker in the topology. */ -export class PartitionTopologyWorker { +/** Manages the actor in the topology. */ +export class PartitionTopologyActor { router: Hono; #registryConfig: RegistryConfig; #runConfig: RunConfig; #connDrivers: Record; - #worker?: AnyWorkerInstance; + #actor?: AnyActorInstance; - get worker(): AnyWorkerInstance { - if (!this.#worker) throw new Error("Worker not loaded"); - return this.#worker; + get actor(): AnyActorInstance { + if (!this.#actor) throw new Error("Actor not loaded"); + return this.#actor; } /** - * Promise used to wait until the worker is started. All network requests wait on this promise in order to ensure they're not accessing the worker before initialize. + * Promise used to wait until the actor is started. All network requests wait on this promise in order to ensure they're not accessing the actor before initialize. **/ - #workerStartedPromise?: PromiseWithResolvers = Promise.withResolvers(); + #actorStartedPromise?: PromiseWithResolvers = Promise.withResolvers(); constructor(registryConfig: RegistryConfig, runConfig: RunConfig) { this.#registryConfig = registryConfig; @@ -126,26 +126,26 @@ export class PartitionTopologyWorker { const genericConnGlobalState = new GenericConnGlobalState(); this.#connDrivers = createGenericConnDrivers(genericConnGlobalState); - // TODO: Store this worker router globally so we're not re-initializing it for every DO - this.router = createWorkerRouter(registryConfig, runConfig, { - getWorkerId: async () => { - if (this.#workerStartedPromise) - await this.#workerStartedPromise.promise; - return this.worker.id; + // TODO: Store this actor router globally so we're not re-initializing it for every DO + this.router = createActorRouter(registryConfig, runConfig, { + getActorId: async () => { + if (this.#actorStartedPromise) + await this.#actorStartedPromise.promise; + return this.actor.id; }, connectionHandlers: { onConnectWebSocket: async ( opts: ConnectWebSocketOpts, ): Promise => { - if (this.#workerStartedPromise) - await this.#workerStartedPromise.promise; + if (this.#actorStartedPromise) + await this.#actorStartedPromise.promise; - const worker = this.#worker; - if (!worker) throw new Error("Worker should be defined"); + const actor = this.#actor; + if (!actor) throw new Error("Actor should be defined"); const connId = generateConnId(); const connToken = generateConnToken(); - const connState = await worker.prepareConn( + const connState = await actor.prepareConn( opts.params, opts.req?.raw, ); @@ -157,7 +157,7 @@ export class PartitionTopologyWorker { genericConnGlobalState.websockets.set(connId, ws); // Create connection - conn = await worker.createConn( + conn = await actor.createConn( connId, connToken, opts.params, @@ -177,13 +177,13 @@ export class PartitionTopologyWorker { return; } - await worker.processMessage(message, conn); + await actor.processMessage(message, conn); }, onClose: async () => { genericConnGlobalState.websockets.delete(connId); if (conn) { - worker.__removeConn(conn); + actor.__removeConn(conn); } }, }; @@ -191,15 +191,15 @@ export class PartitionTopologyWorker { onConnectSse: async ( opts: ConnectSseOpts, ): Promise => { - if (this.#workerStartedPromise) - await this.#workerStartedPromise.promise; + if (this.#actorStartedPromise) + await this.#actorStartedPromise.promise; - const worker = this.#worker; - if (!worker) throw new Error("Worker should be defined"); + const actor = this.#actor; + if (!actor) throw new Error("Actor should be defined"); const connId = generateConnId(); const connToken = generateConnToken(); - const connState = await worker.prepareConn( + const connState = await actor.prepareConn( opts.params, opts.req?.raw, ); @@ -211,7 +211,7 @@ export class PartitionTopologyWorker { genericConnGlobalState.sseStreams.set(connId, stream); // Create connection - conn = await worker.createConn( + conn = await actor.createConn( connId, connToken, opts.params, @@ -225,7 +225,7 @@ export class PartitionTopologyWorker { genericConnGlobalState.sseStreams.delete(connId); if (conn) { - worker.__removeConn(conn); + actor.__removeConn(conn); } }, }; @@ -234,18 +234,18 @@ export class PartitionTopologyWorker { let conn: AnyConn | undefined; try { // Wait for init to finish - if (this.#workerStartedPromise) - await this.#workerStartedPromise.promise; + if (this.#actorStartedPromise) + await this.#actorStartedPromise.promise; - const worker = this.#worker; - if (!worker) throw new Error("Worker should be defined"); + const actor = this.#actor; + if (!actor) throw new Error("Actor should be defined"); // Create conn - const connState = await worker.prepareConn( + const connState = await actor.prepareConn( opts.params, opts.req?.raw, ); - conn = await worker.createConn( + conn = await actor.createConn( generateConnId(), generateConnToken(), opts.params, @@ -256,8 +256,8 @@ export class PartitionTopologyWorker { ); // Call action - const ctx = new ActionContext(worker.workerContext!, conn!); - const output = await worker.executeAction( + const ctx = new ActionContext(actor.actorContext!, conn!); + const output = await actor.executeAction( ctx, opts.actionName, opts.actionArgs, @@ -266,20 +266,20 @@ export class PartitionTopologyWorker { return { output }; } finally { if (conn) { - this.#worker?.__removeConn(conn); + this.#actor?.__removeConn(conn); } } }, onConnMessage: async (opts: ConnsMessageOpts): Promise => { // Wait for init to finish - if (this.#workerStartedPromise) - await this.#workerStartedPromise.promise; + if (this.#actorStartedPromise) + await this.#actorStartedPromise.promise; - const worker = this.#worker; - if (!worker) throw new Error("Worker should be defined"); + const actor = this.#actor; + if (!actor) throw new Error("Actor should be defined"); // Find connection - const conn = worker.conns.get(opts.connId); + const conn = actor.conns.get(opts.connId); if (!conn) { throw new errors.ConnNotFound(opts.connId); } @@ -290,20 +290,20 @@ export class PartitionTopologyWorker { } // Process message - await worker.processMessage(opts.message, conn); + await actor.processMessage(opts.message, conn); }, }, // onConnectInspector: async () => { - // if (this.#workerStartedPromise) - // await this.#workerStartedPromise.promise; + // if (this.#actorStartedPromise) + // await this.#actorStartedPromise.promise; // - // const worker = this.#worker; - // if (!worker) throw new Error("Worker should be defined"); + // const actor = this.#actor; + // if (!actor) throw new Error("Actor should be defined"); // - // let conn: WorkerInspectorConnection | undefined; + // let conn: ActorInspectorConnection | undefined; // return { // onOpen: async (ws) => { - // conn = worker.inspector.createConnection(ws); + // conn = actor.inspector.createConnection(ws); // }, // onMessage: async (message) => { // if (!conn) { @@ -311,11 +311,11 @@ export class PartitionTopologyWorker { // return; // } // - // worker.inspector.processMessage(conn, message); + // actor.inspector.processMessage(conn, message); // }, // onClose: async () => { // if (conn) { - // worker.inspector.removeConnection(conn); + // actor.inspector.removeConnection(conn); // } // }, // }; @@ -323,29 +323,29 @@ export class PartitionTopologyWorker { }); } - async start(id: string, name: string, key: WorkerKey, region: string) { - const workerDriver = this.#runConfig.driver.worker; + async start(id: string, name: string, key: ActorKey, region: string) { + const actorDriver = this.#runConfig.driver.actor; - // Find worker prototype - const definition = this.#registryConfig.workers[name]; + // Find actor prototype + const definition = this.#registryConfig.actors[name]; // TODO: Handle error here gracefully somehow if (!definition) - throw new Error(`no worker in registry for name ${definition}`); + throw new Error(`no actor in registry for name ${definition}`); - // Create worker - this.#worker = definition.instantiate(); + // Create actor + this.#actor = definition.instantiate(); - // Start worker - await this.#worker.start( + // Start actor + await this.#actor.start( this.#connDrivers, - workerDriver, + actorDriver, id, name, key, region, ); - this.#workerStartedPromise?.resolve(); - this.#workerStartedPromise = undefined; + this.#actorStartedPromise?.resolve(); + this.#actorStartedPromise = undefined; } } diff --git a/packages/core/src/topologies/standalone/conn-routing-handlers.ts b/packages/core/src/topologies/standalone/conn-routing-handlers.ts index ff0cc87e5..ed7c5685e 100644 --- a/packages/core/src/topologies/standalone/conn-routing-handlers.ts +++ b/packages/core/src/topologies/standalone/conn-routing-handlers.ts @@ -1,10 +1,10 @@ -import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; import { type AnyConn, generateConnId, generateConnToken, -} from "@/worker/connection"; -import * as errors from "@/worker/errors"; +} from "@/actor/connection"; +import * as errors from "@/actor/errors"; import { CONN_DRIVER_GENERIC_HTTP, CONN_DRIVER_GENERIC_SSE, @@ -13,7 +13,7 @@ import { type GenericSseDriverState, type GenericWebSocketDriverState, } from "../common/generic-conn-driver"; -import { ActionContext } from "@/worker/action"; +import { ActionContext } from "@/actor/action"; import type { ConnectWebSocketOpts, ConnectWebSocketOutput, @@ -23,7 +23,7 @@ import type { ActionOpts, ActionOutput, ConnectionHandlers, -} from "@/worker/router-endpoints"; +} from "@/actor/router-endpoints"; import { StandaloneTopology } from "@/mod"; import { logger } from "./log"; diff --git a/packages/core/src/topologies/standalone/log.ts b/packages/core/src/topologies/standalone/log.ts index e150bb4f6..cc1b03491 100644 --- a/packages/core/src/topologies/standalone/log.ts +++ b/packages/core/src/topologies/standalone/log.ts @@ -1,6 +1,6 @@ import { getLogger } from "@/common/log"; -export const LOGGER_NAME = "worker-standalone"; +export const LOGGER_NAME = "actor-standalone"; export function logger() { return getLogger(LOGGER_NAME); diff --git a/packages/core/src/topologies/standalone/topology.ts b/packages/core/src/topologies/standalone/topology.ts index cb0f5cc2b..b3917ae7e 100644 --- a/packages/core/src/topologies/standalone/topology.ts +++ b/packages/core/src/topologies/standalone/topology.ts @@ -1,12 +1,12 @@ -import type { AnyWorkerInstance } from "@/worker/instance"; +import type { AnyActorInstance } from "@/actor/instance"; import { Hono } from "hono"; import { type AnyConn, generateConnId, generateConnToken, -} from "@/worker/connection"; +} from "@/actor/connection"; import { logger } from "./log"; -import * as errors from "@/worker/errors"; +import * as errors from "@/actor/errors"; import { CONN_DRIVER_GENERIC_HTTP, CONN_DRIVER_GENERIC_SSE, @@ -17,7 +17,7 @@ import { type GenericSseDriverState, type GenericWebSocketDriverState, } from "../common/generic-conn-driver"; -import { ActionContext } from "@/worker/action"; +import { ActionContext } from "@/actor/action"; import type { RegistryConfig } from "@/registry/config"; import { createManagerRouter } from "@/manager/router"; import type { @@ -29,26 +29,26 @@ import type { ActionOpts, ActionOutput, ConnectionHandlers, -} from "@/worker/router-endpoints"; +} from "@/actor/router-endpoints"; import { createInlineClientDriver } from "@/inline-client-driver/mod"; import invariant from "invariant"; import { ClientDriver } from "@/client/client"; -import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; import { DriverConfig, RunConfig } from "@/mod"; -class WorkerHandler { +class ActorHandler { /** Will be undefined if not yet loaded. */ - worker?: AnyWorkerInstance; + actor?: AnyActorInstance; - /** Promise that will resolve when the worker is loaded. This should always be awaited before accessing the worker. */ - workerPromise?: PromiseWithResolvers = Promise.withResolvers(); + /** Promise that will resolve when the actor is loaded. This should always be awaited before accessing the actor. */ + actorPromise?: PromiseWithResolvers = Promise.withResolvers(); genericConnGlobalState = new GenericConnGlobalState(); } /** * Standalone topology implementation. - * Manages workers in a single instance without distributed coordination. + * Manages actors in a single instance without distributed coordination. */ export class StandaloneTopology { clientDriver: ClientDriver; @@ -56,7 +56,7 @@ export class StandaloneTopology { #registryConfig: RegistryConfig; #runConfig: RunConfig; - #workers = new Map(); + #actors = new Map(); constructor(registryConfig: RegistryConfig, runConfig: RunConfig) { this.#registryConfig = registryConfig; @@ -111,59 +111,59 @@ export class StandaloneTopology { this.router = router; } - async #getWorker( - workerId: string, - ): Promise<{ handler: WorkerHandler; worker: AnyWorkerInstance }> { - // Load existing worker - let handler = this.#workers.get(workerId); + async #getActor( + actorId: string, + ): Promise<{ handler: ActorHandler; actor: AnyActorInstance }> { + // Load existing actor + let handler = this.#actors.get(actorId); if (handler) { - if (handler.workerPromise) await handler.workerPromise.promise; - if (!handler.worker) throw new Error("Worker should be loaded"); - return { handler, worker: handler.worker }; + if (handler.actorPromise) await handler.actorPromise.promise; + if (!handler.actor) throw new Error("Actor should be loaded"); + return { handler, actor: handler.actor }; } - // Create new worker - logger().debug("creating new worker", { workerId }); + // Create new actor + logger().debug("creating new actor", { actorId }); - // Insert unloaded placeholder in order to prevent race conditions with multiple insertions of the worker - handler = new WorkerHandler(); - this.#workers.set(workerId, handler); + // Insert unloaded placeholder in order to prevent race conditions with multiple insertions of the actor + handler = new ActorHandler(); + this.#actors.set(actorId, handler); - // Load worker meta - const workerMetadata = await this.#runConfig.driver.manager.getForId({ - workerId, + // Load actor meta + const actorMetadata = await this.#runConfig.driver.manager.getForId({ + actorId, }); - if (!workerMetadata) throw new Error(`No worker found for ID ${workerId}`); + if (!actorMetadata) throw new Error(`No actor found for ID ${actorId}`); - // Build worker - const definition = this.#registryConfig.workers[workerMetadata.name]; + // Build actor + const definition = this.#registryConfig.actors[actorMetadata.name]; if (!definition) - throw new Error(`no worker in registry for name ${definition}`); + throw new Error(`no actor in registry for name ${definition}`); - // Create leader worker - const worker = definition.instantiate(); - handler.worker = worker; + // Create leader actor + const actor = definition.instantiate(); + handler.actor = actor; // Create connection drivers const connDrivers = createGenericConnDrivers( handler.genericConnGlobalState, ); - // Start worker - await handler.worker.start( + // Start actor + await handler.actor.start( connDrivers, - this.#runConfig.driver.worker, - workerId, - workerMetadata.name, - workerMetadata.key, + this.#runConfig.driver.actor, + actorId, + actorMetadata.name, + actorMetadata.key, "unknown", ); // Finish - handler.workerPromise?.resolve(); - handler.workerPromise = undefined; + handler.actorPromise?.resolve(); + handler.actorPromise = undefined; - return { handler, worker }; + return { handler, actor }; } #createRoutingHandlers(): ConnRoutingHandler { @@ -171,11 +171,11 @@ export class StandaloneTopology { onConnectWebSocket: async ( opts: ConnectWebSocketOpts, ): Promise => { - const { handler, worker } = await this.#getWorker(opts.workerId); + const { handler, actor } = await this.#getActor(opts.actorId); const connId = generateConnId(); const connToken = generateConnToken(); - const connState = await worker.prepareConn(opts.params, opts.req?.raw); + const connState = await actor.prepareConn(opts.params, opts.req?.raw); let conn: AnyConn | undefined; return { @@ -184,7 +184,7 @@ export class StandaloneTopology { handler.genericConnGlobalState.websockets.set(connId, ws); // Create connection - conn = await worker.createConn( + conn = await actor.createConn( connId, connToken, opts.params, @@ -202,23 +202,23 @@ export class StandaloneTopology { return; } - await worker.processMessage(message, conn); + await actor.processMessage(message, conn); }, onClose: async () => { handler.genericConnGlobalState.websockets.delete(connId); if (conn) { - worker.__removeConn(conn); + actor.__removeConn(conn); } }, }; }, onConnectSse: async (opts: ConnectSseOpts): Promise => { - const { handler, worker } = await this.#getWorker(opts.workerId); + const { handler, actor } = await this.#getActor(opts.actorId); const connId = generateConnId(); const connToken = generateConnToken(); - const connState = await worker.prepareConn(opts.params, opts.req?.raw); + const connState = await actor.prepareConn(opts.params, opts.req?.raw); let conn: AnyConn | undefined; return { @@ -227,7 +227,7 @@ export class StandaloneTopology { handler.genericConnGlobalState.sseStreams.set(connId, stream); // Create connection - conn = await worker.createConn( + conn = await actor.createConn( connId, connToken, opts.params, @@ -241,7 +241,7 @@ export class StandaloneTopology { handler.genericConnGlobalState.sseStreams.delete(connId); if (conn) { - worker.__removeConn(conn); + actor.__removeConn(conn); } }, }; @@ -249,14 +249,14 @@ export class StandaloneTopology { onAction: async (opts: ActionOpts): Promise => { let conn: AnyConn | undefined; try { - const { worker } = await this.#getWorker(opts.workerId); + const { actor } = await this.#getActor(opts.actorId); // Create conn - const connState = await worker.prepareConn( + const connState = await actor.prepareConn( opts.params, opts.req?.raw, ); - conn = await worker.createConn( + conn = await actor.createConn( generateConnId(), generateConnToken(), opts.params, @@ -267,8 +267,8 @@ export class StandaloneTopology { ); // Call action - const ctx = new ActionContext(worker.workerContext!, conn); - const output = await worker.executeAction( + const ctx = new ActionContext(actor.actorContext!, conn); + const output = await actor.executeAction( ctx, opts.actionName, opts.actionArgs, @@ -277,16 +277,16 @@ export class StandaloneTopology { return { output }; } finally { if (conn) { - const { worker } = await this.#getWorker(opts.workerId); - worker.__removeConn(conn); + const { actor } = await this.#getActor(opts.actorId); + actor.__removeConn(conn); } } }, onConnMessage: async (opts: ConnsMessageOpts): Promise => { - const { worker } = await this.#getWorker(opts.workerId); + const { actor } = await this.#getActor(opts.actorId); // Find connection - const conn = worker.conns.get(opts.connId); + const conn = actor.conns.get(opts.connId); if (!conn) { throw new errors.ConnNotFound(opts.connId); } @@ -297,7 +297,7 @@ export class StandaloneTopology { } // Process message - await worker.processMessage(opts.message, conn); + await actor.processMessage(opts.message, conn); }, }; diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 3a2abceec..88a293a7d 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -6,7 +6,7 @@ import pkgJson from "../package.json" with { type: "json" }; import { DriverConfig, UserError } from "./mod"; import { createMemoryDriver } from "./drivers/memory/mod"; import { createRivetManagerDriver } from "./drivers/rivet/mod"; -import { logger } from "./worker/log"; +import { logger } from "./actor/log"; export const VERSION = pkgJson.version; diff --git a/packages/core/src/worker/definition.ts b/packages/core/src/worker/definition.ts deleted file mode 100644 index 31bcaf0c0..000000000 --- a/packages/core/src/worker/definition.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { WorkerConfig, Actions } from "./config"; -import { WorkerInstance } from "./instance"; -import type { WorkerContext } from "./context"; -import type { ActionContext } from "./action"; - -export type AnyWorkerDefinition = WorkerDefinition< - any, - any, - any, - any, - any, - any, - any, - any ->; - -/** - * Extracts the context type from an WorkerDefinition - */ -export type WorkerContextOf = - AD extends WorkerDefinition< - infer S, - infer CP, - infer CS, - infer V, - infer I, - infer AD, - infer DB, - any - > - ? WorkerContext - : never; - -/** - * Extracts the context type from an WorkerDefinition - */ -export type ActionContextOf = - AD extends WorkerDefinition< - infer S, - infer CP, - infer CS, - infer V, - infer I, - infer AD, - infer DB, - any - > - ? ActionContext - : never; - -export class WorkerDefinition< - S, - CP, - CS, - V, - I, - AD, - DB, - R extends Actions, -> { - #config: WorkerConfig; - - constructor(config: WorkerConfig) { - this.#config = config; - } - - get config(): WorkerConfig { - return this.#config; - } - - instantiate(): WorkerInstance { - return new WorkerInstance(this.#config); - } -} diff --git a/packages/core/src/worker/log.ts b/packages/core/src/worker/log.ts deleted file mode 100644 index d093e846a..000000000 --- a/packages/core/src/worker/log.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { getLogger } from "@/common//log"; - -/** Logger for this library. */ -export const RUNTIME_LOGGER_NAME = "worker-runtime"; - -/** Logger used for logs from the worker instance itself. */ -export const WORKER_LOGGER_NAME = "worker"; - -export function logger() { - return getLogger(RUNTIME_LOGGER_NAME); -} - -export function instanceLogger() { - return getLogger(WORKER_LOGGER_NAME); -} - diff --git a/packages/core/src/worker/mod.ts b/packages/core/src/worker/mod.ts deleted file mode 100644 index 3dede08b2..000000000 --- a/packages/core/src/worker/mod.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - type WorkerConfigInput, - WorkerConfigSchema, - type Actions, - type WorkerConfig, -} from "./config"; -import { WorkerDefinition } from "./definition"; - -export type { WorkerContext } from "./context"; -export { UserError, type UserErrorOptions } from "./errors"; -export type { Conn } from "./connection"; -export type { ActionContext } from "./action"; -export type { WorkerConfig, OnConnectOptions } from "./config"; -export type { Encoding } from "@/worker/protocol/serde"; -export type { WorkerKey } from "@/common/utils"; -export type { - WorkerDefinition, - AnyWorkerDefinition, - WorkerContextOf, - ActionContextOf, -} from "./definition"; - -export function worker< - S, - CP, - CS, - V, - I, - AD, - DB, - R extends Actions, ->( - input: WorkerConfigInput, -): WorkerDefinition { - const config = WorkerConfigSchema.parse(input) as WorkerConfig< - S, - CP, - CS, - V, - I, - AD, - DB - >; - return new WorkerDefinition(config); -} diff --git a/packages/core/src/worker/schedule.ts b/packages/core/src/worker/schedule.ts deleted file mode 100644 index b94b6abf7..000000000 --- a/packages/core/src/worker/schedule.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { AnyWorkerInstance } from "./instance"; - -export class Schedule { - #worker: AnyWorkerInstance; - - constructor(worker: AnyWorkerInstance) { - this.#worker = worker; - } - - async after(duration: number, fn: string, ...args: unknown[]) { - await this.#worker.scheduleEvent(Date.now() + duration, fn, args); - } - - async at(timestamp: number, fn: string, ...args: unknown[]) { - await this.#worker.scheduleEvent(timestamp, fn, args); - } -} diff --git a/packages/core/tests/worker-types.test.ts b/packages/core/tests/actor-types.test.ts similarity index 62% rename from packages/core/tests/worker-types.test.ts rename to packages/core/tests/actor-types.test.ts index 268f9cf87..a26e3ec76 100644 --- a/packages/core/tests/worker-types.test.ts +++ b/packages/core/tests/actor-types.test.ts @@ -1,10 +1,10 @@ import { describe, it, expect, expectTypeOf } from "vitest"; -import { WorkerDefinition, type WorkerContextOf } from "@/worker/definition"; -import type { WorkerContext } from "@/worker/context"; +import { ActorDefinition, type ActorContextOf } from "@/actor/definition"; +import type { ActorContext } from "@/actor/context"; -describe("WorkerDefinition", () => { - describe("WorkerContextOf type utility", () => { - it("should correctly extract the context type from an WorkerDefinition", () => { +describe("ActorDefinition", () => { + describe("ActorContextOf type utility", () => { + it("should correctly extract the context type from an ActorDefinition", () => { // Define some simple types for testing interface TestState { counter: number; @@ -35,10 +35,10 @@ describe("WorkerDefinition", () => { client: object; } - // For testing type utilities, we don't need a real worker instance - // We just need a properly typed WorkerDefinition to check against + // For testing type utilities, we don't need a real actor instance + // We just need a properly typed ActorDefinition to check against type TestActions = Record; - const dummyDefinition = {} as WorkerDefinition< + const dummyDefinition = {} as ActorDefinition< TestState, TestConnParams, TestConnState, @@ -50,8 +50,8 @@ describe("WorkerDefinition", () => { >; // Use expectTypeOf to verify our type utility works correctly - expectTypeOf>().toEqualTypeOf< - WorkerContext< + expectTypeOf>().toEqualTypeOf< + ActorContext< TestState, TestConnParams, TestConnState, @@ -67,8 +67,8 @@ describe("WorkerDefinition", () => { value: string; } - expectTypeOf>().not.toEqualTypeOf< - WorkerContext< + expectTypeOf>().not.toEqualTypeOf< + ActorContext< DifferentState, TestConnParams, TestConnState, diff --git a/packages/core/tests/rivet/deployment.test.ts b/packages/core/tests/rivet/deployment.test.ts index f6f311878..83e58aab3 100644 --- a/packages/core/tests/rivet/deployment.test.ts +++ b/packages/core/tests/rivet/deployment.test.ts @@ -8,11 +8,11 @@ // // const __dirname = path.dirname(fileURLToPath(import.meta.url)); // -// // Simple counter worker definition to deploy -// const COUNTER_WORKER = ` -// import { worker, setup } from "@rivetkit/core"; +// // Simple counter actor definition to deploy +// const COUNTER_ACTOR = ` +// import { actor, setup } from "@rivetkit/core"; // -// const counter = worker({ +// const counter = actor({ // state: { count: 0 }, // actions: { // increment: (c, amount) => { @@ -27,7 +27,7 @@ // }); // // export const registry = setup({ -// workers: { counter }, +// actors: { counter }, // }); // // export type Registry = typeof registry; @@ -35,6 +35,6 @@ // // test("Rivet deployment tests", async () => { // const tempFilePath = path.join(os.tmpdir(), `registry-${randomUUID()}`); -// await fs.writeFile(tempFilePath, COUNTER_WORKER); +// await fs.writeFile(tempFilePath, COUNTER_ACTOR); // await deployToRivet("test-registry", tempFilePath, true); // }); diff --git a/packages/core/tests/rivet/driver-tests.test.ts b/packages/core/tests/rivet/driver-tests.test.ts index 500933cca..338f1174b 100644 --- a/packages/core/tests/rivet/driver-tests.test.ts +++ b/packages/core/tests/rivet/driver-tests.test.ts @@ -16,10 +16,10 @@ // } // const endpoint = await deployProjectOnce; // -// // Cleanup workers from previous tests -// await deleteAllWorkers(rivetClientConfig); +// // Cleanup actors from previous tests +// await deleteAllActors(rivetClientConfig); // -// // Flush cache since we manually updated the workers +// // Flush cache since we manually updated the actors // const res = await fetch(`${endpoint}/.test/rivet/flush-cache`, { // method: "POST", // }); @@ -28,26 +28,26 @@ // return { // endpoint, // async cleanup() { -// // This takes time and slows down tests -- it's fine if we leak workers that'll be cleaned up in the next run -// // await deleteAllWorkers(rivetClientConfig); +// // This takes time and slows down tests -- it's fine if we leak actors that'll be cleaned up in the next run +// // await deleteAllActors(rivetClientConfig); // }, // }; // }, // }); // -// async function deleteAllWorkers(clientConfig: RivetClientConfig) { +// async function deleteAllActors(clientConfig: RivetClientConfig) { // // TODO: This is not paginated // -// console.log("Listing workers to delete"); +// console.log("Listing actors to delete"); // const { actors } = await rivetRequest< // void, // { actors: { id: string; tags: Record }[] } // >(clientConfig, "GET", "/actors"); // // for (const actor of actors) { -// if (actor.tags.role !== "worker") continue; +// if (actor.tags.role !== "actor") continue; // -// console.log(`Deleting worker ${actor.id} (${JSON.stringify(actor.tags)})`); +// console.log(`Deleting actor ${actor.id} (${JSON.stringify(actor.tags)})`); // await rivetRequest( // clientConfig, // "DELETE", diff --git a/packages/core/tests/rivet/rivet-deploy.ts b/packages/core/tests/rivet/rivet-deploy.ts index 2193f15cb..6d13874db 100644 --- a/packages/core/tests/rivet/rivet-deploy.ts +++ b/packages/core/tests/rivet/rivet-deploy.ts @@ -132,7 +132,7 @@ // RIVET_PROJECT: project, // RIVET_ENVIRONMENT: environment, // _LOG_LEVEL: "DEBUG", -// _WORKER_LOG_LEVEL: "DEBUG", +// _ACTOR_LOG_LEVEL: "DEBUG", // }, // }, // resources: { @@ -142,9 +142,9 @@ // }, // }, // actors: { -// worker: { -// tags: { role: "worker", framework: "rivetkit" }, -// script: "src/worker.ts", +// actor: { +// tags: { role: "actor", framework: "rivetkit" }, +// script: "src/actor.ts", // }, // }, // }; @@ -165,8 +165,8 @@ // pnpm install --frozen-lockfile // // COPY . . -// # HACK: Remove worker.ts bc file is invalid in Node -// RUN rm src/worker.ts && pnpm build +// # HACK: Remove actor.ts bc file is invalid in Node +// RUN rm src/actor.ts && pnpm build // // RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ // pnpm install --prod --frozen-lockfile @@ -227,12 +227,12 @@ // } // // // Copy project to test directory -// console.log(`Copying project from ${projectPath} to ${tmpDir}/src/workers`); -// const projectDestDir = path.join(tmpDir, "src", "workers"); +// console.log(`Copying project from ${projectPath} to ${tmpDir}/src/actors`); +// const projectDestDir = path.join(tmpDir, "src", "actors"); // await fs.cp(projectPath, projectDestDir, { recursive: true }); // // const serverTsContent = `import { startManager } from "@rivetkit/rivet/manager"; -// import { registry } from "./workers/registry"; +// import { registry } from "./actors/registry"; // // // TODO: Find a cleaner way of flagging an registry as test mode (ideally not in the config itself) // // Force enable test @@ -242,15 +242,15 @@ // `; // await writeFile(tmpDir, "src/server.ts", serverTsContent); // -// const workerTsContent = `import { createWorkerHandler } from "@rivetkit/worker/drivers/rivet"; -// import { registry } from "./workers/registry"; +// const actorTsContent = `import { createActorHandler } from "@rivetkit/actor/drivers/rivet"; +// import { registry } from "./actors/registry"; // // // TODO: Find a cleaner way of flagging an registry as test mode (ideally not in the config itself) // // Force enable test // registry.config.test.enabled = true; // -// export default createWorkerHandler(registry);`; -// await writeFile(tmpDir, "src/worker.ts", workerTsContent); +// export default createActorHandler(registry);`; +// await writeFile(tmpDir, "src/actor.ts", actorTsContent); // // // Build and deploy to Rivet // console.log("Building and deploying to Rivet..."); diff --git a/packages/drivers/file-system/src/actor.ts b/packages/drivers/file-system/src/actor.ts new file mode 100644 index 000000000..a7bc60803 --- /dev/null +++ b/packages/drivers/file-system/src/actor.ts @@ -0,0 +1,52 @@ +import type { ActorDriver, AnyActorInstance } from "@rivetkit/core/driver-helpers"; +import type { FileSystemGlobalState } from "./global-state"; + +export type ActorDriverContext = Record; + +/** + * File System implementation of the Actor Driver + */ +export class FileSystemActorDriver implements ActorDriver { + #state: FileSystemGlobalState; + + constructor(state: FileSystemGlobalState) { + this.#state = state; + } + + /** + * Get the current storage directory path + */ + get storagePath(): string { + return this.#state.storagePath; + } + + getContext(_actorId: string): ActorDriverContext { + return {}; + } + + async readInput(actorId: string): Promise { + return this.#state.readInput(actorId); + } + + async readPersistedData(actorId: string): Promise { + return this.#state.readPersistedData(actorId); + } + + async writePersistedData(actorId: string, data: unknown): Promise { + this.#state.writePersistedData(actorId, data); + + // Save state to disk + await this.#state.saveActorState(actorId); + } + + async setAlarm(actor: AnyActorInstance, timestamp: number): Promise { + const delay = Math.max(0, timestamp - Date.now()); + setTimeout(() => { + actor.onAlarm(); + }, delay); + } + + getDatabase(actorId: string): Promise { + return Promise.resolve(undefined); + } +} diff --git a/packages/drivers/file-system/src/global-state.ts b/packages/drivers/file-system/src/global-state.ts index 33c80c366..e6e3b3aad 100644 --- a/packages/drivers/file-system/src/global-state.ts +++ b/packages/drivers/file-system/src/global-state.ts @@ -1,23 +1,23 @@ import * as fs from "node:fs/promises"; import * as fsSync from "node:fs"; import * as path from "node:path"; -import type { WorkerKey } from "@rivetkit/core"; +import type { ActorKey } from "@rivetkit/core"; import { logger } from "./log"; import { getStoragePath, - getWorkerStoragePath, + getActorStoragePath, ensureDirectoryExists, ensureDirectoryExistsSync, } from "./utils"; import invariant from "invariant"; /** - * Interface representing a worker's state + * Interface representing a actor's state */ -export interface WorkerState { +export interface ActorState { id: string; name: string; - key: WorkerKey; + key: ActorKey; persistedData: unknown; input?: unknown; } @@ -27,7 +27,7 @@ export interface WorkerState { */ export class FileSystemGlobalState { #storagePath: string; - #stateCache: Map = new Map(); + #stateCache: Map = new Map(); constructor(customPath?: string) { // Set up storage directory @@ -35,47 +35,47 @@ export class FileSystemGlobalState { // Ensure storage directories exist synchronously during initialization ensureDirectoryExistsSync(this.#storagePath); - ensureDirectoryExistsSync(`${this.#storagePath}/workers`); + ensureDirectoryExistsSync(`${this.#storagePath}/actors`); - // Load all workers into cache synchronously - this.#loadAllWorkersIntoCache(); + // Load all actors into cache synchronously + this.#loadAllActorsIntoCache(); logger().info("file system loaded", { dir: this.#storagePath, - workerCount: this.#stateCache.size, + actorCount: this.#stateCache.size, }); } /** - * Load all workers into the state cache from the file system + * Load all actors into the state cache from the file system * Only called once during initialization */ - #loadAllWorkersIntoCache(): void { - const workersDir = path.join(this.#storagePath, "workers"); + #loadAllActorsIntoCache(): void { + const actorsDir = path.join(this.#storagePath, "actors"); try { // HACK: Use synchronous filesystem operations for initialization - const workerIds = fsSync.readdirSync(workersDir); + const actorIds = fsSync.readdirSync(actorsDir); - for (const workerId of workerIds) { - const stateFilePath = this.getStateFilePath(workerId); + for (const actorId of actorIds) { + const stateFilePath = this.getStateFilePath(actorId); if (fsSync.existsSync(stateFilePath)) { try { const stateData = fsSync.readFileSync(stateFilePath, "utf8"); - const state = JSON.parse(stateData) as WorkerState; + const state = JSON.parse(stateData) as ActorState; - this.#stateCache.set(workerId, state); + this.#stateCache.set(actorId, state); } catch (error) { logger().error( - "failed to read worker state during cache initialization", - { workerId, error }, + "failed to read actor state during cache initialization", + { actorId, error }, ); } } } } catch (error) { - logger().error("failed to load workers into cache", { error }); + logger().error("failed to load actors into cache", { error }); } } @@ -87,62 +87,62 @@ export class FileSystemGlobalState { } /** - * Get state file path for a worker + * Get state file path for a actor */ - getStateFilePath(workerId: string): string { - const workerDir = getWorkerStoragePath(this.#storagePath, workerId); - return path.join(workerDir, "state.json"); + getStateFilePath(actorId: string): string { + const actorDir = getActorStoragePath(this.#storagePath, actorId); + return path.join(actorDir, "state.json"); } /** - * Load worker state from cache + * Load actor state from cache */ - loadWorkerState(workerId: string): WorkerState { - this.ensureWorkerExists(workerId); + loadActorState(actorId: string): ActorState { + this.ensureActorExists(actorId); - // Get worker state from cache - const cachedWorker = this.#stateCache.get(workerId); - invariant(cachedWorker, `worker state should exist in cache for ${workerId}`); + // Get actor state from cache + const cachedActor = this.#stateCache.get(actorId); + invariant(cachedActor, `actor state should exist in cache for ${actorId}`); - return cachedWorker; + return cachedActor; } - readInput(workerId: string): unknown | undefined { - const state = this.loadWorkerState(workerId); + readInput(actorId: string): unknown | undefined { + const state = this.loadActorState(actorId); return state.input; } /** - * Read persisted data for a worker + * Read persisted data for a actor */ - readPersistedData(workerId: string): unknown | undefined { - const state = this.loadWorkerState(workerId); + readPersistedData(actorId: string): unknown | undefined { + const state = this.loadActorState(actorId); return state.persistedData; } /** - * Write persisted data for a worker + * Write persisted data for a actor */ - writePersistedData(workerId: string, data: unknown): void { - const state = this.loadWorkerState(workerId); + writePersistedData(actorId: string, data: unknown): void { + const state = this.loadActorState(actorId); state.persistedData = data; } /** - * Save worker state to disk + * Save actor state to disk */ - async saveWorkerState(workerId: string): Promise { - const state = this.#stateCache.get(workerId); + async saveActorState(actorId: string): Promise { + const state = this.#stateCache.get(actorId); if (!state) { return; } - const workerDir = getWorkerStoragePath(this.#storagePath, workerId); - const stateFilePath = this.getStateFilePath(workerId); + const actorDir = getActorStoragePath(this.#storagePath, actorId); + const stateFilePath = this.getStateFilePath(actorId); try { - // Create worker directory - await ensureDirectoryExists(workerDir); + // Create actor directory + await ensureDirectoryExists(actorDir); // Create serializable object // State is already in serializable format @@ -154,44 +154,44 @@ export class FileSystemGlobalState { "utf8", ); } catch (error) { - logger().error("failed to save worker state", { workerId, error }); - throw new Error(`Failed to save worker state: ${error}`); + logger().error("failed to save actor state", { actorId, error }); + throw new Error(`Failed to save actor state: ${error}`); } } /** - * Check if a worker exists in the cache + * Check if a actor exists in the cache */ - hasWorker(workerId: string): boolean { - return this.#stateCache.has(workerId); + hasActor(actorId: string): boolean { + return this.#stateCache.has(actorId); } /** - * Ensure a worker exists, throwing if it doesn't + * Ensure a actor exists, throwing if it doesn't */ - ensureWorkerExists(workerId: string): void { - if (!this.hasWorker(workerId)) { - throw new Error(`Worker does not exist for ID: ${workerId}`); + ensureActorExists(actorId: string): void { + if (!this.hasActor(actorId)) { + throw new Error(`Actor does not exist for ID: ${actorId}`); } } /** - * Create a worker + * Create a actor */ - async createWorker( - workerId: string, + async createActor( + actorId: string, name: string, - key: WorkerKey, + key: ActorKey, input?: unknown, ): Promise { - // Check if worker already exists - if (this.hasWorker(workerId)) { - throw new Error(`Worker already exists for ID: ${workerId}`); + // Check if actor already exists + if (this.hasActor(actorId)) { + throw new Error(`Actor already exists for ID: ${actorId}`); } // Create initial state - const newState: WorkerState = { - id: workerId, + const newState: ActorState = { + id: actorId, name, key, persistedData: undefined, @@ -199,30 +199,30 @@ export class FileSystemGlobalState { }; // Cache the state - this.#stateCache.set(workerId, newState); + this.#stateCache.set(actorId, newState); // Save to disk - await this.saveWorkerState(workerId); + await this.saveActorState(actorId); } /** - * Find worker by name and key + * Find actor by name and key */ - findWorkerByNameAndKey(name: string, key: WorkerKey): WorkerState | undefined { - // NOTE: This is a slow implementation that checks each worker individually. + findActorByNameAndKey(name: string, key: ActorKey): ActorState | undefined { + // NOTE: This is a slow implementation that checks each actor individually. // This can be optimized with an index in the future. - return this.findWorker((worker) => { - if (worker.name !== name) return false; + return this.findActor((actor) => { + if (actor.name !== name) return false; - // If worker doesn't have a key, it's not a match - if (!worker.key || worker.key.length !== key.length) { + // If actor doesn't have a key, it's not a match + if (!actor.key || actor.key.length !== key.length) { return false; } - // Check if all elements in key are in worker.key + // Check if all elements in key are in actor.key for (let i = 0; i < key.length; i++) { - if (key[i] !== worker.key[i]) { + if (key[i] !== actor.key[i]) { return false; } } @@ -231,22 +231,22 @@ export class FileSystemGlobalState { } /** - * Find worker by filter function + * Find actor by filter function */ - findWorker(filter: (worker: WorkerState) => boolean): WorkerState | undefined { - for (const worker of this.#stateCache.values()) { - if (filter(worker)) { - return worker; + findActor(filter: (actor: ActorState) => boolean): ActorState | undefined { + for (const actor of this.#stateCache.values()) { + if (filter(actor)) { + return actor; } } return undefined; } /** - * Get all workers from the cache + * Get all actors from the cache */ - getAllWorkers(): WorkerState[] { - // Return all workers from the cache + getAllActors(): ActorState[] { + // Return all actors from the cache return Array.from(this.#stateCache.values()); } } diff --git a/packages/drivers/file-system/src/manager.ts b/packages/drivers/file-system/src/manager.ts index aa4fdc70e..7b037b932 100644 --- a/packages/drivers/file-system/src/manager.ts +++ b/packages/drivers/file-system/src/manager.ts @@ -4,21 +4,21 @@ import type { GetForIdInput, GetWithKeyInput, ManagerDriver, - WorkerOutput, + ActorOutput, CreateInput, } from "@rivetkit/core/driver-helpers"; -import { WorkerAlreadyExists } from "@rivetkit/core/errors"; +import { ActorAlreadyExists } from "@rivetkit/core/errors"; import { logger } from "./log"; import type { FileSystemGlobalState } from "./global-state"; -import { WorkerState } from "./global-state"; +import { ActorState } from "./global-state"; import type { Registry } from "@rivetkit/core"; export class FileSystemManagerDriver implements ManagerDriver { #state: FileSystemGlobalState; // inspector: ManagerInspector = new ManagerInspector(this, { - // getAllWorkers: () => this.#state.getAllWorkers(), - // getAllTypesOfWorkers: () => Object.keys(this.registry.config.workers), + // getAllActors: () => this.#state.getAllActors(), + // getAllTypesOfActors: () => Object.keys(this.registry.config.actors), // }); constructor( @@ -28,23 +28,23 @@ export class FileSystemManagerDriver implements ManagerDriver { this.#state = state; } - async getForId({ workerId }: GetForIdInput): Promise { - // Validate the worker exists - if (!this.#state.hasWorker(workerId)) { + async getForId({ actorId }: GetForIdInput): Promise { + // Validate the actor exists + if (!this.#state.hasActor(actorId)) { return undefined; } try { - // Load worker state - const state = this.#state.loadWorkerState(workerId); + // Load actor state + const state = this.#state.loadActorState(actorId); return { - workerId, + actorId, name: state.name, key: state.key, }; } catch (error) { - logger().error("failed to read worker state", { workerId, error }); + logger().error("failed to read actor state", { actorId, error }); return undefined; } } @@ -52,15 +52,15 @@ export class FileSystemManagerDriver implements ManagerDriver { async getWithKey({ name, key, - }: GetWithKeyInput): Promise { - // Search through all workers to find a match - const worker = this.#state.findWorkerByNameAndKey(name, key); + }: GetWithKeyInput): Promise { + // Search through all actors to find a match + const actor = this.#state.findActorByNameAndKey(name, key); - if (worker) { + if (actor) { return { - workerId: worker.id, + actorId: actor.id, name, - key: worker.key, + key: actor.key, }; } @@ -69,31 +69,31 @@ export class FileSystemManagerDriver implements ManagerDriver { async getOrCreateWithKey( input: GetOrCreateWithKeyInput, - ): Promise { - // First try to get the worker without locking + ): Promise { + // First try to get the actor without locking const getOutput = await this.getWithKey(input); if (getOutput) { return getOutput; } else { - return await this.createWorker(input); + return await this.createActor(input); } } - async createWorker({ name, key, input }: CreateInput): Promise { - // Check if worker with the same name and key already exists - const existingWorker = await this.getWithKey({ name, key }); - if (existingWorker) { - throw new WorkerAlreadyExists(name, key); + async createActor({ name, key, input }: CreateInput): Promise { + // Check if actor with the same name and key already exists + const existingActor = await this.getWithKey({ name, key }); + if (existingActor) { + throw new ActorAlreadyExists(name, key); } - const workerId = crypto.randomUUID(); - await this.#state.createWorker(workerId, name, key, input); + const actorId = crypto.randomUUID(); + await this.#state.createActor(actorId, name, key, input); - // Notify inspector about worker changes - // this.inspector.onWorkersChange(this.#state.getAllWorkers()); + // Notify inspector about actor changes + // this.inspector.onActorsChange(this.#state.getAllActors()); return { - workerId, + actorId, name, key, }; diff --git a/packages/drivers/file-system/src/mod.ts b/packages/drivers/file-system/src/mod.ts index 51587a1c6..aea1f1c21 100644 --- a/packages/drivers/file-system/src/mod.ts +++ b/packages/drivers/file-system/src/mod.ts @@ -1,5 +1,5 @@ export { getStoragePath } from "./utils"; -export { FileSystemWorkerDriver } from "./worker"; +export { FileSystemActorDriver } from "./actor"; export { FileSystemManagerDriver } from "./manager"; export { FileSystemGlobalState } from "./global-state"; diff --git a/packages/drivers/file-system/src/utils.ts b/packages/drivers/file-system/src/utils.ts index 2cbb44d4b..cdfd5baa5 100644 --- a/packages/drivers/file-system/src/utils.ts +++ b/packages/drivers/file-system/src/utils.ts @@ -37,10 +37,10 @@ export function getStoragePath(customPath?: string): string { } /** - * Get worker's storage directory + * Get actor's storage directory */ -export function getWorkerStoragePath(baseDir: string, workerId: string): string { - return path.join(baseDir, "workers", workerId); +export function getActorStoragePath(baseDir: string, actorId: string): string { + return path.join(baseDir, "actors", actorId); } /** diff --git a/packages/drivers/file-system/src/worker.ts b/packages/drivers/file-system/src/worker.ts deleted file mode 100644 index 9b7412da6..000000000 --- a/packages/drivers/file-system/src/worker.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { WorkerDriver, AnyWorkerInstance } from "@rivetkit/core/driver-helpers"; -import type { FileSystemGlobalState } from "./global-state"; - -export type WorkerDriverContext = Record; - -/** - * File System implementation of the Worker Driver - */ -export class FileSystemWorkerDriver implements WorkerDriver { - #state: FileSystemGlobalState; - - constructor(state: FileSystemGlobalState) { - this.#state = state; - } - - /** - * Get the current storage directory path - */ - get storagePath(): string { - return this.#state.storagePath; - } - - getContext(_workerId: string): WorkerDriverContext { - return {}; - } - - async readInput(workerId: string): Promise { - return this.#state.readInput(workerId); - } - - async readPersistedData(workerId: string): Promise { - return this.#state.readPersistedData(workerId); - } - - async writePersistedData(workerId: string, data: unknown): Promise { - this.#state.writePersistedData(workerId, data); - - // Save state to disk - await this.#state.saveWorkerState(workerId); - } - - async setAlarm(worker: AnyWorkerInstance, timestamp: number): Promise { - const delay = Math.max(0, timestamp - Date.now()); - setTimeout(() => { - worker.onAlarm(); - }, delay); - } - - getDatabase(workerId: string): Promise { - return Promise.resolve(undefined); - } -} diff --git a/packages/drivers/file-system/tests/driver-tests.test.ts b/packages/drivers/file-system/tests/driver-tests.test.ts index 93fd1a3b4..02c479157 100644 --- a/packages/drivers/file-system/tests/driver-tests.test.ts +++ b/packages/drivers/file-system/tests/driver-tests.test.ts @@ -3,7 +3,7 @@ import { createTestRuntime, } from "@rivetkit/core/driver-test-suite"; import { - FileSystemWorkerDriver, + FileSystemActorDriver, FileSystemManagerDriver, FileSystemGlobalState, } from "../src/mod"; @@ -23,7 +23,7 @@ runDriverTests({ const fileSystemState = new FileSystemGlobalState(testDir); return { - workerDriver: new FileSystemWorkerDriver(fileSystemState), + actorDriver: new FileSystemActorDriver(fileSystemState), managerDriver: new FileSystemManagerDriver(registry, fileSystemState), async cleanup() { await fs.rmdir(testDir, { recursive: true }); diff --git a/packages/drivers/redis/src/actor.ts b/packages/drivers/redis/src/actor.ts new file mode 100644 index 000000000..e6ba9917d --- /dev/null +++ b/packages/drivers/redis/src/actor.ts @@ -0,0 +1,51 @@ +import type { ActorDriver, AnyActorInstance } from "@rivetkit/core/driver-helpers"; +import type Redis from "ioredis"; +import { KEYS } from "./keys"; + +export interface ActorDriverContext { + redis: Redis; +} + +export class RedisActorDriver implements ActorDriver { + #redis: Redis; + + constructor(redis: Redis) { + this.#redis = redis; + } + + getContext(_actorId: string): ActorDriverContext { + return { redis: this.#redis }; + } + + async readInput(actorId: string): Promise { + // TODO: We should read this all in one batch, this will require multiple RTT to Redis + const data = await this.#redis.get(KEYS.ACTOR.input(actorId)); + if (data !== null) return JSON.parse(data); + return undefined; + } + + async readPersistedData(actorId: string): Promise { + const data = await this.#redis.get(KEYS.ACTOR.persistedData(actorId)); + if (data !== null) return JSON.parse(data); + return undefined; + } + + async writePersistedData(actorId: string, data: unknown): Promise { + await this.#redis.set( + KEYS.ACTOR.persistedData(actorId), + JSON.stringify(data), + ); + } + + async setAlarm(actor: AnyActorInstance, timestamp: number): Promise { + const delay = Math.max(timestamp - Date.now(), 0); + setTimeout(() => { + actor.onAlarm(); + }, delay); + } + + getDatabase(actorId: string): Promise { + // Redis does not have a database concept like other drivers, so we return undefined + return Promise.resolve(undefined); + } +} diff --git a/packages/drivers/redis/src/coordinate.ts b/packages/drivers/redis/src/coordinate.ts index d20c4781e..bdcec0e56 100644 --- a/packages/drivers/redis/src/coordinate.ts +++ b/packages/drivers/redis/src/coordinate.ts @@ -1,10 +1,10 @@ import type { AttemptAcquireLease, ExtendLeaseOutput, - GetWorkerLeaderOutput, + GetActorLeaderOutput, NodeMessageCallback, CoordinateDriver, - StartWorkerAndAcquireLeaseOutput, + StartActorAndAcquireLeaseOutput, } from "@rivetkit/core/driver-helpers"; import type Redis from "ioredis"; import { KEYS, PUBSUB } from "./keys"; @@ -13,21 +13,21 @@ import dedent from "dedent"; // Define custom commands for ioredis declare module "ioredis" { interface RedisCommander { - workerPeerAcquireLease( + actorPeerAcquireLease( nodeKey: string, nodeId: string, leaseDuration: number, ): Promise; - workerPeerExtendLease( + actorPeerExtendLease( nodeKey: string, nodeId: string, leaseDuration: number, ): Promise; - workerPeerReleaseLease(nodeKey: string, nodeId: string): Promise; + actorPeerReleaseLease(nodeKey: string, nodeId: string): Promise; } interface ChainableCommander { - workerPeerAcquireLease( + actorPeerAcquireLease( nodeKey: string, nodeId: string, leaseDuration: number, @@ -66,35 +66,35 @@ export class RedisCoordinateDriver implements CoordinateDriver { await this.#redis.publish(PUBSUB.node(targetNodeId), message); } - async getWorkerLeader(workerId: string): Promise { + async getActorLeader(actorId: string): Promise { // Get current leader from Redis const [initialized, nodeId] = await this.#redis.mget([ - KEYS.WORKER.initialized(workerId), - KEYS.WORKER.LEASE.node(workerId), + KEYS.ACTOR.initialized(actorId), + KEYS.ACTOR.LEASE.node(actorId), ]); if (!initialized) { - return { worker: undefined }; + return { actor: undefined }; } return { - worker: { + actor: { leaderNodeId: nodeId || undefined, }, }; } - async startWorkerAndAcquireLease( - workerId: string, + async startActorAndAcquireLease( + actorId: string, selfNodeId: string, leaseDuration: number, - ): Promise { - // Execute multi to get worker info and attempt to acquire lease in a single operation + ): Promise { + // Execute multi to get actor info and attempt to acquire lease in a single operation const execRes = await this.#redis .multi() - .mget([KEYS.WORKER.initialized(workerId), KEYS.WORKER.metadata(workerId)]) - .workerPeerAcquireLease( - KEYS.WORKER.LEASE.node(workerId), + .mget([KEYS.ACTOR.initialized(actorId), KEYS.ACTOR.metadata(actorId)]) + .actorPeerAcquireLease( + KEYS.ACTOR.LEASE.node(actorId), selfNodeId, leaseDuration, ) @@ -113,15 +113,15 @@ export class RedisCoordinateDriver implements CoordinateDriver { const leaderNodeId = leaseRes as unknown as string; if (!initialized) { - return { worker: undefined }; + return { actor: undefined }; } // Parse metadata if present - if (!metadataRaw) throw new Error("Worker should have metadata if initialized."); + if (!metadataRaw) throw new Error("Actor should have metadata if initialized."); const metadata = JSON.parse(metadataRaw); return { - worker: { + actor: { name: metadata.name, key: metadata.key, leaderNodeId, @@ -130,12 +130,12 @@ export class RedisCoordinateDriver implements CoordinateDriver { } async extendLease( - workerId: string, + actorId: string, selfNodeId: string, leaseDuration: number, ): Promise { - const res = await this.#redis.workerPeerExtendLease( - KEYS.WORKER.LEASE.node(workerId), + const res = await this.#redis.actorPeerExtendLease( + KEYS.ACTOR.LEASE.node(actorId), selfNodeId, leaseDuration, ); @@ -146,12 +146,12 @@ export class RedisCoordinateDriver implements CoordinateDriver { } async attemptAcquireLease( - workerId: string, + actorId: string, selfNodeId: string, leaseDuration: number, ): Promise { - const newLeaderNodeId = await this.#redis.workerPeerAcquireLease( - KEYS.WORKER.LEASE.node(workerId), + const newLeaderNodeId = await this.#redis.actorPeerAcquireLease( + KEYS.ACTOR.LEASE.node(actorId), selfNodeId, leaseDuration, ); @@ -161,16 +161,16 @@ export class RedisCoordinateDriver implements CoordinateDriver { }; } - async releaseLease(workerId: string, nodeId: string): Promise { - await this.#redis.workerPeerReleaseLease( - KEYS.WORKER.LEASE.node(workerId), + async releaseLease(actorId: string, nodeId: string): Promise { + await this.#redis.actorPeerReleaseLease( + KEYS.ACTOR.LEASE.node(actorId), nodeId, ); } #defineRedisScripts() { // Add custom Lua script commands to Redis - this.#redis.defineCommand("workerPeerAcquireLease", { + this.#redis.defineCommand("actorPeerAcquireLease", { numberOfKeys: 1, lua: dedent` -- Get the current value of the key @@ -189,7 +189,7 @@ export class RedisCoordinateDriver implements CoordinateDriver { `, }); - this.#redis.defineCommand("workerPeerExtendLease", { + this.#redis.defineCommand("actorPeerExtendLease", { numberOfKeys: 1, lua: dedent` -- Return 0 if an entry exists with a different lease holder @@ -205,7 +205,7 @@ export class RedisCoordinateDriver implements CoordinateDriver { `, }); - this.#redis.defineCommand("workerPeerReleaseLease", { + this.#redis.defineCommand("actorPeerReleaseLease", { numberOfKeys: 1, lua: dedent` -- Only remove the entry for this lock value diff --git a/packages/drivers/redis/src/keys.ts b/packages/drivers/redis/src/keys.ts index 11bb81e62..db16aaa2e 100644 --- a/packages/drivers/redis/src/keys.ts +++ b/packages/drivers/redis/src/keys.ts @@ -1,17 +1,17 @@ export const KEYS = { - WORKER: { + ACTOR: { // KEY - initialized: (workerId: string) => `worker:${workerId}:initialized`, + initialized: (actorId: string) => `actor:${actorId}:initialized`, LEASE: { // KEY (expire) = node ID - node: (workerId: string) => `worker:${workerId}:lease:node`, + node: (actorId: string) => `actor:${actorId}:lease:node`, }, // KEY - metadata: (workerId: string) => `worker:${workerId}:metadata`, + metadata: (actorId: string) => `actor:${actorId}:metadata`, // KEY - persistedData: (workerId: string) => `worker:${workerId}:persisted_data`, + persistedData: (actorId: string) => `actor:${actorId}:persisted_data`, // KEY - input: (workerId: string) => `worker:${workerId}:input`, + input: (actorId: string) => `actor:${actorId}:input`, }, }; diff --git a/packages/drivers/redis/src/manager.ts b/packages/drivers/redis/src/manager.ts index 784181a51..fb631e1d0 100644 --- a/packages/drivers/redis/src/manager.ts +++ b/packages/drivers/redis/src/manager.ts @@ -1,18 +1,18 @@ import type { CreateInput, - WorkerOutput, + ActorOutput, GetForIdInput, GetOrCreateWithKeyInput, GetWithKeyInput, ManagerDriver, } from "@rivetkit/core/driver-helpers"; -import { WorkerAlreadyExists } from "@rivetkit/core/errors"; +import { ActorAlreadyExists } from "@rivetkit/core/errors"; import type Redis from "ioredis"; import * as crypto from "node:crypto"; import { KEYS } from "./keys"; import type { Registry } from "@rivetkit/core"; -interface Worker { +interface Actor { id: string; name: string; key: string[]; @@ -26,18 +26,18 @@ export class RedisManagerDriver implements ManagerDriver { #registry?: Registry; // inspector: ManagerInspector = new ManagerInspector(this, { - // getAllWorkers: () => { - // // Create a function that returns an array of workers directly + // getAllActors: () => { + // // Create a function that returns an array of actors directly // // Not returning a Promise since the ManagerInspector expects a synchronous function - // const workers: Worker[] = []; + // const actors: Actor[] = []; // // // Return empty array since we can't do async operations here - // // The actual data will be fetched when needed by calling getAllWorkers() manually - // return workers; + // // The actual data will be fetched when needed by calling getAllActors() manually + // return actors; // }, - // getAllTypesOfWorkers: () => { + // getAllTypesOfActors: () => { // if (!this.#registry) return []; - // return Object.keys(this.#registry.config.workers); + // return Object.keys(this.#registry.config.actors); // }, // }); @@ -46,11 +46,11 @@ export class RedisManagerDriver implements ManagerDriver { this.#registry = registry; } - async getForId({ workerId }: GetForIdInput): Promise { + async getForId({ actorId }: GetForIdInput): Promise { // Get metadata from Redis - const metadataStr = await this.#redis.get(KEYS.WORKER.metadata(workerId)); + const metadataStr = await this.#redis.get(KEYS.ACTOR.metadata(actorId)); - // If the worker doesn't exist, return undefined + // If the actor doesn't exist, return undefined if (!metadataStr) { return undefined; } @@ -59,7 +59,7 @@ export class RedisManagerDriver implements ManagerDriver { const { name, key } = metadata; return { - workerId, + actorId, name, key, }; @@ -68,98 +68,98 @@ export class RedisManagerDriver implements ManagerDriver { async getWithKey({ name, key, - }: GetWithKeyInput): Promise { - // Since keys are 1:1 with worker IDs, we can directly look up by key - const lookupKey = this.#generateWorkerKeyRedisKey(name, key); - const workerId = await this.#redis.get(lookupKey); + }: GetWithKeyInput): Promise { + // Since keys are 1:1 with actor IDs, we can directly look up by key + const lookupKey = this.#generateActorKeyRedisKey(name, key); + const actorId = await this.#redis.get(lookupKey); - if (!workerId) { + if (!actorId) { return undefined; } - return this.getForId({ workerId }); + return this.getForId({ actorId }); } async getOrCreateWithKey( input: GetOrCreateWithKeyInput, - ): Promise { + ): Promise { // TODO: Prevent race condition here const getOutput = await this.getWithKey(input); if (getOutput) { return getOutput; } else { - return await this.createWorker(input); + return await this.createActor(input); } } - async createWorker({ name, key, input }: CreateInput): Promise { - // Check if worker with the same name and key already exists - const existingWorker = await this.getWithKey({ name, key }); - if (existingWorker) { - throw new WorkerAlreadyExists(name, key); + async createActor({ name, key, input }: CreateInput): Promise { + // Check if actor with the same name and key already exists + const existingActor = await this.getWithKey({ name, key }); + if (existingActor) { + throw new ActorAlreadyExists(name, key); } - const workerId = crypto.randomUUID(); - const workerKeyRedisKey = this.#generateWorkerKeyRedisKey(name, key); + const actorId = crypto.randomUUID(); + const actorKeyRedisKey = this.#generateActorKeyRedisKey(name, key); // Use a transaction to ensure all operations are atomic const pipeline = this.#redis.multi(); - // Store basic worker information - pipeline.set(KEYS.WORKER.initialized(workerId), "1"); - pipeline.set(KEYS.WORKER.metadata(workerId), JSON.stringify({ name, key })); - pipeline.set(KEYS.WORKER.input(workerId), JSON.stringify(input)); + // Store basic actor information + pipeline.set(KEYS.ACTOR.initialized(actorId), "1"); + pipeline.set(KEYS.ACTOR.metadata(actorId), JSON.stringify({ name, key })); + pipeline.set(KEYS.ACTOR.input(actorId), JSON.stringify(input)); - // Create direct lookup by name+key -> workerId - pipeline.set(workerKeyRedisKey, workerId); + // Create direct lookup by name+key -> actorId + pipeline.set(actorKeyRedisKey, actorId); // Execute all commands atomically await pipeline.exec(); - // Notify inspector of worker creation - // this.inspector.onWorkersChange([ + // Notify inspector of actor creation + // this.inspector.onActorsChange([ // { - // id: workerId, + // id: actorId, // name, // key, // }, // ]); return { - workerId, + actorId, name, key, }; } - // Helper method to get all workers (for inspector) - private async getAllWorkers(): Promise { + // Helper method to get all actors (for inspector) + private async getAllActors(): Promise { const keys = await this.#redis.keys( - KEYS.WORKER.metadata("*").replace(/:metadata$/, ""), + KEYS.ACTOR.metadata("*").replace(/:metadata$/, ""), ); - const workerIds = keys.map((key) => key.split(":")[1]); + const actorIds = keys.map((key) => key.split(":")[1]); - const workers: Worker[] = []; - for (const workerId of workerIds) { - const metadataStr = await this.#redis.get(KEYS.WORKER.metadata(workerId)); + const actors: Actor[] = []; + for (const actorId of actorIds) { + const metadataStr = await this.#redis.get(KEYS.ACTOR.metadata(actorId)); if (metadataStr) { const metadata = JSON.parse(metadataStr); - workers.push({ - id: workerId, + actors.push({ + id: actorId, name: metadata.name, key: metadata.key || [], }); } } - return workers; + return actors; } - // Generate a Redis key for looking up a worker by name+key - #generateWorkerKeyRedisKey(name: string, key: string[]): string { - // Base prefix for worker key lookups - let redisKey = `worker_by_key:${this.#escapeRedisKey(name)}`; + // Generate a Redis key for looking up a actor by name+key + #generateActorKeyRedisKey(name: string, key: string[]): string { + // Base prefix for actor key lookups + let redisKey = `actor_by_key:${this.#escapeRedisKey(name)}`; // Add each key component with proper escaping if (key.length > 0) { diff --git a/packages/drivers/redis/src/mod.ts b/packages/drivers/redis/src/mod.ts index 47887de25..80cf3d092 100644 --- a/packages/drivers/redis/src/mod.ts +++ b/packages/drivers/redis/src/mod.ts @@ -1,3 +1,3 @@ -export { RedisWorkerDriver } from "./worker"; +export { RedisActorDriver } from "./actor"; export { RedisManagerDriver } from "./manager"; -export { RedisCoordinateDriver } from "./coordinate"; \ No newline at end of file +export { RedisCoordinateDriver } from "./coordinate"; diff --git a/packages/drivers/redis/src/worker.ts b/packages/drivers/redis/src/worker.ts deleted file mode 100644 index 1c32c80ce..000000000 --- a/packages/drivers/redis/src/worker.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { WorkerDriver, AnyWorkerInstance } from "@rivetkit/core/driver-helpers"; -import type Redis from "ioredis"; -import { KEYS } from "./keys"; - -export interface WorkerDriverContext { - redis: Redis; -} - -export class RedisWorkerDriver implements WorkerDriver { - #redis: Redis; - - constructor(redis: Redis) { - this.#redis = redis; - } - - getContext(_workerId: string): WorkerDriverContext { - return { redis: this.#redis }; - } - - async readInput(workerId: string): Promise { - // TODO: We should read this all in one batch, this will require multiple RTT to Redis - const data = await this.#redis.get(KEYS.WORKER.input(workerId)); - if (data !== null) return JSON.parse(data); - return undefined; - } - - async readPersistedData(workerId: string): Promise { - const data = await this.#redis.get(KEYS.WORKER.persistedData(workerId)); - if (data !== null) return JSON.parse(data); - return undefined; - } - - async writePersistedData(workerId: string, data: unknown): Promise { - await this.#redis.set( - KEYS.WORKER.persistedData(workerId), - JSON.stringify(data), - ); - } - - async setAlarm(worker: AnyWorkerInstance, timestamp: number): Promise { - const delay = Math.max(timestamp - Date.now(), 0); - setTimeout(() => { - worker.onAlarm(); - }, delay); - } - - getDatabase(workerId: string): Promise { - // Redis does not have a database concept like other drivers, so we return undefined - return Promise.resolve(undefined); - } -} diff --git a/packages/drivers/redis/tests/driver-tests.test.ts b/packages/drivers/redis/tests/driver-tests.test.ts index 5c48c318f..eb157426c 100644 --- a/packages/drivers/redis/tests/driver-tests.test.ts +++ b/packages/drivers/redis/tests/driver-tests.test.ts @@ -4,7 +4,7 @@ // createTestRuntime, //} from "@rivetkit/core/driver-test-suite"; //import { -// RedisWorkerDriver, +// RedisActorDriver, // RedisCoordinateDriver, // RedisManagerDriver, //} from "../src/mod"; @@ -87,7 +87,7 @@ // }); // // return { -// workerDriver: new RedisWorkerDriver(redisClient), +// actorDriver: new RedisActorDriver(redisClient), // managerDriver: new RedisManagerDriver(redisClient, registry), // coordinateDriver: new RedisCoordinateDriver(redisClient), // async cleanup() { diff --git a/packages/frameworks/framework-base/lib/mod.ts b/packages/frameworks/framework-base/lib/mod.ts index e40111b03..83648f0e5 100644 --- a/packages/frameworks/framework-base/lib/mod.ts +++ b/packages/frameworks/framework-base/lib/mod.ts @@ -1,67 +1,67 @@ import { Derived, Effect, Store, type Updater } from "@tanstack/store"; -import type { AnyWorkerDefinition, Registry } from "@rivetkit/core"; +import type { AnyActorDefinition, Registry } from "@rivetkit/core"; import type { Client, - ExtractWorkersFromRegistry, - WorkerConn, - WorkerHandle, + ExtractActorsFromRegistry, + ActorConn, + ActorHandle, } from "@rivetkit/core/client"; -// biome-ignore lint/suspicious/noExplicitAny: its a generic worker registry -export type AnyWorkerRegistry = Registry; +// biome-ignore lint/suspicious/noExplicitAny: its a generic actor registry +export type AnyActorRegistry = Registry; -interface WorkerStateReference { +interface ActorStateReference { /** - * The unique identifier for the worker. - * This is a hash generated from the worker's options. - * It is used to identify the worker instance in the store. + * The unique identifier for the actor. + * This is a hash generated from the actor's options. + * It is used to identify the actor instance in the store. * @internal */ hash: string; /** - * The state of the worker, derived from the store. - * This includes the worker's connection and handle. + * The state of the actor, derived from the store. + * This includes the actor's connection and handle. */ - handle: WorkerHandle | null; + handle: ActorHandle | null; /** - * The connection to the worker. - * This is used to communicate with the worker in realtime. + * The connection to the actor. + * This is used to communicate with the actor in realtime. */ - connection: WorkerConn | null; + connection: ActorConn | null; /** - * Whether the worker is enabled. + * Whether the actor is enabled. */ isConnected?: boolean; /** - * Whether the worker is currently connecting, indicating that a connection attempt is in progress. + * Whether the actor is currently connecting, indicating that a connection attempt is in progress. */ isConnecting?: boolean; /** - * Whether there was an error connecting to the worker. + * Whether there was an error connecting to the actor. */ isError?: boolean; /** - * The error that occurred while trying to connect to the worker, if any. + * The error that occurred while trying to connect to the actor, if any. */ error: Error | null; /** - * Options for the worker, including its name, key, parameters, and whether it is enabled. + * Options for the actor, including its name, key, parameters, and whether it is enabled. */ opts: { name: keyof AD; /** - * Unique key for the worker instance. + * Unique key for the actor instance. * This can be a string or an array of strings to create multiple instances. * @example "abc" or ["abc", "def"] */ key: string | string[]; /** - * Parameters for the worker. - * These are additional options that can be passed to the worker. + * Parameters for the actor. + * These are additional options that can be passed to the actor. */ params?: Record; /** - * Whether the worker is enabled. + * Whether the actor is enabled. * Defaults to true. */ enabled?: boolean; @@ -69,59 +69,59 @@ interface WorkerStateReference { } interface InternalRivetKitStore< - Registry extends AnyWorkerRegistry, - Workers extends ExtractWorkersFromRegistry, + Registry extends AnyActorRegistry, + Actors extends ExtractActorsFromRegistry, > { - workers: Record>; + actors: Record>; } /** - * Options for configuring a worker in RivetKit. + * Options for configuring a actor in RivetKit. */ -export interface WorkerOptions< - Registry extends AnyWorkerRegistry, - WorkerName extends keyof ExtractWorkersFromRegistry, +export interface ActorOptions< + Registry extends AnyActorRegistry, + ActorName extends keyof ExtractActorsFromRegistry, > { /** - * Typesafe name of the worker. - * This should match the worker's name in the app's worker definitions. + * Typesafe name of the actor. + * This should match the actor's name in the app's actor definitions. * @example "chatRoom" */ - name: WorkerName; + name: ActorName; /** - * Unique key for the worker instance. + * Unique key for the actor instance. * This can be a string or an array of strings to create multiple instances. * @example "abc" or ["abc", "def"] */ key: string | string[]; /** - * Parameters for the worker. + * Parameters for the actor. */ - params?: Registry[ExtractWorkersFromRegistry]["params"]; + params?: Registry[ExtractActorsFromRegistry]["params"]; /** - * Whether the worker is enabled. + * Whether the actor is enabled. * Defaults to true. */ enabled?: boolean; } -// biome-ignore lint/suspicious/noExplicitAny: worker name can be anything -export type AnyWorkerOptions = WorkerOptions; +// biome-ignore lint/suspicious/noExplicitAny: actor name can be anything +export type AnyActorOptions = ActorOptions; -export interface CreateRivetKitOptions { - // biome-ignore lint/suspicious/noExplicitAny: worker name can be anything - hashFunction?: (opts: WorkerOptions) => string; +export interface CreateRivetKitOptions { + // biome-ignore lint/suspicious/noExplicitAny: actor name can be anything + hashFunction?: (opts: ActorOptions) => string; } export function createRivetKit< - Registry extends AnyWorkerRegistry, - Workers extends ExtractWorkersFromRegistry, - WorkerNames extends keyof Workers, + Registry extends AnyActorRegistry, + Actors extends ExtractActorsFromRegistry, + ActorNames extends keyof Actors, >(client: Client, opts: CreateRivetKitOptions = {}) { - type RivetKitStore = InternalRivetKitStore; + type RivetKitStore = InternalRivetKitStore; const store = new Store({ - workers: {}, + actors: {}, }); const hash = opts.hashFunction || defaultHashFunction; @@ -129,10 +129,10 @@ export function createRivetKit< const cache = new Map< string, { - state: Derived; + state: Derived; key: string; mount: () => void; - setState: (set: Updater) => void; + setState: (set: Updater) => void; create: () => void; addEventListener?: ( event: string, @@ -142,8 +142,8 @@ export function createRivetKit< } >(); - function getOrCreateWorker( - opts: WorkerOptions, + function getOrCreateActor( + opts: ActorOptions, ) { const key = hash(opts); const cached = cache.get(key); @@ -151,9 +151,9 @@ export function createRivetKit< return { ...cached, state: cached.state as Derived< - Omit & { - handle: WorkerHandle | null; - connection: WorkerConn | null; + Omit & { + handle: ActorHandle | null; + connection: ActorConn | null; } >, }; @@ -161,19 +161,19 @@ export function createRivetKit< const derived = new Derived({ fn: ({ currDepVals: [store] }) => { - return store.workers[key]; + return store.actors[key]; }, deps: [store], }); function create() { - async function createWorkerConnection() { - const worker = store.state.workers[key]; + async function createActorConnection() { + const actor = store.state.actors[key]; try { const handle = client.getOrCreate( - worker.opts.name as string, - worker.opts.key, - worker.opts.params, + actor.opts.name as string, + actor.opts.key, + actor.opts.params, ); const connection = handle.connect(); @@ -182,14 +182,14 @@ export function createRivetKit< store.setState((prev) => { return { ...prev, - workers: { - ...prev.workers, + actors: { + ...prev.actors, [key]: { - ...prev.workers[key], + ...prev.actors[key], isConnected: true, isConnecting: false, - handle: handle as WorkerHandle, - connection: connection as WorkerConn, + handle: handle as ActorHandle, + connection: connection as ActorConn, isError: false, error: null, }, @@ -200,10 +200,10 @@ export function createRivetKit< store.setState((prev) => { return { ...prev, - workers: { - ...prev.workers, + actors: { + ...prev.actors, [key]: { - ...prev.workers[key], + ...prev.actors[key], isError: true, isConnecting: false, error: error as Error, @@ -215,10 +215,10 @@ export function createRivetKit< } store.setState((prev) => { - prev.workers[key].isConnecting = true; - prev.workers[key].isError = false; - prev.workers[key].error = null; - createWorkerConnection(); + prev.actors[key].isConnecting = true; + prev.actors[key].isError = false; + prev.actors[key].error = null; + createActorConnection(); return prev; }); } @@ -228,18 +228,18 @@ export function createRivetKit< fn: () => { // check if prev state is different from current state // do a shallow comparison - const worker = store.state.workers[key]; + const actor = store.state.actors[key]; const isSame = - JSON.stringify(store.prevState.workers[key].opts) === - JSON.stringify(store.state.workers[key].opts); + JSON.stringify(store.prevState.actors[key].opts) === + JSON.stringify(store.state.actors[key].opts); if ( isSame && - !worker.isConnected && - !worker.isConnecting && - !worker.isError && - worker.opts.enabled + !actor.isConnected && + !actor.isConnecting && + !actor.isError && + actor.opts.enabled ) { create(); } @@ -248,13 +248,13 @@ export function createRivetKit< }); store.setState((prev) => { - if (prev.workers[key]) { + if (prev.actors[key]) { return prev; } return { ...prev, - workers: { - ...prev.workers, + actors: { + ...prev.actors, [key]: { hash: key, isConnected: false, @@ -269,25 +269,25 @@ export function createRivetKit< }; }); - function setState(updater: Updater) { + function setState(updater: Updater) { store.setState((prev) => { - const worker = prev.workers[key]; - if (!worker) { - throw new Error(`Worker with key "${key}" does not exist.`); + const actor = prev.actors[key]; + if (!actor) { + throw new Error(`Actor with key "${key}" does not exist.`); } - let newState: RivetKitStore["workers"][string]; + let newState: RivetKitStore["actors"][string]; if (typeof updater === "function") { - newState = updater(worker); + newState = updater(actor); } else { - // If updater is a direct value, we assume it replaces the entire worker state + // If updater is a direct value, we assume it replaces the entire actor state newState = updater; } return { ...prev, - workers: { - ...prev.workers, + actors: { + ...prev.actors, [key]: newState, }, }; @@ -317,9 +317,9 @@ export function createRivetKit< mount, setState, state: derived as Derived< - Omit & { - handle: WorkerHandle | null; - connection: WorkerConn | null; + Omit & { + handle: ActorHandle | null; + connection: ActorConn | null; } >, create, @@ -328,11 +328,11 @@ export function createRivetKit< } return { - getOrCreateWorker, + getOrCreateActor, store, }; } -function defaultHashFunction({ name, key, params }: AnyWorkerOptions) { +function defaultHashFunction({ name, key, params }: AnyActorOptions) { return JSON.stringify({ name, key, params }); } diff --git a/packages/frameworks/react/src/mod.tsx b/packages/frameworks/react/src/mod.tsx index ad32e255b..002e48e46 100644 --- a/packages/frameworks/react/src/mod.tsx +++ b/packages/frameworks/react/src/mod.tsx @@ -1,36 +1,36 @@ import { useStore } from "@tanstack/react-store"; import { - type AnyWorkerRegistry, + type AnyActorRegistry, type CreateRivetKitOptions, - type WorkerOptions, + type ActorOptions, createRivetKit as createVanillaRivetKit, } from "@rivetkit/framework-base"; -import type { Client, ExtractWorkersFromRegistry } from "@rivetkit/core/client"; +import type { Client, ExtractActorsFromRegistry } from "@rivetkit/core/client"; import { useEffect } from "react"; export { createClient } from "@rivetkit/core/client"; -export function createRivetKit( +export function createRivetKit( client: Client, opts: CreateRivetKitOptions = {}, ) { - const { getOrCreateWorker } = createVanillaRivetKit< + const { getOrCreateActor } = createVanillaRivetKit< Registry, - ExtractWorkersFromRegistry, - keyof ExtractWorkersFromRegistry + ExtractActorsFromRegistry, + keyof ExtractActorsFromRegistry >(client, opts); /** - * Hook to connect to a worker and retrieve its state. Using this hook with the same options - * will return the same worker instance. This simplifies passing around the worker state in your components. - * It also provides a method to listen for events emitted by the worker. - * @param opts - Options for the worker, including its name, key, and parameters. - * @returns An object containing the worker's state and a method to listen for events. + * Hook to connect to a actor and retrieve its state. Using this hook with the same options + * will return the same actor instance. This simplifies passing around the actor state in your components. + * It also provides a method to listen for events emitted by the actor. + * @param opts - Options for the actor, including its name, key, and parameters. + * @returns An object containing the actor's state and a method to listen for events. */ - function useWorker< - WorkerName extends keyof ExtractWorkersFromRegistry, - >(opts: WorkerOptions) { - const { mount, setState, state } = getOrCreateWorker(opts); + function useActor< + ActorName extends keyof ExtractActorsFromRegistry, + >(opts: ActorOptions) { + const { mount, setState, state } = getOrCreateActor(opts); useEffect(() => { setState((prev) => { @@ -46,14 +46,14 @@ export function createRivetKit( return mount(); }, [mount]); - const workerState = useStore(state) || {}; + const actorState = useStore(state) || {}; /** - * Hook to listen for events emitted by the worker. - * This hook allows you to subscribe to specific events emitted by the worker and execute a handler function + * Hook to listen for events emitted by the actor. + * This hook allows you to subscribe to specific events emitted by the actor and execute a handler function * when the event occurs. - * It uses the `useEffect` hook to set up the event listener when the worker connection is established. - * It cleans up the listener when the component unmounts or when the worker connection changes. + * It uses the `useEffect` hook to set up the event listener when the actor connection is established. + * It cleans up the listener when the component unmounts or when the actor connection changes. * @param eventName The name of the event to listen for. * @param handler The function to call when the event is emitted. */ @@ -64,19 +64,19 @@ export function createRivetKit( ) => { // biome-ignore lint/correctness/useExhaustiveDependencies: it's okay to not include all dependencies here useEffect(() => { - if (!workerState?.connection) return; + if (!actorState?.connection) return; - const connection = workerState.connection; + const connection = actorState.connection; return connection.on(eventName, handler); - }, [workerState.connection, workerState.isConnected, eventName, handler]); + }, [actorState.connection, actorState.isConnected, eventName, handler]); }; return { - ...workerState, + ...actorState, useEvent, }; } return { - useWorker, + useActor, }; } diff --git a/packages/misc/docs-middleware/tsconfig.json b/packages/misc/docs-middleware/tsconfig.json index ce321858b..6c50ecc88 100644 --- a/packages/misc/docs-middleware/tsconfig.json +++ b/packages/misc/docs-middleware/tsconfig.json @@ -40,5 +40,5 @@ "skipLibCheck": true }, "exclude": ["test"], - "include": ["worker-configuration.d.ts", "src/**/*.ts"] + "include": ["actor-configuration.d.ts", "src/**/*.ts"] } diff --git a/packages/platforms/cloudflare-workers/README.md b/packages/platforms/cloudflare-workers/README.md index 8adff7d71..6a8204849 100644 --- a/packages/platforms/cloudflare-workers/README.md +++ b/packages/platforms/cloudflare-workers/README.md @@ -8,4 +8,4 @@ _Lightweight Libraries for Backends_ ## License -Apache 2.0 \ No newline at end of file +Apache 2.0 diff --git a/packages/platforms/cloudflare-workers/package.json b/packages/platforms/cloudflare-workers/package.json index 3981e8de1..858dea366 100644 --- a/packages/platforms/cloudflare-workers/package.json +++ b/packages/platforms/cloudflare-workers/package.json @@ -1,7 +1,7 @@ { "name": "@rivetkit/cloudflare-workers", "version": "0.9.0-rc.1", - "keywords": ["rivetkit", "cloudflare", "workers", "edge", "platform", "serverless"], + "keywords": ["rivetkit", "cloudflare", "actors", "edge", "platform", "serverless"], "files": [ "src", "dist", diff --git a/packages/platforms/cloudflare-workers/src/actor-driver.ts b/packages/platforms/cloudflare-workers/src/actor-driver.ts new file mode 100644 index 000000000..072c8d1b6 --- /dev/null +++ b/packages/platforms/cloudflare-workers/src/actor-driver.ts @@ -0,0 +1,70 @@ +// import type { ActorDriver, AnyActorInstance } from "rivetkit/driver-helpers"; +// import invariant from "invariant"; +// import { KEYS } from "./actor-handler-do"; +// +// interface DurableObjectGlobalState { +// ctx: DurableObjectState; +// env: unknown; +// } +// +// /** +// * Cloudflare DO can have multiple DO running within the same global scope. +// * +// * This allows for storing the actor context globally and looking it up by ID in `CloudflareActorsActorDriver`. +// */ +// export class CloudflareDurableObjectGlobalState { +// // Single map for all actor state +// #dos: Map = new Map(); +// +// getDOState(actorId: string): DurableObjectGlobalState { +// const state = this.#dos.get(actorId); +// invariant(state !== undefined, "durable object state not in global state"); +// return state; +// } +// +// setDOState(actorId: string, state: DurableObjectGlobalState) { +// this.#dos.set(actorId, state); +// } +// } +// +// export interface ActorDriverContext { +// ctx: DurableObjectState; +// env: unknown; +// } +// +// export class CloudflareActorsActorDriver implements ActorDriver { +// #globalState: CloudflareDurableObjectGlobalState; +// +// constructor(globalState: CloudflareDurableObjectGlobalState) { +// this.#globalState = globalState; +// } +// +// #getDOCtx(actorId: string) { +// return this.#globalState.getDOState(actorId).ctx; +// } +// +// getContext(actorId: string): ActorDriverContext { +// const state = this.#globalState.getDOState(actorId); +// return { ctx: state.ctx, env: state.env }; +// } +// +// async readInput(actorId: string): Promise { +// return await this.#getDOCtx(actorId).storage.get(KEYS.INPUT); +// } +// +// async readPersistedData(actorId: string): Promise { +// return await this.#getDOCtx(actorId).storage.get(KEYS.PERSISTED_DATA); +// } +// +// async writePersistedData(actorId: string, data: unknown): Promise { +// await this.#getDOCtx(actorId).storage.put(KEYS.PERSISTED_DATA, data); +// } +// +// async setAlarm(actor: AnyActorInstance, timestamp: number): Promise { +// await this.#getDOCtx(actor.id).storage.setAlarm(timestamp); +// } +// +// async getDatabase(actorId: string): Promise { +// return this.#getDOCtx(actorId).storage.sql; +// } +// } diff --git a/packages/platforms/cloudflare-workers/src/worker-handler-do.ts b/packages/platforms/cloudflare-workers/src/actor-handler-do.ts similarity index 66% rename from packages/platforms/cloudflare-workers/src/worker-handler-do.ts rename to packages/platforms/cloudflare-workers/src/actor-handler-do.ts index b83ce9fb9..7e578053a 100644 --- a/packages/platforms/cloudflare-workers/src/worker-handler-do.ts +++ b/packages/platforms/cloudflare-workers/src/actor-handler-do.ts @@ -1,11 +1,11 @@ -// import { DurableObject } from "cloudflare:workers"; -// import type { Registry, RunConfig, WorkerKey } from "@rivetkit/core"; +// import { DurableObject } from "cloudflare:actors"; +// import type { Registry, RunConfig, ActorKey } from "@rivetkit/core"; // import { logger } from "./log"; -// import { PartitionTopologyWorker } from "@rivetkit/core/topologies/partition"; +// import { PartitionTopologyActor } from "@rivetkit/core/topologies/partition"; // import { // CloudflareDurableObjectGlobalState, -// CloudflareWorkersWorkerDriver, -// } from "./worker-driver"; +// CloudflareActorsActorDriver, +// } from "./actor-driver"; // import { Bindings, CF_AMBIENT_ENV } from "./handler"; // import { ExecutionContext } from "hono"; // @@ -17,30 +17,30 @@ // PERSISTED_DATA: "rivetkit:data", // }; // -// export interface WorkerHandlerInterface extends DurableObject { -// initialize(req: WorkerInitRequest): Promise; +// export interface ActorHandlerInterface extends DurableObject { +// initialize(req: ActorInitRequest): Promise; // } // -// export interface WorkerInitRequest { +// export interface ActorInitRequest { // name: string; -// key: WorkerKey; +// key: ActorKey; // input?: unknown; // } // // interface InitializedData { // name: string; -// key: WorkerKey; +// key: ActorKey; // } // // export type DurableObjectConstructor = new ( // ...args: ConstructorParameters> // ) => DurableObject; // -// interface LoadedWorker { -// workerTopology: PartitionTopologyWorker; +// interface LoadedActor { +// actorTopology: PartitionTopologyActor; // } // -// export function createWorkerDurableObject( +// export function createActorDurableObject( // registry: Registry, // runConfig: RunConfig, // ): DurableObjectConstructor { @@ -49,19 +49,19 @@ // /** // * Startup steps: // * 1. If not already created call `initialize`, otherwise check KV to ensure it's initialized -// * 2. Load worker +// * 2. Load actor // * 3. Start service requests // */ -// return class WorkerHandler +// return class ActorHandler // extends DurableObject -// implements WorkerHandlerInterface +// implements ActorHandlerInterface // { // #initialized?: InitializedData; // #initializedPromise?: PromiseWithResolvers; // -// #worker?: LoadedWorker; +// #actor?: LoadedActor; // -// async #loadWorker(): Promise { +// async #loadActor(): Promise { // // This is always called from another context using CF_AMBIENT_ENV // // // Wait for init @@ -78,9 +78,9 @@ // ]); // if (res.get(KEYS.INITIALIZED)) { // const name = res.get(KEYS.NAME) as string; -// if (!name) throw new Error("missing worker name"); -// const key = res.get(KEYS.KEY) as WorkerKey; -// if (!key) throw new Error("missing worker key"); +// if (!name) throw new Error("missing actor name"); +// const key = res.get(KEYS.KEY) as ActorKey; +// if (!key) throw new Error("missing actor key"); // // logger().debug("already initialized", { name, key }); // @@ -93,16 +93,16 @@ // } // // // Check if already loaded -// if (this.#worker) { -// return this.#worker; +// if (this.#actor) { +// return this.#actor; // } // // if (!this.#initialized) throw new Error("Not initialized"); // -// // Configure worker driver -// runConfig.driver.worker = new CloudflareWorkersWorkerDriver(globalState); +// // Configure actor driver +// runConfig.driver.actor = new CloudflareActorsActorDriver(globalState); // -// const workerTopology = new PartitionTopologyWorker( +// const actorTopology = new PartitionTopologyActor( // registry.config, // runConfig, // ); @@ -111,28 +111,28 @@ // // HACK: This leaks the DO context, but DO does not provide a native way // // of knowing when the DO shuts down. We're making a broad assumption // // that DO will boot a new isolate frequenlty enough that this is not an issue. -// const workerId = this.ctx.id.toString(); -// globalState.setDOState(workerId, { ctx: this.ctx, env: this.env }); +// const actorId = this.ctx.id.toString(); +// globalState.setDOState(actorId, { ctx: this.ctx, env: this.env }); // -// // Save worker -// this.#worker = { -// workerTopology, +// // Save actor +// this.#actor = { +// actorTopology, // }; // -// // Start worker -// await workerTopology.start( -// workerId, +// // Start actor +// await actorTopology.start( +// actorId, // this.#initialized.name, // this.#initialized.key, // // TODO: // "unknown", // ); // -// return this.#worker; +// return this.#actor; // } // // /** RPC called by the service that creates the DO to initialize it. */ -// async initialize(req: WorkerInitRequest) { +// async initialize(req: ActorInitRequest) { // // TODO: Need to add this to a core promise that needs to be resolved before start // // return await CF_AMBIENT_ENV.run(this.env, async () => { @@ -147,19 +147,19 @@ // key: req.key, // }; // -// logger().debug("initialized worker", { key: req.key }); +// logger().debug("initialized actor", { key: req.key }); // -// // Preemptively worker so the lifecycle hooks are called -// await this.#loadWorker(); +// // Preemptively actor so the lifecycle hooks are called +// await this.#loadActor(); // }); // } // // async fetch(request: Request): Promise { // return await CF_AMBIENT_ENV.run(this.env, async () => { -// const { workerTopology } = await this.#loadWorker(); +// const { actorTopology } = await this.#loadActor(); // // const ctx = this.ctx; -// return await workerTopology.router.fetch( +// return await actorTopology.router.fetch( // request, // this.env, // // Implement execution context so we can wait on requests @@ -178,8 +178,8 @@ // // async alarm(): Promise { // return await CF_AMBIENT_ENV.run(this.env, async () => { -// const { workerTopology } = await this.#loadWorker(); -// await workerTopology.worker.onAlarm(); +// const { actorTopology } = await this.#loadActor(); +// await actorTopology.actor.onAlarm(); // }); // } // }; diff --git a/packages/platforms/cloudflare-workers/src/handler.ts b/packages/platforms/cloudflare-workers/src/handler.ts index 24f13d725..99f71ac4a 100644 --- a/packages/platforms/cloudflare-workers/src/handler.ts +++ b/packages/platforms/cloudflare-workers/src/handler.ts @@ -1,8 +1,8 @@ // import { // type DurableObjectConstructor, -// type WorkerHandlerInterface, -// createWorkerDurableObject, -// } from "./worker-handler-do"; +// type ActorHandlerInterface, +// createActorDurableObject, +// } from "./actor-handler-do"; // import { ConfigSchema, type InputConfig } from "./config"; // import { assertUnreachable } from "@rivetkit/core/utils"; // import { @@ -14,7 +14,7 @@ // import type { Hono } from "hono"; // import { PartitionTopologyManager } from "@rivetkit/core/topologies/partition"; // import { logger } from "./log"; -// import { CloudflareWorkersManagerDriver } from "./manager-driver"; +// import { CloudflareActorsManagerDriver } from "./manager-driver"; // import { Encoding, Registry, RunConfig } from "@rivetkit/core"; // import { upgradeWebSocket } from "./websocket"; // import invariant from "invariant"; @@ -23,8 +23,8 @@ // // /** Cloudflare Workers env */ // export interface Bindings { -// WORKER_KV: KVNamespace; -// WORKER_DO: DurableObjectNamespace; +// ACTOR_KV: KVNamespace; +// ACTOR_DO: DurableObjectNamespace; // } // // /** @@ -54,10 +54,10 @@ // inputConfig?: InputConfig, // ): { // handler: ExportedHandler; -// WorkerHandler: DurableObjectConstructor; +// ActorHandler: DurableObjectConstructor; // } { // // Create router -// const { router, WorkerHandler } = createRouter(registry, inputConfig); +// const { router, ActorHandler } = createRouter(registry, inputConfig); // // // Create Cloudflare handler // const handler = { @@ -66,7 +66,7 @@ // }, // } satisfies ExportedHandler; // -// return { handler, WorkerHandler }; +// return { handler, ActorHandler }; // } // // export function createRouter( @@ -74,54 +74,54 @@ // inputConfig?: InputConfig, // ): { // router: Hono<{ Bindings: Bindings }>; -// WorkerHandler: DurableObjectConstructor; +// ActorHandler: DurableObjectConstructor; // } { // const config = ConfigSchema.parse(inputConfig); // const runConfig = { // driver: { // topology: "partition", -// manager: new CloudflareWorkersManagerDriver(), -// // HACK: We can't build the worker driver until we're inside the Druable Object -// worker: undefined as any, +// manager: new CloudflareActorsManagerDriver(), +// // HACK: We can't build the actor driver until we're inside the Druable Object +// actor: undefined as any, // }, // getUpgradeWebSocket: () => upgradeWebSocket, // ...config, // } satisfies RunConfig; // // // Create Durable Object -// const WorkerHandler = createWorkerDurableObject(registry, runConfig); +// const ActorHandler = createActorDurableObject(registry, runConfig); // // const managerTopology = new PartitionTopologyManager( // registry.config, // runConfig, // { -// sendRequest: async (workerId, workerRequest): Promise => { +// sendRequest: async (actorId, actorRequest): Promise => { // const env = getCloudflareAmbientEnv(); // // logger().debug("sending request to durable object", { -// workerId, -// method: workerRequest.method, -// url: workerRequest.url, +// actorId, +// method: actorRequest.method, +// url: actorRequest.url, // }); // -// const id = env.WORKER_DO.idFromString(workerId); -// const stub = env.WORKER_DO.get(id); +// const id = env.ACTOR_DO.idFromString(actorId); +// const stub = env.ACTOR_DO.get(id); // -// return await stub.fetch(workerRequest); +// return await stub.fetch(actorRequest); // }, // // openWebSocket: async ( -// workerId, +// actorId, // encodingKind: Encoding, // params: unknown, // ): Promise => { // const env = getCloudflareAmbientEnv(); // -// logger().debug("opening websocket to durable object", { workerId }); +// logger().debug("opening websocket to durable object", { actorId }); // // // Make a fetch request to the Durable Object with WebSocket upgrade -// const id = env.WORKER_DO.idFromString(workerId); -// const stub = env.WORKER_DO.get(id); +// const id = env.ACTOR_DO.idFromString(actorId); +// const stub = env.ACTOR_DO.get(id); // // const headers: Record = { // Upgrade: "websocket", @@ -135,7 +135,7 @@ // // HACK: See packages/platforms/cloudflare-workers/src/websocket.ts // headers["sec-websocket-protocol"] = "rivetkit"; // -// const response = await stub.fetch("http://worker/connect/websocket", { +// const response = await stub.fetch("http://actor/connect/websocket", { // headers, // }); // const webSocket = response.webSocket; @@ -147,7 +147,7 @@ // } // // logger().debug("durable object websocket connection open", { -// workerId, +// actorId, // }); // // webSocket.accept(); @@ -162,21 +162,21 @@ // return webSocket as unknown as WebSocket; // }, // -// proxyRequest: async (c, workerRequest, workerId): Promise => { +// proxyRequest: async (c, actorRequest, actorId): Promise => { // logger().debug("forwarding request to durable object", { -// workerId, -// method: workerRequest.method, -// url: workerRequest.url, +// actorId, +// method: actorRequest.method, +// url: actorRequest.url, // }); // -// const id = c.env.WORKER_DO.idFromString(workerId); -// const stub = c.env.WORKER_DO.get(id); +// const id = c.env.ACTOR_DO.idFromString(actorId); +// const stub = c.env.ACTOR_DO.get(id); // -// return await stub.fetch(workerRequest); +// return await stub.fetch(actorRequest); // }, -// proxyWebSocket: async (c, path, workerId, encoding, params, authData) => { +// proxyWebSocket: async (c, path, actorId, encoding, params, authData) => { // logger().debug("forwarding websocket to durable object", { -// workerId, +// actorId, // path, // }); // @@ -189,34 +189,34 @@ // } // // // TODO: strip headers -// const newUrl = new URL(`http://worker${path}`); -// const workerRequest = new Request(newUrl, c.req.raw); +// const newUrl = new URL(`http://actor${path}`); +// const actorRequest = new Request(newUrl, c.req.raw); // // // Always build fresh request to prevent forwarding unwanted headers // // HACK: Since we can't build a new request, we need to remove // // non-standard headers manually // const headerKeys: string[] = []; -// workerRequest.headers.forEach((v, k) => headerKeys.push(k)); +// actorRequest.headers.forEach((v, k) => headerKeys.push(k)); // for (const k of headerKeys) { // if (!STANDARD_WEBSOCKET_HEADERS.includes(k)) { -// workerRequest.headers.delete(k); +// actorRequest.headers.delete(k); // } // } // // // Add RivetKit headers -// workerRequest.headers.set(HEADER_EXPOSE_INTERNAL_ERROR, "true"); -// workerRequest.headers.set(HEADER_ENCODING, encoding); +// actorRequest.headers.set(HEADER_EXPOSE_INTERNAL_ERROR, "true"); +// actorRequest.headers.set(HEADER_ENCODING, encoding); // if (params) { -// workerRequest.headers.set(HEADER_CONN_PARAMS, JSON.stringify(params)); +// actorRequest.headers.set(HEADER_CONN_PARAMS, JSON.stringify(params)); // } // if (authData) { -// workerRequest.headers.set(HEADER_AUTH_DATA, JSON.stringify(authData)); +// actorRequest.headers.set(HEADER_AUTH_DATA, JSON.stringify(authData)); // } // -// const id = c.env.WORKER_DO.idFromString(workerId); -// const stub = c.env.WORKER_DO.get(id); +// const id = c.env.ACTOR_DO.idFromString(actorId); +// const stub = c.env.ACTOR_DO.get(id); // -// return await stub.fetch(workerRequest); +// return await stub.fetch(actorRequest); // }, // }, // ); @@ -226,5 +226,5 @@ // Bindings: Bindings; // }>; // -// return { router, WorkerHandler }; +// return { router, ActorHandler }; // } diff --git a/packages/platforms/cloudflare-workers/src/manager-driver.ts b/packages/platforms/cloudflare-workers/src/manager-driver.ts index d48ac1c33..089d47853 100644 --- a/packages/platforms/cloudflare-workers/src/manager-driver.ts +++ b/packages/platforms/cloudflare-workers/src/manager-driver.ts @@ -2,57 +2,57 @@ // ManagerDriver, // GetForIdInput, // GetWithKeyInput, -// WorkerOutput, +// ActorOutput, // CreateInput, // GetOrCreateWithKeyInput, // } from "@rivetkit/core/driver-helpers"; -// import { WorkerAlreadyExists } from "@rivetkit/core/errors"; +// import { ActorAlreadyExists } from "@rivetkit/core/errors"; // import { Bindings } from "./mod"; // import { logger } from "./log"; // import { serializeNameAndKey, serializeKey } from "./util"; // import { getCloudflareAmbientEnv } from "./handler"; // -// // Worker metadata structure -// interface WorkerData { +// // Actor metadata structure +// interface ActorData { // name: string; // key: string[]; // } // // // Key constants similar to Redis implementation // const KEYS = { -// WORKER: { -// // Combined key for worker metadata (name and key) -// metadata: (workerId: string) => `worker:${workerId}:metadata`, +// ACTOR: { +// // Combined key for actor metadata (name and key) +// metadata: (actorId: string) => `actor:${actorId}:metadata`, // -// // Key index function for worker lookup +// // Key index function for actor lookup // keyIndex: (name: string, key: string[] = []) => { // // Use serializeKey for consistent handling of all keys -// return `worker_key:${serializeKey(key)}`; +// return `actor_key:${serializeKey(key)}`; // }, // }, // }; // -// export class CloudflareWorkersManagerDriver implements ManagerDriver { +// export class CloudflareActorsManagerDriver implements ManagerDriver { // async getForId({ // c, -// workerId, -// }: GetForIdInput<{ Bindings: Bindings }>): Promise { +// actorId, +// }: GetForIdInput<{ Bindings: Bindings }>): Promise { // const env = getCloudflareAmbientEnv(); // -// // Get worker metadata from KV (combined name and key) -// const workerData = (await env.WORKER_KV.get(KEYS.WORKER.metadata(workerId), { +// // Get actor metadata from KV (combined name and key) +// const actorData = (await env.ACTOR_KV.get(KEYS.ACTOR.metadata(actorId), { // type: "json", -// })) as WorkerData | null; +// })) as ActorData | null; // -// // If the worker doesn't exist, return undefined -// if (!workerData) { +// // If the actor doesn't exist, return undefined +// if (!actorData) { // return undefined; // } // // return { -// workerId, -// name: workerData.name, -// key: workerData.key, +// actorId, +// name: actorData.name, +// key: actorData.key, // }; // } // @@ -61,115 +61,115 @@ // name, // key, // }: GetWithKeyInput<{ Bindings: Bindings }>): Promise< -// WorkerOutput | undefined +// ActorOutput | undefined // > { // const env = getCloudflareAmbientEnv(); // -// logger().debug("getWithKey: searching for worker", { name, key }); +// logger().debug("getWithKey: searching for actor", { name, key }); // // // Generate deterministic ID from the name and key -// // This is aligned with how createWorker generates IDs +// // This is aligned with how createActor generates IDs // const nameKeyString = serializeNameAndKey(name, key); -// const workerId = env.WORKER_DO.idFromName(nameKeyString).toString(); +// const actorId = env.ACTOR_DO.idFromName(nameKeyString).toString(); // -// // Check if the worker metadata exists -// const workerData = await env.WORKER_KV.get(KEYS.WORKER.metadata(workerId), { +// // Check if the actor metadata exists +// const actorData = await env.ACTOR_KV.get(KEYS.ACTOR.metadata(actorId), { // type: "json", // }); // -// if (!workerData) { -// logger().debug("getWithKey: no worker found with matching name and key", { +// if (!actorData) { +// logger().debug("getWithKey: no actor found with matching name and key", { // name, // key, -// workerId, +// actorId, // }); // return undefined; // } // -// logger().debug("getWithKey: found worker with matching name and key", { -// workerId, +// logger().debug("getWithKey: found actor with matching name and key", { +// actorId, // name, // key, // }); -// return this.#buildWorkerOutput(c, workerId); +// return this.#buildActorOutput(c, actorId); // } // // async getOrCreateWithKey( // input: GetOrCreateWithKeyInput, -// ): Promise { +// ): Promise { // // TODO: Prevent race condition here // const getOutput = await this.getWithKey(input); // if (getOutput) { // return getOutput; // } else { -// return await this.createWorker(input); +// return await this.createActor(input); // } // } // -// async createWorker({ +// async createActor({ // c, // name, // key, // input, -// }: CreateInput<{ Bindings: Bindings }>): Promise { +// }: CreateInput<{ Bindings: Bindings }>): Promise { // const env = getCloudflareAmbientEnv(); // -// // Check if worker with the same name and key already exists -// const existingWorker = await this.getWithKey({ c, name, key }); -// if (existingWorker) { -// throw new WorkerAlreadyExists(name, key); +// // Check if actor with the same name and key already exists +// const existingActor = await this.getWithKey({ c, name, key }); +// if (existingActor) { +// throw new ActorAlreadyExists(name, key); // } // -// // Create a deterministic ID from the worker name and key -// // This ensures that workers with the same name and key will have the same ID +// // Create a deterministic ID from the actor name and key +// // This ensures that actors with the same name and key will have the same ID // const nameKeyString = serializeNameAndKey(name, key); -// const doId = env.WORKER_DO.idFromName(nameKeyString); -// const workerId = doId.toString(); +// const doId = env.ACTOR_DO.idFromName(nameKeyString); +// const actorId = doId.toString(); // -// // Init worker -// const worker = env.WORKER_DO.get(doId); -// await worker.initialize({ +// // Init actor +// const actor = env.ACTOR_DO.get(doId); +// await actor.initialize({ // name, // key, // input, // }); // -// // Store combined worker metadata (name and key) -// const workerData: WorkerData = { name, key }; -// await env.WORKER_KV.put( -// KEYS.WORKER.metadata(workerId), -// JSON.stringify(workerData), +// // Store combined actor metadata (name and key) +// const actorData: ActorData = { name, key }; +// await env.ACTOR_KV.put( +// KEYS.ACTOR.metadata(actorId), +// JSON.stringify(actorData), // ); // // // Add to key index for lookups by name and key -// await env.WORKER_KV.put(KEYS.WORKER.keyIndex(name, key), workerId); +// await env.ACTOR_KV.put(KEYS.ACTOR.keyIndex(name, key), actorId); // // return { -// workerId, +// actorId, // name, // key, // }; // } // -// // Helper method to build worker output from an ID -// async #buildWorkerOutput( +// // Helper method to build actor output from an ID +// async #buildActorOutput( // c: any, -// workerId: string, -// ): Promise { +// actorId: string, +// ): Promise { // const env = getCloudflareAmbientEnv(); // -// const workerData = (await env.WORKER_KV.get(KEYS.WORKER.metadata(workerId), { +// const actorData = (await env.ACTOR_KV.get(KEYS.ACTOR.metadata(actorId), { // type: "json", -// })) as WorkerData | null; +// })) as ActorData | null; // -// if (!workerData) { +// if (!actorData) { // return undefined; // } // // return { -// workerId, -// name: workerData.name, -// key: workerData.key, +// actorId, +// name: actorData.name, +// key: actorData.key, // }; // } // } diff --git a/packages/platforms/cloudflare-workers/src/util.ts b/packages/platforms/cloudflare-workers/src/util.ts index f26fa4abc..77549ca90 100644 --- a/packages/platforms/cloudflare-workers/src/util.ts +++ b/packages/platforms/cloudflare-workers/src/util.ts @@ -5,7 +5,7 @@ // /** // * Serializes an array of key strings into a single string for use with idFromName // * -// * @param name The worker name +// * @param name The actor name // * @param key Array of key strings to serialize // * @returns A single string containing the serialized name and key // */ diff --git a/packages/platforms/cloudflare-workers/src/websocket.ts b/packages/platforms/cloudflare-workers/src/websocket.ts index 06e43e018..284272eae 100644 --- a/packages/platforms/cloudflare-workers/src/websocket.ts +++ b/packages/platforms/cloudflare-workers/src/websocket.ts @@ -52,7 +52,7 @@ // // server.accept?.(); // -// // note: cloudflare workers doesn't support 'open' event, so we call it immediately with a fake event +// // note: cloudflare actors doesn't support 'open' event, so we call it immediately with a fake event // // // // we have to do this after `server.accept() is called` // events.onOpen?.(new Event("open"), wsContext); diff --git a/packages/platforms/cloudflare-workers/src/worker-driver.ts b/packages/platforms/cloudflare-workers/src/worker-driver.ts deleted file mode 100644 index df36fc738..000000000 --- a/packages/platforms/cloudflare-workers/src/worker-driver.ts +++ /dev/null @@ -1,70 +0,0 @@ -// import type { WorkerDriver, AnyWorkerInstance } from "rivetkit/driver-helpers"; -// import invariant from "invariant"; -// import { KEYS } from "./worker-handler-do"; -// -// interface DurableObjectGlobalState { -// ctx: DurableObjectState; -// env: unknown; -// } -// -// /** -// * Cloudflare DO can have multiple DO running within the same global scope. -// * -// * This allows for storing the worker context globally and looking it up by ID in `CloudflareWorkersWorkerDriver`. -// */ -// export class CloudflareDurableObjectGlobalState { -// // Single map for all worker state -// #dos: Map = new Map(); -// -// getDOState(workerId: string): DurableObjectGlobalState { -// const state = this.#dos.get(workerId); -// invariant(state !== undefined, "durable object state not in global state"); -// return state; -// } -// -// setDOState(workerId: string, state: DurableObjectGlobalState) { -// this.#dos.set(workerId, state); -// } -// } -// -// export interface WorkerDriverContext { -// ctx: DurableObjectState; -// env: unknown; -// } -// -// export class CloudflareWorkersWorkerDriver implements WorkerDriver { -// #globalState: CloudflareDurableObjectGlobalState; -// -// constructor(globalState: CloudflareDurableObjectGlobalState) { -// this.#globalState = globalState; -// } -// -// #getDOCtx(workerId: string) { -// return this.#globalState.getDOState(workerId).ctx; -// } -// -// getContext(workerId: string): WorkerDriverContext { -// const state = this.#globalState.getDOState(workerId); -// return { ctx: state.ctx, env: state.env }; -// } -// -// async readInput(workerId: string): Promise { -// return await this.#getDOCtx(workerId).storage.get(KEYS.INPUT); -// } -// -// async readPersistedData(workerId: string): Promise { -// return await this.#getDOCtx(workerId).storage.get(KEYS.PERSISTED_DATA); -// } -// -// async writePersistedData(workerId: string, data: unknown): Promise { -// await this.#getDOCtx(workerId).storage.put(KEYS.PERSISTED_DATA, data); -// } -// -// async setAlarm(worker: AnyWorkerInstance, timestamp: number): Promise { -// await this.#getDOCtx(worker.id).storage.setAlarm(timestamp); -// } -// -// async getDatabase(workerId: string): Promise { -// return this.#getDOCtx(workerId).storage.sql; -// } -// } diff --git a/packages/platforms/cloudflare-workers/tests/driver-tests.test.ts b/packages/platforms/cloudflare-workers/tests/driver-tests.test.ts index da3e15e3f..4e710bc49 100644 --- a/packages/platforms/cloudflare-workers/tests/driver-tests.test.ts +++ b/packages/platforms/cloudflare-workers/tests/driver-tests.test.ts @@ -1,194 +1,194 @@ -import { runDriverTests } from "@rivetkit/core/driver-test-suite"; -import fs from "node:fs/promises"; -import path from "node:path"; -import os from "node:os"; -import { spawn, exec } from "node:child_process"; -import crypto from "node:crypto"; -import { promisify } from "node:util"; -import { getPort } from "@rivetkit/core/test"; - -const execPromise = promisify(exec); - -// Bypass createTestRuntime by providing an endpoint directly -runDriverTests({ - useRealTimers: true, - HACK_skipCleanupNet: true, - async start(projectPath: string) { - // Setup project - if (!setupProjectOnce) { - setupProjectOnce = setupProject(projectPath); - } - const projectDir = await setupProjectOnce; - - console.log("project dir", projectDir); - - // Get an available port - const port = await getPort(); - const inspectorPort = await getPort(); - - // Start wrangler dev - const wranglerProcess = spawn( - "pnpm", - [ - "start", - "src/index.ts", - "--port", - `${port}`, - "--inspector-port", - `${inspectorPort}`, - "--persist-to", - `/tmp/workers-test-${crypto.randomUUID()}`, - ], - { - cwd: projectDir, - stdio: "pipe", - }, - ); - - // Wait for wrangler to start - await new Promise((resolve, reject) => { - let isResolved = false; - const timeout = setTimeout(() => { - if (!isResolved) { - isResolved = true; - wranglerProcess.kill(); - reject(new Error("Timeout waiting for wrangler to start")); - } - }, 30000); - - wranglerProcess.stdout?.on("data", (data) => { - const output = data.toString(); - console.log(`wrangler: ${output}`); - if (output.includes(`Ready on http://localhost:${port}`)) { - if (!isResolved) { - isResolved = true; - clearTimeout(timeout); - resolve(); - } - } - }); - - wranglerProcess.stderr?.on("data", (data) => { - console.error(`wrangler: ${data}`); - }); - - wranglerProcess.on("error", (error) => { - if (!isResolved) { - isResolved = true; - clearTimeout(timeout); - reject(error); - } - }); - - wranglerProcess.on("exit", (code) => { - if (!isResolved && code !== 0) { - isResolved = true; - clearTimeout(timeout); - reject(new Error(`wrangler exited with code ${code}`)); - } - }); - }); - - return { - endpoint: `http://localhost:${port}`, - async cleanup() { - // Shut down wrangler process - wranglerProcess.kill(); - }, - }; - }, -}); - -let setupProjectOnce: Promise | undefined = undefined; - -async function setupProject(projectPath: string) { - // Create a temporary directory for the test - const uuid = crypto.randomUUID(); - const tmpDir = path.join(os.tmpdir(), `rivetkit-test-${uuid}`); - await fs.mkdir(tmpDir, { recursive: true }); - - // Create package.json with workspace dependencies - const packageJson = { - name: "rivetkit-test", - private: true, - version: "1.0.0", - type: "module", - scripts: { - start: "wrangler dev", - }, - dependencies: { - wrangler: "4.8.0", - "@rivetkit/cloudflare-workers": "workspace:*", - rivetkit: "workspace:*", - }, - packageManager: - "pnpm@10.7.1+sha512.2d92c86b7928dc8284f53494fb4201f983da65f0fb4f0d40baafa5cf628fa31dae3e5968f12466f17df7e97310e30f343a648baea1b9b350685dafafffdf5808", - }; - await fs.writeFile( - path.join(tmpDir, "package.json"), - JSON.stringify(packageJson, null, 2), - ); - - // Get the current workspace root path and link the workspace - const workspaceRoot = path.resolve(__dirname, "../../../.."); - await execPromise(`pnpm link -A ${workspaceRoot}`, { cwd: tmpDir }); - - // Install deps - await execPromise("pnpm install", { cwd: tmpDir }); - - // Create a wrangler.json file - const wranglerConfig = { - name: "rivetkit-test", - compatibility_date: "2025-01-29", - compatibility_flags: ["nodejs_compat"], - migrations: [ - { - new_classes: ["WorkerHandler"], - tag: "v1", - }, - ], - durable_objects: { - bindings: [ - { - class_name: "WorkerHandler", - name: "WORKER_DO", - }, - ], - }, - kv_namespaces: [ - { - binding: "WORKER_KV", - id: "test", // Will be replaced with a mock in dev mode - }, - ], - observability: { - enabled: true, - }, - }; - await fs.writeFile( - path.join(tmpDir, "wrangler.json"), - JSON.stringify(wranglerConfig, null, 2), - ); - - // Copy project to test directory - const projectDestDir = path.join(tmpDir, "src", "workers"); - await fs.cp(projectPath, projectDestDir, { recursive: true }); - - // Write script - const indexContent = `import { createHandler } from "@rivetkit/cloudflare-workers"; -import { registry } from "./workers/registry"; - -// TODO: Find a cleaner way of flagging an registry as test mode (ideally not in the config itself) -// Force enable test -registry.config.test.enabled = true; - -// Create handlers for Cloudflare Workers -const { handler, WorkerHandler } = createHandler(registry); - -// Export the handlers for Cloudflare -export { handler as default, WorkerHandler }; -`; - await fs.writeFile(path.join(tmpDir, "src/index.ts"), indexContent); - - return tmpDir; -} +// import { runDriverTests } from "@rivetkit/core/driver-test-suite"; +// import fs from "node:fs/promises"; +// import path from "node:path"; +// import os from "node:os"; +// import { spawn, exec } from "node:child_process"; +// import crypto from "node:crypto"; +// import { promisify } from "node:util"; +// import { getPort } from "@rivetkit/core/test"; +// +// const execPromise = promisify(exec); +// +// // Bypass createTestRuntime by providing an endpoint directly +// runDriverTests({ +// useRealTimers: true, +// HACK_skipCleanupNet: true, +// async start(projectPath: string) { +// // Setup project +// if (!setupProjectOnce) { +// setupProjectOnce = setupProject(projectPath); +// } +// const projectDir = await setupProjectOnce; +// +// console.log("project dir", projectDir); +// +// // Get an available port +// const port = await getPort(); +// const inspectorPort = await getPort(); +// +// // Start wrangler dev +// const wranglerProcess = spawn( +// "pnpm", +// [ +// "start", +// "src/index.ts", +// "--port", +// `${port}`, +// "--inspector-port", +// `${inspectorPort}`, +// "--persist-to", +// `/tmp/actors-test-${crypto.randomUUID()}`, +// ], +// { +// cwd: projectDir, +// stdio: "pipe", +// }, +// ); +// +// // Wait for wrangler to start +// await new Promise((resolve, reject) => { +// let isResolved = false; +// const timeout = setTimeout(() => { +// if (!isResolved) { +// isResolved = true; +// wranglerProcess.kill(); +// reject(new Error("Timeout waiting for wrangler to start")); +// } +// }, 30000); +// +// wranglerProcess.stdout?.on("data", (data) => { +// const output = data.toString(); +// console.log(`wrangler: ${output}`); +// if (output.includes(`Ready on http://localhost:${port}`)) { +// if (!isResolved) { +// isResolved = true; +// clearTimeout(timeout); +// resolve(); +// } +// } +// }); +// +// wranglerProcess.stderr?.on("data", (data) => { +// console.error(`wrangler: ${data}`); +// }); +// +// wranglerProcess.on("error", (error) => { +// if (!isResolved) { +// isResolved = true; +// clearTimeout(timeout); +// reject(error); +// } +// }); +// +// wranglerProcess.on("exit", (code) => { +// if (!isResolved && code !== 0) { +// isResolved = true; +// clearTimeout(timeout); +// reject(new Error(`wrangler exited with code ${code}`)); +// } +// }); +// }); +// +// return { +// endpoint: `http://localhost:${port}`, +// async cleanup() { +// // Shut down wrangler process +// wranglerProcess.kill(); +// }, +// }; +// }, +// }); +// +// let setupProjectOnce: Promise | undefined = undefined; +// +// async function setupProject(projectPath: string) { +// // Create a temporary directory for the test +// const uuid = crypto.randomUUID(); +// const tmpDir = path.join(os.tmpdir(), `rivetkit-test-${uuid}`); +// await fs.mkdir(tmpDir, { recursive: true }); +// +// // Create package.json with workspace dependencies +// const packageJson = { +// name: "rivetkit-test", +// private: true, +// version: "1.0.0", +// type: "module", +// scripts: { +// start: "wrangler dev", +// }, +// dependencies: { +// wrangler: "4.8.0", +// "@rivetkit/cloudflare-workers": "workspace:*", +// rivetkit: "workspace:*", +// }, +// packageManager: +// "pnpm@10.7.1+sha512.2d92c86b7928dc8284f53494fb4201f983da65f0fb4f0d40baafa5cf628fa31dae3e5968f12466f17df7e97310e30f343a648baea1b9b350685dafafffdf5808", +// }; +// await fs.writeFile( +// path.join(tmpDir, "package.json"), +// JSON.stringify(packageJson, null, 2), +// ); +// +// // Get the current workspace root path and link the workspace +// const workspaceRoot = path.resolve(__dirname, "../../../.."); +// await execPromise(`pnpm link -A ${workspaceRoot}`, { cwd: tmpDir }); +// +// // Install deps +// await execPromise("pnpm install", { cwd: tmpDir }); +// +// // Create a wrangler.json file +// const wranglerConfig = { +// name: "rivetkit-test", +// compatibility_date: "2025-01-29", +// compatibility_flags: ["nodejs_compat"], +// migrations: [ +// { +// new_classes: ["ActorHandler"], +// tag: "v1", +// }, +// ], +// durable_objects: { +// bindings: [ +// { +// class_name: "ActorHandler", +// name: "ACTOR_DO", +// }, +// ], +// }, +// kv_namespaces: [ +// { +// binding: "ACTOR_KV", +// id: "test", // Will be replaced with a mock in dev mode +// }, +// ], +// observability: { +// enabled: true, +// }, +// }; +// await fs.writeFile( +// path.join(tmpDir, "wrangler.json"), +// JSON.stringify(wranglerConfig, null, 2), +// ); +// +// // Copy project to test directory +// const projectDestDir = path.join(tmpDir, "src", "actors"); +// await fs.cp(projectPath, projectDestDir, { recursive: true }); +// +// // Write script +// const indexContent = `import { createHandler } from "@rivetkit/cloudflare-workers"; +// import { registry } from "./actors/registry"; +// +// // TODO: Find a cleaner way of flagging an registry as test mode (ideally not in the config itself) +// // Force enable test +// registry.config.test.enabled = true; +// +// // Create handlers for Cloudflare Workers +// const { handler, ActorHandler } = createHandler(registry); +// +// // Export the handlers for Cloudflare +// export { handler as default, ActorHandler }; +// `; +// await fs.writeFile(path.join(tmpDir, "src/index.ts"), indexContent); +// +// return tmpDir; +// } diff --git a/packages/platforms/cloudflare-workers/tests/id-generation.test.ts b/packages/platforms/cloudflare-workers/tests/id-generation.test.ts index ef456e97d..6e2e12083 100644 --- a/packages/platforms/cloudflare-workers/tests/id-generation.test.ts +++ b/packages/platforms/cloudflare-workers/tests/id-generation.test.ts @@ -1,41 +1,41 @@ -import { describe, test, expect, vi } from "vitest"; -import { serializeNameAndKey } from "../src/util"; -import { CloudflareWorkersManagerDriver } from "../src/manager-driver"; - -describe("Deterministic ID generation", () => { - test("should generate consistent IDs for the same name and key", () => { - const name = "test-worker"; - const key = ["key1", "key2"]; - - // Test that serializeNameAndKey produces a consistent string - const serialized1 = serializeNameAndKey(name, key); - const serialized2 = serializeNameAndKey(name, key); - - expect(serialized1).toBe(serialized2); - expect(serialized1).toBe("test-worker:key1,key2"); - }); - - test("should properly escape special characters in keys", () => { - const name = "test-worker"; - const key = ["key,with,commas", "normal-key"]; - - const serialized = serializeNameAndKey(name, key); - expect(serialized).toBe("test-worker:key\\,with\\,commas,normal-key"); - }); - - test("should properly escape colons in worker names", () => { - const name = "test:worker:with:colons"; - const key = ["key1", "key2"]; - - const serialized = serializeNameAndKey(name, key); - expect(serialized).toBe("test\\:worker\\:with\\:colons:key1,key2"); - }); - - test("should handle empty key arrays", () => { - const name = "test-worker"; - const key: string[] = []; - - const serialized = serializeNameAndKey(name, key); - expect(serialized).toBe("test-worker:(none)"); - }); -}); \ No newline at end of file +// import { describe, test, expect, vi } from "vitest"; +// import { serializeNameAndKey } from "../src/util"; +// import { CloudflareActorsManagerDriver } from "../src/manager-driver"; +// +// describe("Deterministic ID generation", () => { +// test("should generate consistent IDs for the same name and key", () => { +// const name = "test-actor"; +// const key = ["key1", "key2"]; +// +// // Test that serializeNameAndKey produces a consistent string +// const serialized1 = serializeNameAndKey(name, key); +// const serialized2 = serializeNameAndKey(name, key); +// +// expect(serialized1).toBe(serialized2); +// expect(serialized1).toBe("test-actor:key1,key2"); +// }); +// +// test("should properly escape special characters in keys", () => { +// const name = "test-actor"; +// const key = ["key,with,commas", "normal-key"]; +// +// const serialized = serializeNameAndKey(name, key); +// expect(serialized).toBe("test-actor:key\\,with\\,commas,normal-key"); +// }); +// +// test("should properly escape colons in actor names", () => { +// const name = "test:actor:with:colons"; +// const key = ["key1", "key2"]; +// +// const serialized = serializeNameAndKey(name, key); +// expect(serialized).toBe("test\\:actor\\:with\\:colons:key1,key2"); +// }); +// +// test("should handle empty key arrays", () => { +// const name = "test-actor"; +// const key: string[] = []; +// +// const serialized = serializeNameAndKey(name, key); +// expect(serialized).toBe("test-actor:(none)"); +// }); +// }); diff --git a/packages/platforms/cloudflare-workers/tests/key-indexes.test.ts b/packages/platforms/cloudflare-workers/tests/key-indexes.test.ts index 4e2237a71..6324f4d81 100644 --- a/packages/platforms/cloudflare-workers/tests/key-indexes.test.ts +++ b/packages/platforms/cloudflare-workers/tests/key-indexes.test.ts @@ -1,42 +1,42 @@ -import { describe, test, expect } from "vitest"; -import { serializeKey, serializeNameAndKey } from "../src/util"; - -// Access internal KEYS directly -// Since KEYS is a private constant in manager_driver.ts, we'll redefine it here for testing -const KEYS = { - WORKER: { - metadata: (workerId: string) => `worker:${workerId}:metadata`, - keyIndex: (name: string, key: string[] = []) => { - // Use serializeKey for consistent handling of all keys - return `worker_key:${serializeKey(key)}`; - }, - }, -}; - -describe("Key index functions", () => { - test("keyIndex handles empty key array", () => { - expect(KEYS.WORKER.keyIndex("test-worker")).toBe("worker_key:(none)"); - expect(KEYS.WORKER.keyIndex("worker:with:colons")).toBe("worker_key:(none)"); - }); - - test("keyIndex handles single-item key arrays", () => { - // Note: keyIndex ignores the name parameter - expect(KEYS.WORKER.keyIndex("test-worker", ["key1"])).toBe("worker_key:key1"); - expect(KEYS.WORKER.keyIndex("worker:with:colons", ["key:with:colons"])) - .toBe("worker_key:key:with:colons"); - }); - - test("keyIndex handles multi-item array keys", () => { - // Note: keyIndex ignores the name parameter - expect(KEYS.WORKER.keyIndex("test-worker", ["key1", "key2"])) - .toBe(`worker_key:key1,key2`); - - // Test with special characters - expect(KEYS.WORKER.keyIndex("test-worker", ["key,with,commas"])) - .toBe("worker_key:key\\,with\\,commas"); - }); - - test("metadata key creates proper pattern", () => { - expect(KEYS.WORKER.metadata("123-456")).toBe("worker:123-456:metadata"); - }); -}); \ No newline at end of file +// import { describe, test, expect } from "vitest"; +// import { serializeKey, serializeNameAndKey } from "../src/util"; +// +// // Access internal KEYS directly +// // Since KEYS is a private constant in manager_driver.ts, we'll redefine it here for testing +// const KEYS = { +// ACTOR: { +// metadata: (actorId: string) => `actor:${actorId}:metadata`, +// keyIndex: (name: string, key: string[] = []) => { +// // Use serializeKey for consistent handling of all keys +// return `actor_key:${serializeKey(key)}`; +// }, +// }, +// }; +// +// describe("Key index functions", () => { +// test("keyIndex handles empty key array", () => { +// expect(KEYS.ACTOR.keyIndex("test-actor")).toBe("actor_key:(none)"); +// expect(KEYS.ACTOR.keyIndex("actor:with:colons")).toBe("actor_key:(none)"); +// }); +// +// test("keyIndex handles single-item key arrays", () => { +// // Note: keyIndex ignores the name parameter +// expect(KEYS.ACTOR.keyIndex("test-actor", ["key1"])).toBe("actor_key:key1"); +// expect(KEYS.ACTOR.keyIndex("actor:with:colons", ["key:with:colons"])) +// .toBe("actor_key:key:with:colons"); +// }); +// +// test("keyIndex handles multi-item array keys", () => { +// // Note: keyIndex ignores the name parameter +// expect(KEYS.ACTOR.keyIndex("test-actor", ["key1", "key2"])) +// .toBe(`actor_key:key1,key2`); +// +// // Test with special characters +// expect(KEYS.ACTOR.keyIndex("test-actor", ["key,with,commas"])) +// .toBe("actor_key:key\\,with\\,commas"); +// }); +// +// test("metadata key creates proper pattern", () => { +// expect(KEYS.ACTOR.metadata("123-456")).toBe("actor:123-456:metadata"); +// }); +// }); diff --git a/packages/platforms/cloudflare-workers/tests/key-serialization.test.ts b/packages/platforms/cloudflare-workers/tests/key-serialization.test.ts index 49bea67a1..34c8e6e48 100644 --- a/packages/platforms/cloudflare-workers/tests/key-serialization.test.ts +++ b/packages/platforms/cloudflare-workers/tests/key-serialization.test.ts @@ -1,222 +1,222 @@ -import { describe, test, expect } from "vitest"; -import { - serializeKey, - deserializeKey, - serializeNameAndKey, - EMPTY_KEY, - KEY_SEPARATOR -} from "../src/util"; - -describe("Key serialization and deserialization", () => { - // Test key serialization - describe("serializeKey", () => { - test("serializes empty key array", () => { - expect(serializeKey([])).toBe(EMPTY_KEY); - }); - - test("serializes single key", () => { - expect(serializeKey(["test"])).toBe("test"); - }); - - test("serializes multiple keys", () => { - expect(serializeKey(["a", "b", "c"])).toBe(`a${KEY_SEPARATOR}b${KEY_SEPARATOR}c`); - }); - - test("escapes commas in keys", () => { - expect(serializeKey(["a,b"])).toBe("a\\,b"); - expect(serializeKey(["a,b", "c"])).toBe(`a\\,b${KEY_SEPARATOR}c`); - }); - - test("escapes empty key marker in keys", () => { - expect(serializeKey([EMPTY_KEY])).toBe(`\\${EMPTY_KEY}`); - }); - - test("handles complex keys", () => { - expect(serializeKey(["a,b", EMPTY_KEY, "c,d"])).toBe(`a\\,b${KEY_SEPARATOR}\\${EMPTY_KEY}${KEY_SEPARATOR}c\\,d`); - }); - }); - - // Test key deserialization - describe("deserializeKey", () => { - test("deserializes empty string", () => { - expect(deserializeKey("")).toEqual([]); - }); - - test("deserializes undefined/null", () => { - expect(deserializeKey(undefined as unknown as string)).toEqual([]); - expect(deserializeKey(null as unknown as string)).toEqual([]); - }); - - test("deserializes empty key marker", () => { - expect(deserializeKey(EMPTY_KEY)).toEqual([]); - }); - - test("deserializes single key", () => { - expect(deserializeKey("test")).toEqual(["test"]); - }); - - test("deserializes multiple keys", () => { - expect(deserializeKey(`a${KEY_SEPARATOR}b${KEY_SEPARATOR}c`)).toEqual(["a", "b", "c"]); - }); - - test("deserializes keys with escaped commas", () => { - expect(deserializeKey("a\\,b")).toEqual(["a,b"]); - expect(deserializeKey(`a\\,b${KEY_SEPARATOR}c`)).toEqual(["a,b", "c"]); - }); - - test("deserializes keys with escaped empty key marker", () => { - expect(deserializeKey(`\\${EMPTY_KEY}`)).toEqual([EMPTY_KEY]); - }); - - test("deserializes complex keys", () => { - expect(deserializeKey(`a\\,b${KEY_SEPARATOR}\\${EMPTY_KEY}${KEY_SEPARATOR}c\\,d`)).toEqual(["a,b", EMPTY_KEY, "c,d"]); - }); - }); - - // Test name+key serialization - describe("serializeNameAndKey", () => { - test("serializes name with empty key array", () => { - expect(serializeNameAndKey("test", [])).toBe(`test:${EMPTY_KEY}`); - }); - - test("serializes name with single key", () => { - expect(serializeNameAndKey("test", ["key1"])).toBe("test:key1"); - }); - - test("serializes name with multiple keys", () => { - expect(serializeNameAndKey("test", ["a", "b", "c"])).toBe(`test:a${KEY_SEPARATOR}b${KEY_SEPARATOR}c`); - }); - - test("escapes commas in keys", () => { - expect(serializeNameAndKey("test", ["a,b"])).toBe("test:a\\,b"); - }); - - test("handles complex keys with name", () => { - expect(serializeNameAndKey("worker", ["a,b", EMPTY_KEY, "c,d"])) - .toBe(`worker:a\\,b${KEY_SEPARATOR}\\${EMPTY_KEY}${KEY_SEPARATOR}c\\,d`); - }); - }); - - // Removed createIndexKey tests as function was moved to KEYS.INDEX in manager_driver.ts - - // Test roundtrip - describe("roundtrip", () => { - const testKeys = [ - [], - ["test"], - ["a", "b", "c"], - ["a,b", "c"], - [EMPTY_KEY], - ["a,b", EMPTY_KEY, "c,d"], - ["special\\chars", "more:complex,keys", "final key"] - ]; - - testKeys.forEach(key => { - test(`roundtrip: ${JSON.stringify(key)}`, () => { - const serialized = serializeKey(key); - const deserialized = deserializeKey(serialized); - expect(deserialized).toEqual(key); - }); - }); - - test("handles all test cases in a large batch", () => { - for (const key of testKeys) { - const serialized = serializeKey(key); - const deserialized = deserializeKey(serialized); - expect(deserialized).toEqual(key); - } - }); - }); - - // Test edge cases - describe("edge cases", () => { - test("handles backslash at the end", () => { - const key = ["abc\\"]; - const serialized = serializeKey(key); - const deserialized = deserializeKey(serialized); - expect(deserialized).toEqual(key); - }); - - test("handles backslashes in middle of string", () => { - const keys = [ - ["abc\\def"], - ["abc\\\\def"], - ["path\\to\\file"] - ]; - - for (const key of keys) { - const serialized = serializeKey(key); - const deserialized = deserializeKey(serialized); - expect(deserialized).toEqual(key); - } - }); - - test("handles commas at the end of strings", () => { - const serialized = serializeKey(["abc\\,"]); - expect(deserializeKey(serialized)).toEqual(["abc\\,"]); - }); - - test("handles mixed backslashes and commas", () => { - const keys = [ - ["path\\to\\file,dir"], - ["file\\with,comma"], - ["path\\to\\file", "with,comma"] - ]; - - for (const key of keys) { - const serialized = serializeKey(key); - const deserialized = deserializeKey(serialized); - expect(deserialized).toEqual(key); - } - }); - - test("handles multiple consecutive commas", () => { - const key = ["a,,b"]; - const serialized = serializeKey(key); - const deserialized = deserializeKey(serialized); - expect(deserialized).toEqual(key); - }); - - test("handles special characters", () => { - const key = ["a💻b", "c🔑d"]; - const serialized = serializeKey(key); - const deserialized = deserializeKey(serialized); - expect(deserialized).toEqual(key); - }); - }); - - // Test exact key matching - describe("exact key matching", () => { - test("differentiates [a,b] from [a,b,c]", () => { - const key1 = ["a", "b"]; - const key2 = ["a", "b", "c"]; - - const serialized1 = serializeKey(key1); - const serialized2 = serializeKey(key2); - - expect(serialized1).not.toBe(serialized2); - }); - - test("differentiates [a,b] from [a]", () => { - const key1 = ["a", "b"]; - const key2 = ["a"]; - - const serialized1 = serializeKey(key1); - const serialized2 = serializeKey(key2); - - expect(serialized1).not.toBe(serialized2); - }); - - test("differentiates [a,b] from [a:b]", () => { - const key1 = ["a,b"]; - const key2 = ["a", "b"]; - - const serialized1 = serializeKey(key1); - const serialized2 = serializeKey(key2); - - expect(serialized1).not.toBe(serialized2); - expect(deserializeKey(serialized1)).toEqual(key1); - expect(deserializeKey(serialized2)).toEqual(key2); - }); - }); -}); \ No newline at end of file +// import { describe, test, expect } from "vitest"; +// import { +// serializeKey, +// deserializeKey, +// serializeNameAndKey, +// EMPTY_KEY, +// KEY_SEPARATOR +// } from "../src/util"; +// +// describe("Key serialization and deserialization", () => { +// // Test key serialization +// describe("serializeKey", () => { +// test("serializes empty key array", () => { +// expect(serializeKey([])).toBe(EMPTY_KEY); +// }); +// +// test("serializes single key", () => { +// expect(serializeKey(["test"])).toBe("test"); +// }); +// +// test("serializes multiple keys", () => { +// expect(serializeKey(["a", "b", "c"])).toBe(`a${KEY_SEPARATOR}b${KEY_SEPARATOR}c`); +// }); +// +// test("escapes commas in keys", () => { +// expect(serializeKey(["a,b"])).toBe("a\\,b"); +// expect(serializeKey(["a,b", "c"])).toBe(`a\\,b${KEY_SEPARATOR}c`); +// }); +// +// test("escapes empty key marker in keys", () => { +// expect(serializeKey([EMPTY_KEY])).toBe(`\\${EMPTY_KEY}`); +// }); +// +// test("handles complex keys", () => { +// expect(serializeKey(["a,b", EMPTY_KEY, "c,d"])).toBe(`a\\,b${KEY_SEPARATOR}\\${EMPTY_KEY}${KEY_SEPARATOR}c\\,d`); +// }); +// }); +// +// // Test key deserialization +// describe("deserializeKey", () => { +// test("deserializes empty string", () => { +// expect(deserializeKey("")).toEqual([]); +// }); +// +// test("deserializes undefined/null", () => { +// expect(deserializeKey(undefined as unknown as string)).toEqual([]); +// expect(deserializeKey(null as unknown as string)).toEqual([]); +// }); +// +// test("deserializes empty key marker", () => { +// expect(deserializeKey(EMPTY_KEY)).toEqual([]); +// }); +// +// test("deserializes single key", () => { +// expect(deserializeKey("test")).toEqual(["test"]); +// }); +// +// test("deserializes multiple keys", () => { +// expect(deserializeKey(`a${KEY_SEPARATOR}b${KEY_SEPARATOR}c`)).toEqual(["a", "b", "c"]); +// }); +// +// test("deserializes keys with escaped commas", () => { +// expect(deserializeKey("a\\,b")).toEqual(["a,b"]); +// expect(deserializeKey(`a\\,b${KEY_SEPARATOR}c`)).toEqual(["a,b", "c"]); +// }); +// +// test("deserializes keys with escaped empty key marker", () => { +// expect(deserializeKey(`\\${EMPTY_KEY}`)).toEqual([EMPTY_KEY]); +// }); +// +// test("deserializes complex keys", () => { +// expect(deserializeKey(`a\\,b${KEY_SEPARATOR}\\${EMPTY_KEY}${KEY_SEPARATOR}c\\,d`)).toEqual(["a,b", EMPTY_KEY, "c,d"]); +// }); +// }); +// +// // Test name+key serialization +// describe("serializeNameAndKey", () => { +// test("serializes name with empty key array", () => { +// expect(serializeNameAndKey("test", [])).toBe(`test:${EMPTY_KEY}`); +// }); +// +// test("serializes name with single key", () => { +// expect(serializeNameAndKey("test", ["key1"])).toBe("test:key1"); +// }); +// +// test("serializes name with multiple keys", () => { +// expect(serializeNameAndKey("test", ["a", "b", "c"])).toBe(`test:a${KEY_SEPARATOR}b${KEY_SEPARATOR}c`); +// }); +// +// test("escapes commas in keys", () => { +// expect(serializeNameAndKey("test", ["a,b"])).toBe("test:a\\,b"); +// }); +// +// test("handles complex keys with name", () => { +// expect(serializeNameAndKey("actor", ["a,b", EMPTY_KEY, "c,d"])) +// .toBe(`actor:a\\,b${KEY_SEPARATOR}\\${EMPTY_KEY}${KEY_SEPARATOR}c\\,d`); +// }); +// }); +// +// // Removed createIndexKey tests as function was moved to KEYS.INDEX in manager_driver.ts +// +// // Test roundtrip +// describe("roundtrip", () => { +// const testKeys = [ +// [], +// ["test"], +// ["a", "b", "c"], +// ["a,b", "c"], +// [EMPTY_KEY], +// ["a,b", EMPTY_KEY, "c,d"], +// ["special\\chars", "more:complex,keys", "final key"] +// ]; +// +// testKeys.forEach(key => { +// test(`roundtrip: ${JSON.stringify(key)}`, () => { +// const serialized = serializeKey(key); +// const deserialized = deserializeKey(serialized); +// expect(deserialized).toEqual(key); +// }); +// }); +// +// test("handles all test cases in a large batch", () => { +// for (const key of testKeys) { +// const serialized = serializeKey(key); +// const deserialized = deserializeKey(serialized); +// expect(deserialized).toEqual(key); +// } +// }); +// }); +// +// // Test edge cases +// describe("edge cases", () => { +// test("handles backslash at the end", () => { +// const key = ["abc\\"]; +// const serialized = serializeKey(key); +// const deserialized = deserializeKey(serialized); +// expect(deserialized).toEqual(key); +// }); +// +// test("handles backslashes in middle of string", () => { +// const keys = [ +// ["abc\\def"], +// ["abc\\\\def"], +// ["path\\to\\file"] +// ]; +// +// for (const key of keys) { +// const serialized = serializeKey(key); +// const deserialized = deserializeKey(serialized); +// expect(deserialized).toEqual(key); +// } +// }); +// +// test("handles commas at the end of strings", () => { +// const serialized = serializeKey(["abc\\,"]); +// expect(deserializeKey(serialized)).toEqual(["abc\\,"]); +// }); +// +// test("handles mixed backslashes and commas", () => { +// const keys = [ +// ["path\\to\\file,dir"], +// ["file\\with,comma"], +// ["path\\to\\file", "with,comma"] +// ]; +// +// for (const key of keys) { +// const serialized = serializeKey(key); +// const deserialized = deserializeKey(serialized); +// expect(deserialized).toEqual(key); +// } +// }); +// +// test("handles multiple consecutive commas", () => { +// const key = ["a,,b"]; +// const serialized = serializeKey(key); +// const deserialized = deserializeKey(serialized); +// expect(deserialized).toEqual(key); +// }); +// +// test("handles special characters", () => { +// const key = ["a💻b", "c🔑d"]; +// const serialized = serializeKey(key); +// const deserialized = deserializeKey(serialized); +// expect(deserialized).toEqual(key); +// }); +// }); +// +// // Test exact key matching +// describe("exact key matching", () => { +// test("differentiates [a,b] from [a,b,c]", () => { +// const key1 = ["a", "b"]; +// const key2 = ["a", "b", "c"]; +// +// const serialized1 = serializeKey(key1); +// const serialized2 = serializeKey(key2); +// +// expect(serialized1).not.toBe(serialized2); +// }); +// +// test("differentiates [a,b] from [a]", () => { +// const key1 = ["a", "b"]; +// const key2 = ["a"]; +// +// const serialized1 = serializeKey(key1); +// const serialized2 = serializeKey(key2); +// +// expect(serialized1).not.toBe(serialized2); +// }); +// +// test("differentiates [a,b] from [a:b]", () => { +// const key1 = ["a,b"]; +// const key2 = ["a", "b"]; +// +// const serialized1 = serializeKey(key1); +// const serialized2 = serializeKey(key2); +// +// expect(serialized1).not.toBe(serialized2); +// expect(deserializeKey(serialized1)).toEqual(key1); +// expect(deserializeKey(serialized2)).toEqual(key2); +// }); +// }); +// }); diff --git a/packages/rivetkit/README.md b/packages/rivetkit/README.md index d3c3689d1..50b199c99 100644 --- a/packages/rivetkit/README.md +++ b/packages/rivetkit/README.md @@ -2,7 +2,7 @@ _Lightweight Libraries for Backends_ -**This is not the RivetKit you are looking for. See [`@rivetkit/worker`](https://www.npmjs.com/package/@rivetkit/core).** +**This is not the RivetKit you are looking for. See [`@rivetkit/actor`](https://www.npmjs.com/package/@rivetkit/core).** [Learn More →](https://github.com/rivet-gg/rivetkit) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2d151562..be1963608 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,9 +70,9 @@ importers: specifier: ^18.2.0 version: 18.2.0(react@18.3.1) devDependencies: - '@rivetkit/worker': + '@rivetkit/actor': specifier: workspace:* - version: link:../../packages/worker + version: link:../../packages/actor '@types/node': specifier: ^22.13.9 version: 22.15.32 @@ -103,9 +103,9 @@ importers: examples/chat-room: devDependencies: - '@rivetkit/worker': + '@rivetkit/actor': specifier: workspace:* - version: link:../../packages/worker + version: link:../../packages/actor '@types/node': specifier: ^22.13.9 version: 22.15.32 @@ -134,9 +134,9 @@ importers: '@cloudflare/workers-types': specifier: ^4.20250129.0 version: 4.20250619.0 - '@rivetkit/worker': + '@rivetkit/actor': specifier: workspace:* - version: link:../../packages/worker + version: link:../../packages/actor '@types/node': specifier: ^22.13.9 version: 22.15.32 @@ -152,9 +152,9 @@ importers: examples/counter: devDependencies: - '@rivetkit/worker': + '@rivetkit/actor': specifier: workspace:* - version: link:../../packages/worker + version: link:../../packages/actor '@types/node': specifier: ^22.13.9 version: 22.15.32 @@ -208,9 +208,9 @@ importers: specifier: ^18.2.0 version: 18.2.0(react@18.3.1) devDependencies: - '@rivetkit/worker': + '@rivetkit/actor': specifier: workspace:* - version: link:../../packages/worker + version: link:../../packages/actor '@types/node': specifier: ^22.13.9 version: 22.15.32 @@ -233,9 +233,9 @@ importers: specifier: ^18.2.0 version: 18.2.0(react@18.3.1) devDependencies: - '@rivetkit/worker': + '@rivetkit/actor': specifier: workspace:* - version: link:../../packages/worker + version: link:../../packages/actor '@types/express': specifier: ^4.17.21 version: 4.17.23 @@ -258,9 +258,9 @@ importers: specifier: ^4.7.0 version: 4.8.0 devDependencies: - '@rivetkit/worker': + '@rivetkit/actor': specifier: workspace:* - version: link:../../packages/worker + version: link:../../packages/actor '@types/node': specifier: ^22.13.9 version: 22.15.32 @@ -289,9 +289,9 @@ importers: specifier: ^18.2.0 version: 18.2.0(react@18.3.1) devDependencies: - '@rivetkit/worker': + '@rivetkit/actor': specifier: workspace:* - version: link:../../packages/worker + version: link:../../packages/actor '@types/node': specifier: ^22.13.9 version: 22.15.32 @@ -332,9 +332,9 @@ importers: specifier: ^18.2.0 version: 18.2.0(react@18.3.1) devDependencies: - '@rivetkit/worker': + '@rivetkit/actor': specifier: workspace:* - version: link:../../packages/worker + version: link:../../packages/actor '@types/node': specifier: ^22.13.9 version: 22.15.32 @@ -365,9 +365,9 @@ importers: examples/rivet: dependencies: - '@rivetkit/worker': - specifier: https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/worker@38d8fca - version: https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/worker@38d8fca(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2) + '@rivetkit/actor': + specifier: workspace:* + version: link:../../packages/actor devDependencies: '@types/node': specifier: ^22.13.9 @@ -391,9 +391,9 @@ importers: specifier: ^3.24.1 version: 3.25.67 devDependencies: - '@rivetkit/worker': + '@rivetkit/actor': specifier: workspace:* - version: link:../../packages/worker + version: link:../../packages/actor '@types/node': specifier: ^22.13.9 version: 22.15.32 @@ -404,6 +404,22 @@ importers: specifier: ^5.5.2 version: 5.8.3 + packages/actor: + dependencies: + '@rivetkit/core': + specifier: workspace:* + version: link:../core + devDependencies: + '@types/node': + specifier: ^22.14.0 + version: 22.15.32 + tsup: + specifier: ^8.4.0 + version: 8.5.0(@microsoft/api-extractor@7.52.8(@types/node@22.15.32))(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0) + typescript: + specifier: ^5.5.2 + version: 5.8.3 + packages/core: dependencies: '@hono/zod-openapi': @@ -681,22 +697,6 @@ importers: packages/rivetkit: {} - packages/worker: - dependencies: - '@rivetkit/core': - specifier: workspace:* - version: link:../core - devDependencies: - '@types/node': - specifier: ^22.14.0 - version: 22.15.32 - tsup: - specifier: ^8.4.0 - version: 8.5.0(@microsoft/api-extractor@7.52.8(@types/node@22.15.32))(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0) - typescript: - specifier: ^5.5.2 - version: 5.8.3 - packages: '@ampproject/remapping@2.3.0': @@ -1769,29 +1769,6 @@ packages: '@rivet-gg/actor-core@25.2.0': resolution: {integrity: sha512-4K72XcDLVAz44Ae6G6GuyzWyxQZOLN8jM/W+sVKm6fHr70X8FNCSC5+/9hFIxz/OH9E6q6Wi3V/UN/k6immUBQ==} - '@rivetkit/core@https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/core@38d8fcab136be6bdbd976a802f93581384c12fb2': - resolution: {tarball: https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/core@38d8fcab136be6bdbd976a802f93581384c12fb2} - version: 0.9.0-rc.1 - engines: {node: '>=22.0.0'} - peerDependencies: - '@hono/node-server': ^1.14.0 - '@hono/node-ws': ^1.1.1 - eventsource: ^3.0.5 - ws: ^8.0.0 - peerDependenciesMeta: - '@hono/node-server': - optional: true - '@hono/node-ws': - optional: true - eventsource: - optional: true - ws: - optional: true - - '@rivetkit/worker@https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/worker@38d8fca': - resolution: {tarball: https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/worker@38d8fca} - version: 0.9.0-rc.1 - '@rolldown/pluginutils@1.0.0-beta.11': resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==} @@ -5017,30 +4994,6 @@ snapshots: dependencies: zod: 3.25.67 - '@rivetkit/core@https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/core@38d8fcab136be6bdbd976a802f93581384c12fb2(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2)': - dependencies: - '@hono/zod-openapi': 0.19.8(hono@4.8.0)(zod@3.25.67) - cbor-x: 1.6.0 - hono: 4.8.0 - invariant: 2.2.4 - on-change: 5.0.1 - p-retry: 6.2.1 - zod: 3.25.67 - optionalDependencies: - '@hono/node-server': 1.14.4(hono@4.8.0) - '@hono/node-ws': 1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0) - eventsource: 3.0.7 - ws: 8.18.2 - - '@rivetkit/worker@https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/worker@38d8fca(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2)': - dependencies: - '@rivetkit/core': https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/core@38d8fcab136be6bdbd976a802f93581384c12fb2(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2) - transitivePeerDependencies: - - '@hono/node-server' - - '@hono/node-ws' - - eventsource - - ws - '@rolldown/pluginutils@1.0.0-beta.11': {} '@rollup/pluginutils@5.2.0(rollup@4.44.0)': @@ -5412,30 +5365,6 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.15.32)(tsx@3.14.0)(yaml@2.8.0))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - vite: 6.3.5(@types/node@22.15.32)(tsx@3.14.0)(yaml@2.8.0) - - '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.15.32)(tsx@4.20.3)(yaml@2.8.0))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - vite: 6.3.5(@types/node@22.15.32)(tsx@4.20.3)(yaml@2.8.0) - - '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.0.3)(tsx@4.20.3)(yaml@2.8.0))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - vite: 6.3.5(@types/node@24.0.3)(tsx@4.20.3)(yaml@2.8.0) - '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.0.4)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.2.4 @@ -7533,7 +7462,7 @@ snapshots: dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@22.15.32)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.0.4)(tsx@4.20.3)(yaml@2.8.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -7575,7 +7504,7 @@ snapshots: dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@22.15.32)(tsx@3.14.0)(yaml@2.8.0)) + '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.0.4)(tsx@4.20.3)(yaml@2.8.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -7616,7 +7545,7 @@ snapshots: dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.0.3)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.0.4)(tsx@4.20.3)(yaml@2.8.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4