From fd2930406428a9888fad30100c75578140357af7 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Tue, 10 Jun 2025 14:15:40 -0700 Subject: [PATCH] chore: rename actors -> workers --- clients/python/pyproject.toml | 2 +- clients/python/tests/common.py | 10 +- clients/rust/Cargo.toml | 2 +- clients/rust/tests/e2e.rs | 18 +- docs/clients/javascript.mdx | 16 +- docs/clients/python.mdx | 14 +- docs/clients/rust.mdx | 12 +- docs/concepts/cors.mdx | 8 +- docs/concepts/edge.mdx | 16 +- docs/concepts/external-sql.mdx | 28 +- ...ctors.mdx => interacting-with-workers.mdx} | 128 ++-- docs/concepts/logging.mdx | 20 +- docs/concepts/overview.mdx | 84 +-- docs/concepts/scaling.mdx | 40 +- docs/concepts/testing.mdx | 62 +- docs/concepts/topology.mdx | 8 +- docs/docs.json | 52 +- docs/drivers/build.mdx | 10 +- docs/drivers/cloudflare-workers.mdx | 2 +- docs/drivers/file-system.mdx | 8 +- docs/drivers/memory.mdx | 8 +- docs/drivers/overview.mdx | 4 +- docs/drivers/redis.mdx | 10 +- docs/drivers/rivet.mdx | 4 +- docs/frameworks/react.mdx | 68 +- docs/integrations/hono.mdx | 46 +- docs/integrations/resend.mdx | 44 +- docs/introduction.mdx | 191 +++--- docs/llm/claude.mdx | 28 +- docs/llm/cursor.mdx | 26 +- docs/llm/prompt.mdx | 48 +- docs/llm/windsurf.mdx | 26 +- docs/openapi.json | 32 +- docs/platforms/bun.mdx | 2 +- docs/platforms/cloudflare-workers.mdx | 24 +- docs/platforms/nodejs.mdx | 2 +- docs/platforms/rivet.mdx | 18 +- docs/snippets/cloudflare-deploy.mdx | 6 +- docs/snippets/create-actor-cli.mdx | 10 +- docs/snippets/examples/ai-agent-js.mdx | 4 +- docs/snippets/examples/ai-agent-react.mdx | 24 +- docs/snippets/examples/ai-agent-sqlite.mdx | 6 +- docs/snippets/examples/chat-room-js.mdx | 4 +- docs/snippets/examples/chat-room-react.mdx | 22 +- docs/snippets/examples/chat-room-sqlite.mdx | 4 +- docs/snippets/examples/crdt-js.mdx | 6 +- docs/snippets/examples/crdt-react.mdx | 20 +- docs/snippets/examples/crdt-sqlite.mdx | 6 +- docs/snippets/examples/database-js.mdx | 6 +- docs/snippets/examples/database-react.mdx | 28 +- docs/snippets/examples/database-sqlite.mdx | 6 +- docs/snippets/examples/document-js.mdx | 4 +- docs/snippets/examples/document-react.mdx | 28 +- docs/snippets/examples/document-sqlite.mdx | 4 +- docs/snippets/examples/game-js.mdx | 4 +- docs/snippets/examples/game-react.mdx | 16 +- docs/snippets/examples/game-sqlite.mdx | 4 +- docs/snippets/examples/rate-js.mdx | 4 +- docs/snippets/examples/rate-react.mdx | 12 +- docs/snippets/examples/rate-sqlite.mdx | 4 +- docs/snippets/examples/stream-js.mdx | 4 +- docs/snippets/examples/stream-react.mdx | 22 +- docs/snippets/examples/stream-sqlite.mdx | 4 +- docs/snippets/examples/sync-js.mdx | 4 +- docs/snippets/examples/sync-react.mdx | 38 +- docs/snippets/examples/sync-sqlite.mdx | 4 +- docs/snippets/examples/tenant-js.mdx | 6 +- docs/snippets/examples/tenant-react.mdx | 20 +- docs/snippets/examples/tenant-sqlite.mdx | 6 +- docs/snippets/landing-comparison-table.mdx | 2 +- docs/snippets/landing-faq.mdx | 8 +- docs/snippets/landing-manifesto.mdx | 2 +- docs/snippets/landing-snippets.mdx | 40 +- docs/snippets/landing-tech.mdx | 4 +- docs/snippets/platform-extra-notes.mdx | 2 +- docs/snippets/setup-actor.mdx | 8 +- docs/snippets/setup-next-steps.mdx | 2 +- docs/snippets/step-define-actor.mdx | 14 +- docs/snippets/step-run-studio.mdx | 8 +- docs/snippets/step-update-client.mdx | 2 +- docs/styles/cta.js | 34 +- docs/styles/particles.js | 12 +- docs/styles/style.css | 1 - docs/{actor => workers}/actions.mdx | 48 +- docs/{actor => workers}/authentication.mdx | 32 +- docs/{actor => workers}/connections.mdx | 34 +- docs/{actor => workers}/events.mdx | 48 +- docs/{actor => workers}/lifecycle.mdx | 106 ++-- docs/{actor => workers}/metadata.mdx | 16 +- docs/{actor => workers}/overview.mdx | 116 ++-- docs/{actor => workers}/quickstart.mdx | 14 +- docs/{actor => workers}/schedule.mdx | 10 +- docs/{actor => workers}/state.mdx | 60 +- docs/{actor => workers}/types.mdx | 22 +- docs/workflows/overview.mdx | 8 + examples/chat-room-python/actors/app.ts | 2 +- examples/chat-room-python/package.json | 2 +- examples/chat-room/actors/app.ts | 2 +- examples/chat-room/package.json | 2 +- examples/chat-room/scripts/cli.ts | 2 +- examples/chat-room/scripts/connect.ts | 2 +- examples/chat-room/tests/chat-room.test.ts | 2 +- examples/counter/actors/app.ts | 2 +- examples/counter/package.json | 2 +- examples/counter/scripts/connect.ts | 2 +- examples/counter/tests/counter.test.ts | 2 +- examples/linear-coding-agent/package.json | 2 +- .../linear-coding-agent/src/actors/app.ts | 2 +- .../src/actors/coding-agent/mod.ts | 2 +- .../linear-coding-agent/src/server/index.ts | 2 +- examples/resend-streaks/actors/app.ts | 2 +- examples/resend-streaks/package.json | 2 +- examples/resend-streaks/tests/user.test.ts | 2 +- examples/snippets/ai-agent/App.tsx | 2 +- examples/snippets/ai-agent/actor-json.ts | 2 +- examples/snippets/ai-agent/actor-sqlite.ts | 2 +- examples/snippets/chat-room/App.tsx | 2 +- examples/snippets/chat-room/actor-json.ts | 2 +- examples/snippets/chat-room/actor-sqlite.ts | 2 +- examples/snippets/crdt/App.tsx | 2 +- examples/snippets/crdt/actor-json.ts | 2 +- examples/snippets/crdt/actor-sqlite.ts | 2 +- examples/snippets/database/App.tsx | 2 +- examples/snippets/database/actor-json.ts | 2 +- examples/snippets/database/actor-sqlite.ts | 2 +- examples/snippets/document/App.tsx | 2 +- examples/snippets/document/actor-json.ts | 2 +- examples/snippets/document/actor-sqlite.ts | 2 +- examples/snippets/game/App.tsx | 2 +- examples/snippets/game/actor-json.ts | 2 +- examples/snippets/game/actor-sqlite.ts | 2 +- examples/snippets/rate/App.tsx | 2 +- examples/snippets/rate/actor-json.ts | 2 +- examples/snippets/rate/actor-sqlite.ts | 2 +- examples/snippets/stream/App.tsx | 2 +- examples/snippets/stream/actor-json.ts | 2 +- examples/snippets/stream/actor-sqlite.ts | 2 +- examples/snippets/sync/App.tsx | 2 +- examples/snippets/sync/actor-json.ts | 2 +- examples/snippets/sync/actor-sqlite.ts | 2 +- examples/snippets/tenant/App.tsx | 2 +- examples/snippets/tenant/actor-json.ts | 2 +- examples/snippets/tenant/actor-sqlite.ts | 2 +- packages/actor/src/actor/definition.ts | 37 -- packages/actor/src/actor/log.ts | 16 - packages/actor/src/actor/mod.ts | 28 - packages/actor/src/actor/schedule.ts | 17 - packages/actor/src/client/actor-common.ts | 37 -- packages/actor/src/client/actor-handle.ts | 151 ----- packages/actor/src/client/client.ts | 593 ------------------ .../driver-test-suite/tests/actor-driver.ts | 16 - packages/actor/src/manager/protocol/mod.ts | 24 - packages/actor/src/test/driver/actor.ts | 40 -- .../actor/src/test/driver/global-state.ts | 70 --- packages/actor/src/test/driver/manager.ts | 145 ----- .../actor/src/topologies/partition/mod.ts | 1 - packages/actor/tests/actor-types.test.ts | 51 -- packages/{actor => core}/README.md | 0 .../driver-test-suite/action-inputs.ts | 8 +- .../driver-test-suite/action-timeout.ts | 28 +- .../driver-test-suite/action-types.ts | 22 +- .../fixtures/driver-test-suite/conn-params.ts | 6 +- .../fixtures/driver-test-suite/conn-state.ts | 6 +- .../fixtures/driver-test-suite/counter.ts | 6 +- .../driver-test-suite/error-handling.ts | 16 +- .../fixtures/driver-test-suite/lifecycle.ts | 6 +- .../fixtures/driver-test-suite/metadata.ts | 22 +- .../fixtures/driver-test-suite/scheduled.ts | 6 +- .../fixtures/driver-test-suite/vars.ts | 38 +- packages/{actor => core}/package.json | 22 +- .../{actor => core}/scripts/dump-openapi.ts | 10 +- packages/{actor => core}/src/app/config.ts | 30 +- .../src/app/inline-client-driver.ts | 100 +-- packages/{actor => core}/src/app/log.ts | 2 +- packages/{actor => core}/src/app/mod.ts | 10 +- packages/core/src/client/client.ts | 593 ++++++++++++++++++ packages/{actor => core}/src/client/errors.ts | 18 +- .../src/client/http-client-driver.ts | 64 +- packages/{actor => core}/src/client/log.ts | 2 +- packages/{actor => core}/src/client/mod.ts | 40 +- packages/{actor => core}/src/client/test.ts | 0 packages/{actor => core}/src/client/utils.ts | 6 +- packages/core/src/client/worker-common.ts | 37 ++ .../src/client/worker-conn.ts} | 98 +-- packages/core/src/client/worker-handle.ts | 151 +++++ .../{actor => core}/src/common/eventsource.ts | 0 .../{actor => core}/src/common/log-levels.ts | 0 packages/{actor => core}/src/common/log.ts | 0 packages/{actor => core}/src/common/logfmt.ts | 0 .../{actor => core}/src/common/network.ts | 0 packages/{actor => core}/src/common/router.ts | 8 +- packages/{actor => core}/src/common/utils.ts | 12 +- .../{actor => core}/src/common/websocket.ts | 0 .../src/driver-helpers/config.ts | 8 +- .../{actor => core}/src/driver-helpers/mod.ts | 10 +- .../src/driver-test-suite/log.ts | 0 .../src/driver-test-suite/mod.ts | 42 +- .../src/driver-test-suite/test-apps.ts | 0 .../tests/action-features.ts | 30 +- .../driver-test-suite/tests/manager-driver.ts | 108 ++-- .../tests/worker-conn-state.ts} | 30 +- .../driver-test-suite/tests/worker-conn.ts} | 24 +- .../driver-test-suite/tests/worker-driver.ts | 16 + .../tests/worker-error-handling.ts} | 32 +- .../driver-test-suite/tests/worker-handle.ts} | 52 +- .../tests/worker-metadata.ts} | 50 +- .../tests/worker-schedule.ts} | 8 +- .../driver-test-suite/tests/worker-state.ts} | 16 +- .../driver-test-suite/tests/worker-vars.ts} | 26 +- .../src/driver-test-suite/utils.ts | 6 +- .../{actor => core}/src/inspector/common.ts | 8 +- .../{actor => core}/src/inspector/config.ts | 0 .../{actor => core}/src/inspector/manager.ts | 26 +- packages/{actor => core}/src/inspector/mod.ts | 2 +- .../src/inspector/protocol/manager}/mod.ts | 0 .../inspector/protocol/manager/to-client.ts | 10 +- .../inspector/protocol/manager/to-server.ts | 2 +- .../src/inspector/protocol/worker}/mod.ts | 0 .../inspector/protocol/worker}/to-client.ts | 0 .../inspector/protocol/worker}/to-server.ts | 0 .../actor.ts => core/src/inspector/worker.ts} | 54 +- .../{actor => core}/src/manager/driver.ts | 24 +- packages/{actor => core}/src/manager/log.ts | 2 +- packages/{actor => core}/src/manager/mod.ts | 0 packages/core/src/manager/protocol/mod.ts | 24 + .../src/manager/protocol/query.ts | 32 +- .../{actor => core}/src/manager/router.ts | 190 +++--- packages/{actor => core}/src/mod.ts | 2 +- packages/{actor => core}/src/test/config.ts | 0 packages/core/src/test/driver/global-state.ts | 70 +++ .../{actor => core}/src/test/driver/log.ts | 0 packages/core/src/test/driver/manager.ts | 145 +++++ .../{actor => core}/src/test/driver/mod.ts | 2 +- packages/core/src/test/driver/worker.ts | 40 ++ packages/{actor => core}/src/test/log.ts | 0 packages/{actor => core}/src/test/mod.ts | 24 +- .../topologies/common/generic-conn-driver.ts | 24 +- .../src/topologies/common/log.ts | 0 .../src/topologies/coordinate/conn/driver.ts | 28 +- .../src/topologies/coordinate/conn/mod.ts | 50 +- .../src/topologies/coordinate/driver.ts | 28 +- .../src/topologies/coordinate/log.ts | 2 +- .../src/topologies/coordinate/mod.ts | 0 .../src/topologies/coordinate/node/message.ts | 20 +- .../src/topologies/coordinate/node/mod.ts | 60 +- .../topologies/coordinate/node/protocol.ts | 10 +- .../src/topologies/coordinate/router/sse.ts | 14 +- .../topologies/coordinate/router/websocket.ts | 22 +- .../src/topologies/coordinate/topology.ts | 30 +- .../src/topologies/coordinate/worker-peer.ts} | 184 +++--- .../{actor => core}/src/topologies/mod.ts | 2 +- .../src/topologies/partition/log.ts | 2 +- packages/core/src/topologies/partition/mod.ts | 1 + .../src/topologies/partition/topology.ts | 156 ++--- .../topologies/partition/worker-router.ts} | 40 +- .../src/topologies/standalone/log.ts | 2 +- .../src/topologies/standalone/mod.ts | 0 .../src/topologies/standalone/topology.ts | 136 ++-- packages/{actor => core}/src/utils.ts | 2 +- .../src/actor => core/src/worker}/action.ts | 54 +- .../src/actor => core/src/worker}/config.ts | 74 +-- .../src/worker}/conn-routing-handler.ts | 16 +- .../actor => core/src/worker}/connection.ts | 20 +- .../src/actor => core/src/worker}/context.ts | 52 +- packages/core/src/worker/definition.ts | 37 ++ .../src/actor => core/src/worker}/driver.ts | 22 +- .../src/actor => core/src/worker}/errors.ts | 70 +-- .../src/actor => core/src/worker}/instance.ts | 162 ++--- packages/core/src/worker/log.ts | 16 + packages/core/src/worker/mod.ts | 28 + .../actor => core/src/worker}/persisted.ts | 2 +- .../src/worker}/protocol/http/action.ts | 0 .../src/worker}/protocol/http/error.ts | 0 .../src/worker}/protocol/http/resolve.ts | 2 +- .../src/worker}/protocol/message/mod.ts | 16 +- .../src/worker}/protocol/message/to-client.ts | 2 +- .../src/worker}/protocol/message/to-server.ts | 0 .../src/worker}/protocol/serde.ts | 4 +- .../src/worker}/router-endpoints.ts | 50 +- .../src/actor => core/src/worker}/router.ts | 38 +- packages/core/src/worker/schedule.ts | 17 + .../src/worker}/unstable-react.ts | 8 +- .../src/actor => core/src/worker}/utils.ts | 0 .../tests/driver-test-suite.test.ts | 4 +- packages/core/tests/worker-types.test.ts | 51 ++ packages/{actor => core}/tsconfig.json | 2 +- .../tsup.config.bundled_xvi1jgwbzx.mjs | 0 packages/{actor => core}/tsup.config.ts | 0 packages/{actor => core}/turbo.json | 0 packages/{actor => core}/vitest.config.ts | 0 packages/drivers/file-system/package.json | 4 +- packages/drivers/file-system/src/actor.ts | 48 -- .../drivers/file-system/src/global-state.ts | 168 ++--- packages/drivers/file-system/src/log.ts | 2 +- packages/drivers/file-system/src/manager.ts | 70 +-- packages/drivers/file-system/src/mod.ts | 2 +- packages/drivers/file-system/src/utils.ts | 8 +- packages/drivers/file-system/src/worker.ts | 48 ++ .../file-system/tests/driver-tests.test.ts | 8 +- packages/drivers/memory/package.json | 4 +- packages/drivers/memory/src/actor.ts | 35 -- packages/drivers/memory/src/global-state.ts | 62 +- packages/drivers/memory/src/log.ts | 2 +- packages/drivers/memory/src/manager.ts | 76 +-- packages/drivers/memory/src/mod.ts | 2 +- packages/drivers/memory/src/worker.ts | 35 ++ .../drivers/memory/tests/driver-tests.test.ts | 6 +- packages/drivers/redis/package.json | 4 +- packages/drivers/redis/src/actor.ts | 46 -- packages/drivers/redis/src/coordinate.ts | 68 +- packages/drivers/redis/src/keys.ts | 12 +- packages/drivers/redis/src/log.ts | 2 +- packages/drivers/redis/src/manager.ts | 120 ++-- packages/drivers/redis/src/mod.ts | 2 +- packages/drivers/redis/src/worker.ts | 46 ++ .../drivers/redis/tests/driver-tests.test.ts | 8 +- .../frameworks/framework-base/package.json | 4 +- packages/frameworks/framework-base/src/mod.ts | 66 +- packages/frameworks/react/README.md | 2 +- packages/frameworks/react/package.json | 6 +- packages/frameworks/react/src/mod.tsx | 2 +- packages/platforms/bun/package.json | 6 +- packages/platforms/bun/src/config.ts | 2 +- packages/platforms/bun/src/log.ts | 2 +- packages/platforms/bun/src/mod.ts | 28 +- .../platforms/cloudflare-workers/package.json | 4 +- .../cloudflare-workers/src/actor-driver.ts | 66 -- .../cloudflare-workers/src/config.ts | 2 +- .../cloudflare-workers/src/handler.ts | 62 +- .../platforms/cloudflare-workers/src/log.ts | 2 +- .../cloudflare-workers/src/manager-driver.ts | 136 ++-- .../platforms/cloudflare-workers/src/util.ts | 2 +- .../cloudflare-workers/src/worker-driver.ts | 66 ++ ...tor-handler-do.ts => worker-handler-do.ts} | 96 +-- .../tests/driver-tests.test.ts | 24 +- .../tests/id-generation.test.ts | 18 +- .../tests/key-indexes.test.ts | 26 +- .../tests/key-serialization.test.ts | 4 +- packages/platforms/nodejs/package.json | 6 +- packages/platforms/nodejs/src/config.ts | 2 +- packages/platforms/nodejs/src/log.ts | 2 +- packages/platforms/nodejs/src/mod.ts | 26 +- packages/platforms/rivet/package.json | 4 +- packages/platforms/rivet/src/actor-driver.ts | 55 -- packages/platforms/rivet/src/config.ts | 6 +- packages/platforms/rivet/src/log.ts | 2 +- .../platforms/rivet/src/manager-driver.ts | 166 ++--- .../platforms/rivet/src/manager-handler.ts | 40 +- packages/platforms/rivet/src/mod.ts | 2 +- packages/platforms/rivet/src/rivet-client.ts | 2 +- packages/platforms/rivet/src/util.ts | 4 +- packages/platforms/rivet/src/worker-driver.ts | 55 ++ .../{actor-handler.ts => worker-handler.ts} | 52 +- .../platforms/rivet/tests/deployment.test.ts | 16 +- .../rivet/tests/driver-tests.test.ts | 26 +- .../platforms/rivet/tests/rivet-deploy.ts | 14 +- scripts/release.ts | 4 +- yarn.lock | 114 ++-- 358 files changed, 4793 insertions(+), 4797 deletions(-) rename docs/concepts/{interacting-with-actors.mdx => interacting-with-workers.mdx} (80%) rename docs/{actor => workers}/actions.mdx (81%) rename docs/{actor => workers}/authentication.mdx (80%) rename docs/{actor => workers}/connections.mdx (79%) rename docs/{actor => workers}/events.mdx (62%) rename docs/{actor => workers}/lifecycle.mdx (70%) rename docs/{actor => workers}/metadata.mdx (76%) rename docs/{actor => workers}/overview.mdx (54%) rename docs/{actor => workers}/quickstart.mdx (79%) rename docs/{actor => workers}/schedule.mdx (85%) rename docs/{actor => workers}/state.mdx (68%) rename docs/{actor => workers}/types.mdx (58%) create mode 100644 docs/workflows/overview.mdx delete mode 100644 packages/actor/src/actor/definition.ts delete mode 100644 packages/actor/src/actor/log.ts delete mode 100644 packages/actor/src/actor/mod.ts delete mode 100644 packages/actor/src/actor/schedule.ts delete mode 100644 packages/actor/src/client/actor-common.ts delete mode 100644 packages/actor/src/client/actor-handle.ts delete mode 100644 packages/actor/src/client/client.ts delete mode 100644 packages/actor/src/driver-test-suite/tests/actor-driver.ts delete mode 100644 packages/actor/src/manager/protocol/mod.ts delete mode 100644 packages/actor/src/test/driver/actor.ts delete mode 100644 packages/actor/src/test/driver/global-state.ts delete mode 100644 packages/actor/src/test/driver/manager.ts delete mode 100644 packages/actor/src/topologies/partition/mod.ts delete mode 100644 packages/actor/tests/actor-types.test.ts rename packages/{actor => core}/README.md (100%) rename packages/{actor => core}/fixtures/driver-test-suite/action-inputs.ts (75%) rename packages/{actor => core}/fixtures/driver-test-suite/action-timeout.ts (72%) rename packages/{actor => core}/fixtures/driver-test-suite/action-types.ts (86%) rename packages/{actor => core}/fixtures/driver-test-suite/conn-params.ts (83%) rename packages/{actor => core}/fixtures/driver-test-suite/conn-state.ts (95%) rename packages/{actor => core}/fixtures/driver-test-suite/counter.ts (75%) rename packages/{actor => core}/fixtures/driver-test-suite/error-handling.ts (88%) rename packages/{actor => core}/fixtures/driver-test-suite/lifecycle.ts (86%) rename packages/{actor => core}/fixtures/driver-test-suite/metadata.ts (80%) rename packages/{actor => core}/fixtures/driver-test-suite/scheduled.ts (94%) rename packages/{actor => core}/fixtures/driver-test-suite/vars.ts (68%) rename packages/{actor => core}/package.json (85%) rename packages/{actor => core}/scripts/dump-openapi.ts (87%) rename packages/{actor => core}/src/app/config.ts (70%) rename packages/{actor => core}/src/app/inline-client-driver.ts (65%) rename packages/{actor => core}/src/app/log.ts (72%) rename packages/{actor => core}/src/app/mod.ts (69%) create mode 100644 packages/core/src/client/client.ts rename packages/{actor => core}/src/client/errors.ts (55%) rename packages/{actor => core}/src/client/http-client-driver.ts (72%) rename packages/{actor => core}/src/client/log.ts (70%) rename packages/{actor => core}/src/client/mod.ts (51%) rename packages/{actor => core}/src/client/test.ts (100%) rename packages/{actor => core}/src/client/utils.ts (95%) create mode 100644 packages/core/src/client/worker-common.ts rename packages/{actor/src/client/actor-conn.ts => core/src/client/worker-conn.ts} (88%) create mode 100644 packages/core/src/client/worker-handle.ts rename packages/{actor => core}/src/common/eventsource.ts (100%) rename packages/{actor => core}/src/common/log-levels.ts (100%) rename packages/{actor => core}/src/common/log.ts (100%) rename packages/{actor => core}/src/common/logfmt.ts (100%) rename packages/{actor => core}/src/common/network.ts (100%) rename packages/{actor => core}/src/common/router.ts (84%) rename packages/{actor => core}/src/common/utils.ts (90%) rename packages/{actor => core}/src/common/websocket.ts (100%) rename packages/{actor => core}/src/driver-helpers/config.ts (80%) rename packages/{actor => core}/src/driver-helpers/mod.ts (62%) rename packages/{actor => core}/src/driver-test-suite/log.ts (100%) rename packages/{actor => core}/src/driver-test-suite/mod.ts (79%) rename packages/{actor => core}/src/driver-test-suite/test-apps.ts (100%) rename packages/{actor => core}/src/driver-test-suite/tests/action-features.ts (84%) rename packages/{actor => core}/src/driver-test-suite/tests/manager-driver.ts (78%) rename packages/{actor/src/driver-test-suite/tests/actor-conn-state.ts => core/src/driver-test-suite/tests/worker-conn-state.ts} (89%) rename packages/{actor/src/driver-test-suite/tests/actor-conn.ts => core/src/driver-test-suite/tests/worker-conn.ts} (93%) create mode 100644 packages/core/src/driver-test-suite/tests/worker-driver.ts rename packages/{actor/src/driver-test-suite/tests/actor-error-handling.ts => core/src/driver-test-suite/tests/worker-error-handling.ts} (82%) rename packages/{actor/src/driver-test-suite/tests/actor-handle.ts => core/src/driver-test-suite/tests/worker-handle.ts} (84%) rename packages/{actor/src/driver-test-suite/tests/actor-metadata.ts => core/src/driver-test-suite/tests/worker-metadata.ts} (72%) rename packages/{actor/src/driver-test-suite/tests/actor-schedule.ts => core/src/driver-test-suite/tests/worker-schedule.ts} (94%) rename packages/{actor/src/driver-test-suite/tests/actor-state.ts => core/src/driver-test-suite/tests/worker-state.ts} (80%) rename packages/{actor/src/driver-test-suite/tests/actor-vars.ts => core/src/driver-test-suite/tests/worker-vars.ts} (78%) rename packages/{actor => core}/src/driver-test-suite/utils.ts (90%) rename packages/{actor => core}/src/inspector/common.ts (95%) rename packages/{actor => core}/src/inspector/config.ts (100%) rename packages/{actor => core}/src/inspector/manager.ts (77%) rename packages/{actor => core}/src/inspector/mod.ts (51%) rename packages/{actor/src/inspector/protocol/actor => core/src/inspector/protocol/manager}/mod.ts (100%) rename packages/{actor => core}/src/inspector/protocol/manager/to-client.ts (67%) rename packages/{actor => core}/src/inspector/protocol/manager/to-server.ts (90%) rename packages/{actor/src/inspector/protocol/manager => core/src/inspector/protocol/worker}/mod.ts (100%) rename packages/{actor/src/inspector/protocol/actor => core/src/inspector/protocol/worker}/to-client.ts (100%) rename packages/{actor/src/inspector/protocol/actor => core/src/inspector/protocol/worker}/to-server.ts (100%) rename packages/{actor/src/inspector/actor.ts => core/src/inspector/worker.ts} (52%) rename packages/{actor => core}/src/manager/driver.ts (60%) rename packages/{actor => core}/src/manager/log.ts (70%) rename packages/{actor => core}/src/manager/mod.ts (100%) create mode 100644 packages/core/src/manager/protocol/mod.ts rename packages/{actor => core}/src/manager/protocol/query.ts (69%) rename packages/{actor => core}/src/manager/router.ts (80%) rename packages/{actor => core}/src/mod.ts (67%) rename packages/{actor => core}/src/test/config.ts (100%) create mode 100644 packages/core/src/test/driver/global-state.ts rename packages/{actor => core}/src/test/driver/log.ts (100%) create mode 100644 packages/core/src/test/driver/manager.ts rename packages/{actor => core}/src/test/driver/mod.ts (68%) create mode 100644 packages/core/src/test/driver/worker.ts rename packages/{actor => core}/src/test/log.ts (100%) rename packages/{actor => core}/src/test/mod.ts (89%) rename packages/{actor => core}/src/topologies/common/generic-conn-driver.ts (85%) rename packages/{actor => core}/src/topologies/common/log.ts (100%) rename packages/{actor => core}/src/topologies/coordinate/conn/driver.ts (62%) rename packages/{actor => core}/src/topologies/coordinate/conn/mod.ts (76%) rename packages/{actor => core}/src/topologies/coordinate/driver.ts (63%) rename packages/{actor => core}/src/topologies/coordinate/log.ts (68%) rename packages/{actor => core}/src/topologies/coordinate/mod.ts (100%) rename packages/{actor => core}/src/topologies/coordinate/node/message.ts (85%) rename packages/{actor => core}/src/topologies/coordinate/node/mod.ts (75%) rename packages/{actor => core}/src/topologies/coordinate/node/protocol.ts (91%) rename packages/{actor => core}/src/topologies/coordinate/router/sse.ts (79%) rename packages/{actor => core}/src/topologies/coordinate/router/websocket.ts (82%) rename packages/{actor => core}/src/topologies/coordinate/topology.ts (86%) rename packages/{actor/src/topologies/coordinate/actor-peer.ts => core/src/topologies/coordinate/worker-peer.ts} (57%) rename packages/{actor => core}/src/topologies/mod.ts (79%) rename packages/{actor => core}/src/topologies/partition/log.ts (68%) create mode 100644 packages/core/src/topologies/partition/mod.ts rename packages/{actor => core}/src/topologies/partition/topology.ts (60%) rename packages/{actor/src/topologies/partition/actor-router.ts => core/src/topologies/partition/worker-router.ts} (82%) rename packages/{actor => core}/src/topologies/standalone/log.ts (68%) rename packages/{actor => core}/src/topologies/standalone/mod.ts (100%) rename packages/{actor => core}/src/topologies/standalone/topology.ts (63%) rename packages/{actor => core}/src/utils.ts (93%) rename packages/{actor/src/actor => core/src/worker}/action.ts (55%) rename packages/{actor/src/actor => core/src/worker}/config.ts (78%) rename packages/{actor/src/actor => core/src/worker}/conn-routing-handler.ts (80%) rename packages/{actor/src/actor => core/src/worker}/connection.ts (84%) rename packages/{actor/src/actor => core/src/worker}/context.ts (56%) create mode 100644 packages/core/src/worker/definition.ts rename packages/{actor/src/actor => core/src/worker}/driver.ts (51%) rename packages/{actor/src/actor => core/src/worker}/errors.ts (73%) rename packages/{actor/src/actor => core/src/worker}/instance.ts (86%) create mode 100644 packages/core/src/worker/log.ts create mode 100644 packages/core/src/worker/mod.ts rename packages/{actor/src/actor => core/src/worker}/persisted.ts (94%) rename packages/{actor/src/actor => core/src/worker}/protocol/http/action.ts (100%) rename packages/{actor/src/actor => core/src/worker}/protocol/http/error.ts (100%) rename packages/{actor/src/actor => core/src/worker}/protocol/http/resolve.ts (92%) rename packages/{actor/src/actor => core/src/worker}/protocol/message/mod.ts (90%) rename packages/{actor/src/actor => core/src/worker}/protocol/message/to-client.ts (98%) rename packages/{actor/src/actor => core/src/worker}/protocol/message/to-server.ts (100%) rename packages/{actor/src/actor => core/src/worker}/protocol/serde.ts (96%) rename packages/{actor/src/actor => core/src/worker}/router-endpoints.ts (92%) rename packages/{actor/src/actor => core/src/worker}/router.ts (84%) create mode 100644 packages/core/src/worker/schedule.ts rename packages/{actor/src/actor => core/src/worker}/unstable-react.ts (94%) rename packages/{actor/src/actor => core/src/worker}/utils.ts (100%) rename packages/{actor => core}/tests/driver-test-suite.test.ts (80%) create mode 100644 packages/core/tests/worker-types.test.ts rename packages/{actor => core}/tsconfig.json (86%) rename packages/{actor => core}/tsup.config.bundled_xvi1jgwbzx.mjs (100%) rename packages/{actor => core}/tsup.config.ts (100%) rename packages/{actor => core}/turbo.json (100%) rename packages/{actor => core}/vitest.config.ts (100%) delete mode 100644 packages/drivers/file-system/src/actor.ts create mode 100644 packages/drivers/file-system/src/worker.ts delete mode 100644 packages/drivers/memory/src/actor.ts create mode 100644 packages/drivers/memory/src/worker.ts delete mode 100644 packages/drivers/redis/src/actor.ts create mode 100644 packages/drivers/redis/src/worker.ts delete mode 100644 packages/platforms/cloudflare-workers/src/actor-driver.ts create mode 100644 packages/platforms/cloudflare-workers/src/worker-driver.ts rename packages/platforms/cloudflare-workers/src/{actor-handler-do.ts => worker-handler-do.ts} (56%) delete mode 100644 packages/platforms/rivet/src/actor-driver.ts create mode 100644 packages/platforms/rivet/src/worker-driver.ts rename packages/platforms/rivet/src/{actor-handler.ts => worker-handler.ts} (71%) diff --git a/clients/python/pyproject.toml b/clients/python/pyproject.toml index 48b7d73c6..b22c01591 100644 --- a/clients/python/pyproject.toml +++ b/clients/python/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "@rivetkit/actor-client" +name = "rivetkit-client" version = "0.9.0-rc.1" authors = [ { name="Rivet Gaming, LLC", email="developer@rivet.gg" }, diff --git a/clients/python/tests/common.py b/clients/python/tests/common.py index 530c35953..beb0245a3 100644 --- a/clients/python/tests/common.py +++ b/clients/python/tests/common.py @@ -42,7 +42,7 @@ def start_mock_server(): # Build actor-core logger.info("Building actor-core") subprocess.run( - ["yarn", "build", "-F", "@rivetkit/actor"], + ["yarn", "build", "-F", "rivetkit"], cwd=repo_root, check=True ) @@ -58,7 +58,7 @@ def start_mock_server(): # Pack packages packages = [ - ("@rivetkit/actor", repo_root / "packages/actor-core"), + ("rivetkit", repo_root / "packages/actor-core"), ("nodejs", repo_root / "packages/platforms/nodejs"), ("memory", repo_root / "packages/drivers/memory"), ("file-system", repo_root / "packages/drivers/file-system") @@ -66,7 +66,7 @@ def start_mock_server(): logger.info("Packing packages (3 total)") for name, path in packages: - output_path = vendor_dir / f"@rivetkit/actor-{name}.tgz" + output_path = vendor_dir / f"rivetkit-{name}.tgz" subprocess.run( ["yarn", "pack", "--out", str(output_path)], cwd=path, @@ -98,12 +98,12 @@ def start_mock_server(): # Create package.json logger.info("Creating package.json") package_json = { - "name": "@rivetkit/actor-python-test", + "name": "rivetkit-python-test", "packageManager": "yarn@4.2.2", "private": True, "type": "module", "dependencies": { - "@rivetkit/actor": f"file:{vendor_dir}/actor-core-actor-core.tgz", + "rivetkit": f"file:{vendor_dir}/actor-core-actor-core.tgz", "@rivetkit/nodejs": f"file:{vendor_dir}/actor-core-nodejs.tgz", "@rivetkit/memory": f"file:{vendor_dir}/actor-core-memory.tgz", "@rivetkit/file-system": f"file:{vendor_dir}/actor-core-file-system.tgz", diff --git a/clients/rust/Cargo.toml b/clients/rust/Cargo.toml index 56060c271..c623d34aa 100644 --- a/clients/rust/Cargo.toml +++ b/clients/rust/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "@rivetkit/actor-client" +name = "rivetkit-client" version = "0.9.0-rc.1" description = "Rust client for ActorCore - the Stateful Serverless Framework for building AI agents, realtime apps, and game servers" edition = "2021" diff --git a/clients/rust/tests/e2e.rs b/clients/rust/tests/e2e.rs index 60c10b022..e3f4eca7c 100644 --- a/clients/rust/tests/e2e.rs +++ b/clients/rust/tests/e2e.rs @@ -26,7 +26,7 @@ impl MockServer { // Run `yarn build -F actor-core` in the root of this repo let status = Command::new("yarn") - .args(["build", "-F", "@rivetkit/actor"]) + .args(["build", "-F", "rivetkit"]) .current_dir(&repo_root) .status() .expect("Failed to build actor-core"); @@ -46,7 +46,7 @@ impl MockServer { // Define packages to pack let packages = [ - ("@rivetkit/actor", repo_root.join("packages/actor-core")), + ("rivetkit", repo_root.join("packages/actor-core")), ("nodejs", repo_root.join("packages/platforms/nodejs")), ("memory", repo_root.join("packages/drivers/memory")), ("file-system", repo_root.join("packages/drivers/file-system")), @@ -54,7 +54,7 @@ impl MockServer { // Pack each package to the vendor directory for (name, path) in packages.iter() { - let output_path = vendor_dir.join(format!("@rivetkit/actor-{}.tgz", name)); + let output_path = vendor_dir.join(format!("rivetkit-{}.tgz", name)); println!( "Packing {} from {} to {}", name, @@ -98,12 +98,12 @@ serve(app, { port: PORT, mode: "memory" }); let package_json_path = server_dir.join("package.json"); let package_json = format!( r#"{{ - "name": "@rivetkit/actor-rust-test", + "name": "rivetkit-rust-test", "packageManager": "yarn@4.2.2", "private": true, "type": "module", "dependencies": {{ - "@rivetkit/actor": "file:{}", + "rivetkit": "file:{}", "@rivetkit/nodejs": "file:{}", "@rivetkit/memory": "file:{}", "@rivetkit/file-system": "file:{}" @@ -112,10 +112,10 @@ serve(app, { port: PORT, mode: "memory" }); "tsx": "^3.12.7" }} }}"#, - vendor_dir.join("@rivetkit/actor-actor-core.tgz").display(), - vendor_dir.join("@rivetkit/actor-nodejs.tgz").display(), - vendor_dir.join("@rivetkit/actor-memory.tgz").display(), - vendor_dir.join("@rivetkit/actor-file-system.tgz").display() + vendor_dir.join("rivetkit-actor-core.tgz").display(), + vendor_dir.join("rivetkit-nodejs.tgz").display(), + vendor_dir.join("rivetkit-memory.tgz").display(), + vendor_dir.join("rivetkit-file-system.tgz").display() ); std::fs::write(&package_json_path, package_json).expect("Failed to write package.json"); diff --git a/docs/clients/javascript.mdx b/docs/clients/javascript.mdx index f99e67a8d..7e9901d42 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 StepDefineActor from "/snippets/step-define-actor.mdx"; +import StepDefineWorker from "/snippets/step-define-worker.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 actors from browser and Node.js applications. +The RivetKit JavaScript client allows you to connect to and interact with workers from browser and Node.js applications. @@ -71,20 +71,20 @@ The RivetKit JavaScript client allows you to connect to and interact with actors - + - Create a file `src/client.ts` in your project to connect to your actor: + Create a file `src/client.ts` in your project to connect to your worker: ```typescript src/client.ts - import { createClient } from "@rivetkit/actor/client"; - import type { App } from "../actors/app"; + import { createClient } from "rivetkit/client"; + import type { App } from "../workers/app"; async function main() { // Replace with your endpoint URL after deployment const client = createClient("http://localhost:6420"); - // Get or create an actor instance + // Get or create a worker 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 actors ## Next Steps -See the [Interacting with Actors](/concepts/interacting-with-actors) documentation for information on how to use the client. +See the [Interacting with Workers](/concepts/interacting-with-workers) documentation for information on how to use the client. diff --git a/docs/clients/python.mdx b/docs/clients/python.mdx index 356e0e5c9..bc9f23ea3 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 StepDefineActor from "/snippets/step-define-actor.mdx"; +import StepDefineWorker from "/snippets/step-define-worker.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 actors from Python applications. +The RivetKit Python client provides a way to connect to and interact with workers from Python applications. @@ -39,7 +39,7 @@ The RivetKit Python client provides a way to connect to and interact with actors ``` - + Create a new file `main.py`: @@ -47,13 +47,13 @@ The RivetKit Python client provides a way to connect to and interact with actors ```python Async import asyncio - from actor_core_client import AsyncClient + from worker_core_client import AsyncClient async def main(): # Replace with your endpoint URL after deployment client = AsyncClient("http://localhost:6420") - # Get or create an actor instance + # Get or create a worker instance counter = await client.get("counter") # Subscribe to events using callback @@ -77,12 +77,12 @@ The RivetKit Python client provides a way to connect to and interact with actors ``` ```python Sync - from actor_core_client import Client + from worker_core_client import Client # Replace with your endpoint URL after deployment client = Client("http://localhost:6420") - # Get or create an actor instance + # Get or create a worker instance counter = client.get("counter") # Subscribe to events using callback diff --git a/docs/clients/rust.mdx b/docs/clients/rust.mdx index ff3e723d5..1cc12d68d 100644 --- a/docs/clients/rust.mdx +++ b/docs/clients/rust.mdx @@ -4,12 +4,12 @@ icon: rust --- import MvpWarning from "/snippets/mvp-warning.mdx"; -import StepDefineActor from "/snippets/step-define-actor.mdx"; +import StepDefineWorker from "/snippets/step-define-worker.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 Rust client provides a way to connect to and interact with actors from Rust applications. +The RivetKit Rust client provides a way to connect to and interact with workers from Rust applications. @@ -35,13 +35,13 @@ The RivetKit Rust client provides a way to connect to and interact with actors f ``` - + - Modify `src/main.rs` to connect to your actor: + Modify `src/main.rs` to connect to your worker: ```rust src/main.rs - use actor_core_client::{Client, GetOptions, TransportKind, EncodingKind}; + use worker_core_client::{Client, GetOptions, TransportKind, EncodingKind}; use serde_json::json; use std::time::Duration; @@ -54,7 +54,7 @@ The RivetKit Rust client provides a way to connect to and interact with actors f EncodingKind::Cbor, ); - // Get or create an actor instance + // Get or create a worker instance let options = GetOptions::default(); let counter = client.get("counter", options).await?; diff --git a/docs/concepts/cors.mdx b/docs/concepts/cors.mdx index f2512bc1b..4a2d9af4c 100644 --- a/docs/concepts/cors.mdx +++ b/docs/concepts/cors.mdx @@ -8,17 +8,17 @@ 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 actor service -- **Different Domain:** Your frontend application is hosted on a different domain than your actor service +- **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 ## Example ```ts -import { setup } from "@rivetkit/actor"; +import { setup } from "rivetkit"; import counter from "./counter"; const app = setup({ - actors: { counter }, + workers: { 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 f9e92df8c..52d52b7ce 100644 --- a/docs/concepts/edge.mdx +++ b/docs/concepts/edge.mdx @@ -1,6 +1,6 @@ --- title: Edge Networking -description: Actors run near your users on your provider's global network (if supported). +description: Workers 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, actors will choose the optimal region based on the client's location. +By default, workers 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,16 +17,16 @@ By default, actors will choose the optimal region based on the client's location ### Manual region selection -The region an actor is created in can be overridden using region options: +The region a worker is created in can be overridden using region options: ```typescript client.ts -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420"); -// Create actor in a specific region -const actor = await client.example.get({ +// Create worker in a specific region +const worker = await client.example.get({ options: { create: { region: "atl" @@ -35,7 +35,7 @@ const actor = await client.example.get({ }); ``` -See [Create & Manage Actors](/docs/manage) for more information. +See [Create & Manage Workers](/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/actor/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/worker/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 fa512e08d..d0204b6cf 100644 --- a/docs/concepts/external-sql.mdx +++ b/docs/concepts/external-sql.mdx @@ -3,9 +3,9 @@ title: External SQL Database icon: database --- -While actors can serve as a complete database solution, they can also complement your existing databases. For example, you might use actors to handle frequently-changing data that needs real-time access, while keeping less frequently accessed data in your traditional database. +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. -Actors can be used with common SQL databases, such as PostgreSQL and MySQL. +Workers 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 actor.ts -import { actor } from "@rivetkit/actor"; +```typescript worker.ts +import { worker } from "rivetkit"; import { Pool } from "pg"; // Create a database connection pool @@ -48,8 +48,8 @@ const pool = new Pool({ port: 5432, }); -// Create the actor -const databaseActor = actor({ +// Create the worker +const databaseWorker = worker({ state: { // Local state if needed lastQueryTime: 0 @@ -57,7 +57,7 @@ const databaseActor = actor({ // Initialize any resources onStart: (c) => { - console.log("Database actor started"); + console.log("Database worker started"); }, // Clean up resources if needed @@ -98,15 +98,15 @@ const databaseActor = actor({ } }); -export default databaseActor; +export default databaseWorker; ``` ## With Drizzle ORM Here's an example using Drizzle ORM for more type-safe database operations: -```typescript actor.ts -import { actor } from "@rivetkit/actor"; +```typescript worker.ts +import { worker } 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 actor -const userActor = actor({ +// Create the worker +const userWorker = worker({ state: { - // Actor state (frequently accessed data can be cached here) + // Worker state (frequently accessed data can be cached here) userCache: {} }, @@ -169,5 +169,5 @@ const userActor = actor({ } }); -export default userActor; +export default userWorker; ``` diff --git a/docs/concepts/interacting-with-actors.mdx b/docs/concepts/interacting-with-workers.mdx similarity index 80% rename from docs/concepts/interacting-with-actors.mdx rename to docs/concepts/interacting-with-workers.mdx index beaa92533..6834f350b 100644 --- a/docs/concepts/interacting-with-actors.mdx +++ b/docs/concepts/interacting-with-workers.mdx @@ -1,17 +1,17 @@ --- -title: Interacting with Actors +title: Interacting with Workers icon: square-code --- -This guide covers how to connect to and interact with actors from client applications. +This guide covers how to connect to and interact with workers from client applications. ## Setting Up the Client -The first step is to create a client that will connect to your actor service: +The first step is to create a client that will connect to your worker service: ```typescript TypeScript -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "../src/index"; // Create a client with the connection address and app type @@ -19,7 +19,7 @@ const client = createClient(/* CONNECTION ADDRESS */); ``` ```rust Rust -use actor_core_client::{Client, TransportKind, EncodingKind}; +use worker_core_client::{Client, TransportKind, EncodingKind}; // Create a client with connection address and configuration let client = Client::new( @@ -30,22 +30,22 @@ let client = Client::new( ``` ```python Python (Callbacks) -from actor_core_client import AsyncClient as ActorClient +from worker_core_client import AsyncClient as WorkerClient # Create a client with the connection address -client = ActorClient("http://localhost:6420") +client = WorkerClient("http://localhost:6420") ``` See the setup guide for your platform for details on how to get the connection address. -## Finding & Connecting to Actors +## Finding & Connecting to Workers -RivetKit provides several methods to connect to actors: +RivetKit provides several methods to connect to workers: ### `get(tags, opts)` - Find or Create -The most common way to connect is with `get()`, which finds an existing actor matching the provided tags or creates a new one: +The most common way to connect is with `get()`, which finds an existing worker matching the provided tags or creates a new one: ```typescript TypeScript @@ -55,12 +55,12 @@ const room = await client.chatRoom.get({ channel: "general" }); -// Now you can call methods on the actor +// Now you can call methods on the worker await room.sendMessage("Alice", "Hello everyone!"); ``` ```rust Rust -use actor_core_client::GetOptions; +use worker_core_client::GetOptions; use serde_json::json; // Connect to a chat room for the "general" channel @@ -78,7 +78,7 @@ let room = client.get("chatRoom", options) .await .expect("Failed to connect to chat room"); -// Now you can call methods on the actor +// Now you can call methods on the worker room.action("sendMessage", vec![json!("Alice"), json!("Hello everyone!")]) .await .expect("Failed to send message"); @@ -91,18 +91,18 @@ room = await client.get("chatRoom", tags=[ ("channel", "general") ]) -# Now you can call methods on the actor +# Now you can call methods on the worker await room.action("sendMessage", ["Alice", "Hello everyone!"]) ``` ### `create(opts)` - Explicitly Create New -When you specifically want to create a new actor instance: +When you specifically want to create a new worker instance: ```typescript TypeScript -// Create a new document actor +// Create a new document worker const doc = await client.myDocument.create({ create: { tags: { @@ -116,11 +116,11 @@ await doc.initializeDocument("My New Document"); ``` ```rust Rust -use actor_core_client::{CreateOptions}; -use actor_core_client::client::CreateRequestMetadata; +use worker_core_client::{CreateOptions}; +use worker_core_client::client::CreateRequestMetadata; use serde_json::json; -// Create a new document actor +// Create a new document worker let tags = vec![ ("name".to_string(), "my_document".to_string()), ("docId".to_string(), "123".to_string()), @@ -145,7 +145,7 @@ doc.action("initializeDocument", vec![json!("My New Document")]) ``` ```python Python (Callbacks) -# Create a new document actor +# Create a new document worker doc = await client.get("myDocument", tags=[ ("name", "my_document"), ("docId", "123") @@ -157,26 +157,26 @@ await doc.action("initializeDocument", ["My New Document"]) ### `getWithId(id, opts)` - Connect by ID -Connect to an actor using its internal ID: +Connect to a worker using its internal ID: ```typescript TypeScript -// Connect to a specific actor by its ID -const myActorId = "55425f42-82f8-451f-82c1-6227c83c9372"; -const doc = await client.myDocument.getWithId(myActorId); +// Connect to a specific worker by its ID +const myWorkerId = "55425f42-82f8-451f-82c1-6227c83c9372"; +const doc = await client.myDocument.getWithId(myWorkerId); await doc.updateContent("Updated content"); ``` ```rust Rust -use actor_core_client::GetWithIdOptions; +use worker_core_client::GetWithIdOptions; -// Connect to a specific actor by its ID -let my_actor_id = "55425f42-82f8-451f-82c1-6227c83c9372"; +// Connect to a specific worker by its ID +let my_worker_id = "55425f42-82f8-451f-82c1-6227c83c9372"; let options = GetWithIdOptions { params: None, }; -let doc = client.get_with_id(my_actor_id, options) +let doc = client.get_with_id(my_worker_id, options) .await .expect("Failed to connect to document"); @@ -187,21 +187,21 @@ doc.action("updateContent", vec![json!("Updated content")]) ``` ```python Python (Callbacks) -# Connect to a specific actor by its ID -my_actor_id = "55425f42-82f8-451f-82c1-6227c83c9372" -doc = await client.get_with_id(my_actor_id) +# Connect to a specific worker by its ID +my_worker_id = "55425f42-82f8-451f-82c1-6227c83c9372" +doc = await client.get_with_id(my_worker_id) await doc.action("updateContent", ["Updated content"]) ``` -It's usually better to use tags for discovery rather than directly using actor IDs. +It's usually better to use tags for discovery rather than directly using worker IDs. ## Calling Actions -Once connected, calling actor actions are straightforward: +Once connected, calling worker actions are straightforward: ```typescript TypeScript @@ -263,12 +263,12 @@ await game_room.action("updateSettings", [{ -All actor action calls are asynchronous and require `await`, even if the actor's action is not async. +All worker action calls are asynchronous and require `await`, even if the worker's action is not async. ## Listening for Events -Actors can send realtime updates to clients using events: +Workers can send realtime updates to clients using events: ### `on(eventName, callback)` - Continuous Listening @@ -335,7 +335,7 @@ For events you only need to hear once: ```typescript TypeScript // Listen for when a request is approved -actor.once("requestApproved", () => { +worker.once("requestApproved", () => { showApprovalNotification(); unlockFeatures(); }); @@ -351,7 +351,7 @@ def handle_approval(): show_approval_notification() unlock_features() -actor.on_event("requestApproved", handle_approval) +worker.on_event("requestApproved", handle_approval) ``` @@ -374,7 +374,7 @@ const chatRoom = await client.chatRoom.get({ channel: "super-secret" }, { ```rust Rust use serde_json::json; -use actor_core_client::GetOptions; +use worker_core_client::GetOptions; let tags = vec![ ("channel".to_string(), "super-secret".to_string()), @@ -411,12 +411,12 @@ chat_room = await client.get( ``` -The actor can access these parameters in the `onBeforeConnect` or `createConnState` hook: +The worker can access these parameters in the `onBeforeConnect` or `createConnState` hook: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ state: { messages: [] }, createConnState: (c, { params }) => { @@ -442,7 +442,7 @@ Read more about [connection parameters](/concepts/connections). #### `opts.noCreate` -Connect only if an actor exists, without creating a new one: +Connect only if a worker exists, without creating a new one: ```typescript try { @@ -459,7 +459,7 @@ try { ```typescript TypeScript // Example with all client options const client = createClient( - "https://actors.example.com", + "https://workers.example.com", { // Data serialization format encoding: "cbor", // or "json" @@ -471,11 +471,11 @@ const client = createClient( ``` ```rust Rust -use actor_core_client::{Client, TransportKind, EncodingKind}; +use worker_core_client::{Client, TransportKind, EncodingKind}; // Create client with specific options let client = Client::new( - "https://actors.example.com".to_string(), + "https://workers.example.com".to_string(), TransportKind::WebSocket, // or TransportKind::Sse EncodingKind::Cbor, // or EncodingKind::Json ); @@ -484,11 +484,11 @@ let client = Client::new( ``` ```python Python (Callbacks) -from actor_core_client import AsyncClient as ActorClient +from worker_core_client import AsyncClient as WorkerClient # Example with all client options -client = ActorClient( - "https://actors.example.com", +client = WorkerClient( + "https://workers.example.com", "websocket" # or "sse" "cbor", # or "json" ) @@ -510,7 +510,7 @@ Specifies the data encoding format used for communication: `("websocket" | "sse")[]` (optional) -Configures which network transport mechanisms the client will use to communicate with actors, sorted by priority: +Configures which network transport mechanisms the client will use to communicate with workers, sorted by priority: - `"websocket"`: Real-time bidirectional communication, best for most applications - `"sse"` (Server-Sent Events): Works in more restricted environments where WebSockets may be blocked @@ -527,7 +527,7 @@ When an action fails, it throws an error with details about the failure: ```typescript try { - await actor.someAction(); + await worker.someAction(); } catch (error) { console.error(`Action failed: ${error.code} - ${error.message}`); // Handle specific error codes @@ -537,12 +537,12 @@ try { } ``` -These errors can be thrown from within the actor with `UserError`: +These errors can be thrown from within the worker with `UserError`: ```typescript -import { actor, UserError } from "@rivetkit/actor"; +import { worker, UserError } from "rivetkit"; -const documentActor = actor({ +const documentWorker = worker({ state: { content: "" }, actions: { @@ -567,8 +567,8 @@ RivetKit doesn't expose internal errors to clients for security, helping to prev Other common errors you might encounter: -- `InternalError`: Error from your actor that's not a subclass of `UserError` -- `ManagerError`: Issues when connecting to or communicating with the actor manager +- `InternalError`: Error from your worker that's not a subclass of `UserError` +- `ManagerError`: Issues when connecting to or communicating with the worker manager ## Disconnecting and Cleanup @@ -578,16 +578,16 @@ If you need to explicitly disconnect: ```typescript TypeScript -// Disconnect from the actor -await actor.dispose(); +// Disconnect from the worker +await worker.dispose(); // Disconnect the entire client await client.dispose(); ``` ```rust Rust -// Disconnect from the actor -actor.disconnect().await; +// Disconnect from the worker +worker.disconnect().await; // The client will be cleaned up automatically when it goes out of scope // Or explicitly drop it with: @@ -595,8 +595,8 @@ drop(client); ``` ```python Python (Callbacks) -# Disconnect from the actor -await actor.disconnect() +# Disconnect from the worker +await worker.disconnect() ``` @@ -612,13 +612,13 @@ This makes your applications resilient to temporary network failures without any - Learn how state works in actors + Learn how state works in workers - Learn more about actor events + Learn more about worker events - Add security to your actors + Add security to your workers Manage client connections diff --git a/docs/concepts/logging.mdx b/docs/concepts/logging.mdx index c31856863..92d5be2a1 100644 --- a/docs/concepts/logging.mdx +++ b/docs/concepts/logging.mdx @@ -3,11 +3,11 @@ title: Logging icon: list-ul --- -Actors provide a built-in way to log complex data to the console. +Workers 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 actor logging API is completely optional. +Using the worker logging API is completely optional. ## Log levels @@ -46,9 +46,9 @@ Consider this example: ```typescript structured_logging.ts -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const counter = actor({ +const counter = worker({ state: { count: 0 }, actions: { @@ -64,9 +64,9 @@ const counter = actor({ ``` ```typescript unstructured_logging.ts -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const counter = actor({ +const counter = worker({ 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 { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const loggingExample = actor({ +const loggingExample = worker({ state: { events: [] }, onStart: (c) => { - c.log.info('actor_started', { timestamp: Date.now() }); + c.log.info('worker_started', { timestamp: Date.now() }); }, onBeforeConnect: (c, { params }) => { @@ -137,7 +137,7 @@ const loggingExample = actor({ }, actions: { - // Actor actions... + // Worker actions... } }); ``` diff --git a/docs/concepts/overview.mdx b/docs/concepts/overview.mdx index a8d76f791..d7ca96a45 100644 --- a/docs/concepts/overview.mdx +++ b/docs/concepts/overview.mdx @@ -3,26 +3,26 @@ title: Introduction icon: square-info --- -import CreateActorCli from "/snippets/create-actor-cli.mdx"; +import CreateWorkerCli from "/snippets/create-worker-cli.mdx"; -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. +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. ## Quickstart Run this to get started: - + ## Code Example -Here's a complete chat room actor that maintains state and handles messages. We'll explore each component in depth throughout this document: +Here's a complete chat room worker that maintains state and handles messages. We'll explore each component in depth throughout this document: ```typescript chat_room.ts -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -// Define a chat room actor -const chatRoom = actor({ - // Initialize state when the actor is first created +// Define a chat room worker +const chatRoom = worker({ + // Initialize state when the worker is first created createState: () => ({ messages: [] }), @@ -50,15 +50,15 @@ export default chatRoom; ## Using the App -To start using your actor, create an app and serve it: +To start using your worker, create an app and serve it: ```typescript app.ts -import { setup, serve } from "@rivetkit/actor"; +import { setup, serve } from "rivetkit"; import chatRoom from "./chat_room"; // Create the application const app = setup({ - actors: { chatRoom } + workers: { chatRoom } }); // Start serving on default port @@ -68,17 +68,17 @@ serve(app); export type App = typeof app; ``` -## Key Actor Components +## Key Worker Components ### State -Actors maintain state that's stored in memory and automatically persisted. State is defined either as a constant or via a `createState` function: +Workers maintain state that's stored in memory and automatically persisted. State is defined either as a constant or via a `createState` function: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; // Method 1: State constant -const counter1 = actor({ +const counter1 = worker({ state: { count: 0 }, actions: { // ... @@ -86,7 +86,7 @@ const counter1 = actor({ }); // Method 2: CreateState function -const counter2 = actor({ +const counter2 = worker({ createState: () => ({ count: 0 }), actions: { // ... @@ -97,9 +97,9 @@ const counter2 = actor({ Update state by modifying `c.state` in your actions: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const counter = actor({ +const counter = worker({ 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 actor configuration that clients & other actors can call: +Actions are functions defined in your worker configuration that clients & other workers can call: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const mathUtils = actor({ +const mathUtils = worker({ state: {}, actions: { multiplyByTwo: (c, x) => { @@ -138,12 +138,12 @@ Learn more about [actions](/concepts/actions). ### Events -Actors can broadcast events to connected clients: +Workers can broadcast events to connected clients: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const inventory = actor({ +const inventory = worker({ createState: () => ({ items: [] }), @@ -163,9 +163,9 @@ const inventory = actor({ You can also send events to specific clients: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const messageService = actor({ +const messageService = worker({ state: {}, actions: { sendPrivateMessage: (c, userId, text) => { @@ -181,12 +181,12 @@ const messageService = actor({ Learn more about [events](/concepts/events). -## Actor Tags +## Worker Tags -Tags are key-value pairs attached to actors that serve two purposes: +Tags are key-value pairs attached to workers that serve two purposes: -1. **Actor Discovery**: Find specific actors using `client.get(tags)` -2. **Organization**: Group related actors for management purposes +1. **Worker Discovery**: Find specific workers using `client.get(tags)` +2. **Organization**: Group related workers for management purposes For example, you can query chat rooms by tag like: @@ -197,7 +197,7 @@ await client.chatRoom.get({ channel: "random" }); ### Common Tag Patterns ```typescript -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420"); @@ -215,16 +215,16 @@ const document = await client.document.get({ }); ``` -## Actor Lifecycle +## Worker Lifecycle -Actors are created automatically when needed and persist until explicitly shutdown. +Workers are created automatically when needed and persist until explicitly shutdown. -To shut down an actor, use `c.shutdown()` from within an action: +To shut down a worker, use `c.shutdown()` from within an action: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ createState: () => ({ messages: [] }), @@ -233,26 +233,26 @@ const chatRoom = actor({ // Do any cleanup needed c.broadcast("roomClosed"); - // Shutdown the actor + // Shutdown the worker c.shutdown(); } } }); ``` -Learn more about the [actor lifecycle](/concepts/lifecycle). +Learn more about the [worker lifecycle](/concepts/lifecycle). ## Next Steps - - Learn how to connect to actors from clients + + Learn how to connect to workers from clients - Deep dive into actor state management + Deep dive into worker state management - Learn more about actor actions + Learn more about worker actions Learn more about realtime events diff --git a/docs/concepts/scaling.mdx b/docs/concepts/scaling.mdx index 9922803d1..13c890c07 100644 --- a/docs/concepts/scaling.mdx +++ b/docs/concepts/scaling.mdx @@ -3,49 +3,49 @@ title: Scaling & Concurrency icon: maximize --- -This document covers how actors are able to scale better than traditional applications & provides tips on architecting your actors. +This document covers how workers are able to scale better than traditional applications & provides tips on architecting your workers. -## How actors scale +## How workers scale -Actors scale by design through these key properties: +Workers scale by design through these key properties: | Property | Description | | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Independent State** | Each actor manages its own private data separately from other actors, so they never conflict with each other when running at the same time (i.e. using locking mechanisms). | -| **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. | +| **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. | -## Tips for architecting actors for scale +## Tips for architecting workers for scale -Here are key principles for architecting your actor system: +Here are key principles for architecting your worker system: **Single Responsibility** -- Each actor should represent one specific entity or concept from your application (e.g., `User`, `Document`, `ChatRoom`). -- This makes your system scale better, since actors have small scopes and do not conflict with each other. +- 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. **State Management** -- 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 +- 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 **Granularity Guidelines** -- Too coarse: Actors handling too many responsibilities become bottlenecks -- Too fine: Excessive actors create unnecessary communication overhead -- Aim for actors that can operate independently with minimal cross-actor communication +- 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 ### Examples -**Good actor boundaries** +**Good worker boundaries** - `User`: Manages user profile, preferences, and authentication - `Document`: Handles document content, metadata, and versioning - `ChatRoom`: Manages participants and message history -**Poor actor boundaries** +**Poor worker boundaries** - `Application`: Too broad, handles everything -- `DocumentWordCount`: Too granular, should be part of DocumentActor +- `DocumentWordCount`: Too granular, should be part of DocumentWorker diff --git a/docs/concepts/testing.mdx b/docs/concepts/testing.mdx index d3a3a0c2e..a5520435e 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 actor-based services. +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. ## 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 actors. 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 workers. This allows for fast, isolated tests without external dependencies. -```ts tests/my-actor.test.ts +```ts tests/my-worker.test.ts import { test, expect } from "vitest"; -import { setupTest } from "@rivetkit/actor/test"; +import { setupTest } from "rivetkit/test"; import { app } from "../src/index"; -test("my actor test", async (test) => { +test("my worker test", async (test) => { const { client } = await setupTest(test, app); - // Now you can interact with your actor through the client - const myActor = await client.myActor.get(); + // Now you can interact with your worker through the client + const myWorker = await client.myWorker.get(); - // Test your actor's functionality - await myActor.someAction(); + // Test your worker's functionality + await myWorker.someAction(); // Make assertions - const result = await myActor.getState(); + const result = await myWorker.getState(); expect(result).toEqual("updated"); }); ``` ```ts src/index.ts -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; -const myActor = actor({ +const myWorker = worker({ state: { value: "initial" }, actions: { someAction: (c) => { @@ -59,24 +59,24 @@ const myActor = actor({ }); export const app = setup({ - actors: { myActor } + workers: { myWorker } }); export type App = typeof app; ``` -## Testing Actor State +## Testing Worker State -The test framework uses in-memory drivers that persist state within each test, allowing you to verify that your actor correctly maintains state between operations. +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. ```ts tests/counter.test.ts import { test, expect } from "vitest"; -import { setupTest } from "@rivetkit/actor/test"; +import { setupTest } from "rivetkit/test"; import { app } from "../src/index"; -test("actor should persist state", async (test) => { +test("worker should persist state", async (test) => { const { client } = await setupTest(test, app); const counter = await client.counter.get(); @@ -92,9 +92,9 @@ test("actor should persist state", async (test) => { ``` ```ts src/index.ts -import { setup } from "@rivetkit/actor"; +import { setup } from "rivetkit"; -const counter = actor({ +const counter = worker({ state: { count: 0 }, actions: { increment: (c) => { @@ -109,7 +109,7 @@ const counter = actor({ }); export const app = setup({ - actors: { counter } + workers: { counter } }); export type App = typeof app; @@ -118,15 +118,15 @@ export type App = typeof app; ## Testing Events -For actors that emit events, you can verify events are correctly triggered by subscribing to them: +For workers that emit events, you can verify events are correctly triggered by subscribing to them: ```ts tests/chat-room.test.ts import { test, expect, vi } from "vitest"; -import { setupTest } from "@rivetkit/actor/test"; +import { setupTest } from "rivetkit/test"; import { app } from "../src/index"; -test("actor should emit events", async (test) => { +test("worker should emit events", async (test) => { const { client } = await setupTest(test, app); const chatRoom = await client.chatRoom.get(); @@ -145,9 +145,9 @@ test("actor should emit events", async (test) => { ``` ```ts src/index.ts -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; -export const chatRoom = actor({ +export const chatRoom = worker({ state: { messages: [] }, @@ -164,7 +164,7 @@ export const chatRoom = actor({ // Create and export the app export const app = setup({ - actors: { chatRoom } + workers: { chatRoom } }); // Export type for client type checking @@ -179,7 +179,7 @@ RivetKit's schedule functionality can be tested using Vitest's time manipulation ```ts tests/scheduler.test.ts import { test, expect, vi } from "vitest"; -import { setupTest } from "@rivetkit/actor/test"; +import { setupTest } from "rivetkit/test"; import { app } from "../src/index"; test("scheduled tasks should execute", async (test) => { @@ -199,9 +199,9 @@ test("scheduled tasks should execute", async (test) => { ``` ```ts src/index.ts -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; -const scheduler = actor({ +const scheduler = worker({ state: { tasks: [], completedTasks: [] @@ -225,7 +225,7 @@ const scheduler = actor({ }); export const app = setup({ - actors: { scheduler } + workers: { scheduler } }); export type App = typeof app; @@ -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 actor handles invalid inputs, concurrent operations, and error conditions. +2. **Test edge cases**: Verify how your worker 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 95ce856b7..855c2e1a5 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 actors are distributed and scale. +RivetKit supports three topologies that define how workers 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 actors in a single process +- **How it works**: Runs all workers 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 actor has its own isolated process. Clients connect directly to the actor for optimal performance. +- **How it works**: Each worker has its own isolated process. Clients connect directly to the worker 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 actors 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 workers 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 0123eb688..743cc1ff5 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -46,26 +46,26 @@ ] }, { - "group": "Actors", + "group": "Workers", "pages": [ -"actor/overview", -"actor/quickstart", -"actor/state", -"actor/actions", -"actor/events", +"workers/overview", +"workers/quickstart", +"workers/state", +"workers/actions", +"workers/events", { "group": "More", "pages": [ -"actor/schedule", +"workers/schedule", -"actor/lifecycle", -"actor/connections", -"actor/authentication", +"workers/lifecycle", +"workers/connections", +"workers/authentication", { "group": "Advanced", "pages": [ -"actor/metadata", -"actor/types" +"workers/metadata", +"workers/types" ] } @@ -73,6 +73,12 @@ } ] }, + { + "group": "Workflows", + "pages": [ + "workflows/overview" + ] + }, { "group": "Integrations", "pages": [ @@ -123,7 +129,7 @@ "pages": [ "concepts/cors", "concepts/external-sql", - "concepts/interacting-with-actors", + "concepts/interacting-with-workers", "concepts/logging", "concepts/overview", "concepts/scaling", @@ -177,23 +183,5 @@ "apiHost": "https://ph.rivet.gg/", "apiKey": "phc_6kfTNEAVw7rn1LA51cO3D69FefbKupSWFaM7OUgEpEo" } - }, - "redirects": [ - { - "source": "/changelog/overview", - "destination": "https://github.com/rivet-gg/rivetkit/releases" - }, - { - "source": "/concepts/manage", - "destination": "/concepts/interacting-with-actors" - }, - { - "source": "/concepts/rpc", - "destination": "/concepts/actions" - }, - { - "source": "/concepts/remote-procedure-calls", - "destination": "/concepts/actions" - } - ] + } } diff --git a/docs/drivers/build.mdx b/docs/drivers/build.mdx index 5f325217f..2892f7217 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: -- **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) +- **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) ## Source Code Locations Get started by looking at source code for the driver interfaces and existing drivers: - **Driver Interfaces** - - **ActorDriver*** `packages/rivetkit/src/actor/runtime/driver.ts` - - **ManagerDriver*** `packages/rivetkit/src/actor/runtime/driver.ts` + - **WorkerDriver*** `packages/rivetkit/src/worker/runtime/driver.ts` + - **ManagerDriver*** `packages/rivetkit/src/worker/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 c1e522fd8..dd69b49d2 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 actor state persistence and coordination. It leverages Cloudflare's global network for low-latency, distributed actor systems that can scale automatically. +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. diff --git a/docs/drivers/file-system.mdx b/docs/drivers/file-system.mdx index cd2143994..d2e35b4a9 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 actor 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 worker 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, FileSystemActorDriver, FileSystemGlobalState } from "@rivetkit/file-system"; + import { FileSystemManagerDriver, FileSystemWorkerDriver, FileSystemGlobalState } from "@rivetkit/file-system"; const fsState = new FileSystemGlobalState(); serve(app, { topology: "standalone", drivers: { manager: new FileSystemManagerDriver(app, fsState), - actor: new FileSystemActorDriver(fsState), + worker: new FileSystemWorkerDriver(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**: Actor state is stored in files and persists between application restarts +- **Persistence**: Worker 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 7d15b7a80..e6ec08583 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 actor 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 worker 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, MemoryActorDriver, MemoryGlobalState } from "@rivetkit/memory"; + import { MemoryManagerDriver, MemoryWorkerDriver, MemoryGlobalState } from "@rivetkit/memory"; const memoryState = new MemoryGlobalState(); serve(app, { topology: "standalone", drivers: { manager: new MemoryManagerDriver(app, memoryState), - actor: new MemoryActorDriver(memoryState), + worker: new MemoryWorkerDriver(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 actors can only communicate within the same process +- **Coordination**: Limited support for coordinated topology, as workers 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 622da8e42..2dc7e29da 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 actor code and the underlying systems. +Drivers in RivetKit the infrastructure layer between your worker 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 actor state in files for durability between restarts. + Local file system driver for development and testing with persistent storage. Stores worker 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 9d191d95b..648ef0c19 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 actor state persistence, coordination, and communication. It supports distributed actor systems and enables horizontal scaling across multiple instances. +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. @@ -57,7 +57,7 @@ The Redis Driver is a production-ready implementation that uses Redis for actor ```typescript src/index.ts import { serve } from "@rivetkit/nodejs" import { RedisManagerDriver } from "@rivetkit/redis/manager"; - import { RedisActorDriver } from "@rivetkit/redis/actor"; + import { RedisWorkerDriver } from "@rivetkit/redis/worker"; 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 actor topology: "coordinate", // Can be "standalone" or "coordinate" drivers: { manager: new RedisManagerDriver(redis), - actor: new RedisActorDriver(redis), + worker: new RedisWorkerDriver(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 { RedisActorDriver } from "@rivetkit/redis/actor"; +import { RedisWorkerDriver } from "@rivetkit/redis/worker"; 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 actorDriver = new RedisActorDriver(redis); +const workerDriver = new RedisWorkerDriver(redis); const coordinateDriver = new RedisCoordinateDriver(redis); ``` diff --git a/docs/drivers/rivet.mdx b/docs/drivers/rivet.mdx index 84542ff77..862cf1f09 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 actor 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 worker 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 actors globally with automatic region selection +- **Global Distribution**: Deploy workers 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 0f9ff5200..8e21b0d6a 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 StepDefineActor from "/snippets/step-define-actor.mdx"; +import StepDefineWorker from "/snippets/step-define-worker.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 actor model. +Learn how to create realtime, stateful React applications with RivetKit's worker model. @@ -64,28 +64,28 @@ Learn how to create realtime, stateful React applications with RivetKit's actor - + Now modify your `src/App.tsx` file to connect to your RivetKit backend: ```tsx src/App.tsx - import { createClient } from "@rivetkit/actor/client"; + import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; - import type { App } from "../actors/app"; + import type { App } from "../workers/app"; import React, { useState } from "react"; // Replace with your endpoint URL after deployment const client = createClient("http://localhost:6420"); - const { useActor, useActorEvent } = createReactRivetKit(client); + const { useWorker, useWorkerEvent } = createReactRivetKit(client); function App() { - // Connect to counter actor - const [{ actor }] = useActor("counter"); + // Connect to counter worker + const [{ worker }] = useWorker("counter"); const [count, setCount] = useState(0); // Listen to count updates - useActorEvent({ actor, event: "newCount" }, (newCount) => { + useWorkerEvent({ worker, event: "newCount" }, (newCount) => { setCount(newCount); }); @@ -93,8 +93,8 @@ Learn how to create realtime, stateful React applications with RivetKit's actor

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 { useActor, useActorEvent } = createReactRivetKit(client); +const { useWorker, useWorkerEvent } = createReactRivetKit(client); ``` #### Parameters @@ -160,46 +160,46 @@ const { useActor, useActorEvent } = createReactRivetKit(client); #### Returns An object containing React hooks: -- `useActor`: Hook for connecting to actors -- `useActorEvent`: Hook for subscribing to actor events +- `useWorker`: Hook for connecting to workers +- `useWorkerEvent`: Hook for subscribing to worker events -### `useActor` +### `useWorker` -Hook that connects to an actor, creating it if necessary. It manages the actor connection and returns the actor handle. +Hook that connects to a worker, creating it if necessary. It manages the worker connection and returns the worker handle. ```tsx -const [{ actor, error, isLoading, state }] = useActor(actorName, options); +const [{ worker, error, isLoading, state }] = useWorker(workerName, options); ``` #### Parameters -- `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. +- `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. - `params`: Parameters to pass during connection. - - `noCreate`: Boolean to prevent actor creation if it doesn't exist. + - `noCreate`: Boolean to prevent worker creation if it doesn't exist. #### Returns Returns an array with a single object containing: -- `actor`: The actor handle if connected, or `undefined` if still connecting. +- `worker`: The worker 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"). -### `useActorEvent` +### `useWorkerEvent` -Hook that subscribes to events from an actor. +Hook that subscribes to events from a worker. ```tsx -useActorEvent({ actor, event }, cb); +useWorkerEvent({ worker, event }, cb); ``` #### Parameters - `opts`: Object containing: - - `actor`: The actor handle from `useActor`, or undefined. + - `worker`: The worker handle from `useWorker`, 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. @@ -212,27 +212,27 @@ This hook doesn't return a value. The subscription is automatically managed by t ### Simple Counter ```tsx -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; -import type { App } from "../actors/app"; +import type { App } from "../workers/app"; import { useState } from "react"; // Connect to RivetKit const client = createClient("http://localhost:6420"); -const { useActor, useActorEvent } = createReactRivetKit(client); +const { useWorker, useWorkerEvent } = createReactRivetKit(client); function Counter() { - // Get actor and track count - const [{ actor }] = useActor("counter"); + // Get worker and track count + const [{ worker }] = useWorker("counter"); const [count, setCount] = useState(0); // Listen for count updates - useActorEvent({ actor, event: "newCount" }, setCount); + useWorkerEvent({ worker, event: "newCount" }, setCount); return (

Count: {count}

-
diff --git a/docs/integrations/hono.mdx b/docs/integrations/hono.mdx index f84c8be25..44809d670 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 app = setup({ - actors: { counter }, + workers: { counter }, // IMPORTANT: Must specify the same basePath where your router is mounted basePath: "/my-path" }); // Create a router from the app -const { router: actorRouter } = createRouter(app); +const { router: workerRouter } = createRouter(app); // Mount at the same path specified in basePath -honoApp.route("/my-path", actorRouter); +honoApp.route("/my-path", workerRouter); ``` -This ensures that WebSocket connections and other functionality work correctly when accessing your actors through the custom path. +This ensures that WebSocket connections and other functionality work correctly when accessing your workers through the custom path. ## Platform-Specific Examples @@ -33,7 +33,7 @@ Each platform has specific requirements for integrating Hono with RivetKit. ```typescript import { createRouter } from "@rivetkit/cloudflare-workers"; -import { setup } from "@rivetkit/actor"; +import { setup } from "rivetkit"; import { Hono } from "hono"; import counter from "./counter"; @@ -46,29 +46,29 @@ honoApp.get("/hello", (c) => c.text("Hello, world!")); // Setup the RivetKit app const app = setup({ - actors: { counter }, + workers: { 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: actorRouter, ActorHandler } = createRouter(app); +const { router: workerRouter, WorkerHandler } = createRouter(app); // Mount the RivetKit router at /my-path -honoApp.route("/my-path", actorRouter); +honoApp.route("/my-path", workerRouter); // Export the app type for client usage export type App = typeof app; -// IMPORTANT: Must export `ActorHandler` as this exact name -export { honoApp as default, ActorHandler }; +// IMPORTANT: Must export `WorkerHandler` as this exact name +export { honoApp as default, WorkerHandler }; ``` Make sure to update your client connection URL to include the custom path: ```typescript // If you mounted RivetKit at /my-path -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("https://your-worker.workers.dev/my-path"); @@ -83,21 +83,21 @@ For this to work with Cloudflare Workers, your `wrangler.json` **must** include "compatibility_date": "2025-01-29", "migrations": [ { - "new_classes": ["ActorHandler"], + "new_classes": ["WorkerHandler"], "tag": "v1" } ], "durable_objects": { "bindings": [ { - "class_name": "ActorHandler", // Must match exported class - "name": "ACTOR_DO" // Must use this exact name + "class_name": "WorkerHandler", // Must match exported class + "name": "WORKER_DO" // Must use this exact name } ] }, "kv_namespaces": [ { - "binding": "ACTOR_KV", // Must use this exact name + "binding": "WORKER_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 app = setup({ - actors: { counter }, + workers: { counter }, // IMPORTANT: Must specify the same basePath where your router is mounted basePath: "/my-path" }); // Create a router from the app -const { router: actorRouter, injectWebSocket } = createRouter(app); +const { router: workerRouter, injectWebSocket } = createRouter(app); // Mount the RivetKit router at /my-path -honoApp.route("/my-path", actorRouter); +honoApp.route("/my-path", workerRouter); // Export the app type for client usage export type App = typeof app; @@ -152,7 +152,7 @@ Make sure to update your client connection URL to include the custom path: ```typescript // If you mounted RivetKit at /my-path -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420/my-path"); @@ -174,16 +174,16 @@ honoApp.get("/hello", (c) => c.text("Hello, world!")); // Setup the RivetKit app const app = setup({ - actors: { counter }, + workers: { counter }, // IMPORTANT: Must specify the same basePath where your router is mounted basePath: "/my-path" }); // Create a router from the app -const { router: actorRouter, webSocketHandler } = createRouter(app); +const { router: workerRouter, webSocketHandler } = createRouter(app); // Mount the RivetKit router at /my-path -honoApp.route("/my-path", actorRouter); +honoApp.route("/my-path", workerRouter); // Export the app type for client usage export type App = typeof app; @@ -201,7 +201,7 @@ Make sure to update your client connection URL to include the custom path: ```typescript // If you mounted RivetKit at /my-path -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420/my-path"); diff --git a/docs/integrations/resend.mdx b/docs/integrations/resend.mdx index 3584ca86f..a6fea2101 100644 --- a/docs/integrations/resend.mdx +++ b/docs/integrations/resend.mdx @@ -19,14 +19,14 @@ npm install resend ``` - -```typescript actors.ts -import { actor, setup } from "@rivetkit/actor"; + +```typescript workers.ts +import { worker, setup } from "rivetkit"; import { Resend } from "resend"; const resend = new Resend(process.env.RESEND_API_KEY); -const user = actor({ +const user = worker({ state: { email: null as string | null, }, @@ -51,21 +51,21 @@ const user = actor({ }, }); -export const app = setup({ actors: { user } }); +export const app = setup({ workers: { user } }); export type App = typeof app; ``` - + ```typescript client.ts -import { createClient } from "@rivetkit/actor"; -import { App } from "./actors/app.ts"; +import { createClient } from "rivetkit"; +import { App } from "./workers/app.ts"; const client = createClient("http://localhost:8787"); -const userActor = await client.user.get({ tags: { user: "user123" } }); +const userWorker = await client.user.get({ tags: { user: "user123" } }); -await userActor.register("user@example.com"); -await userActor.sendExampleEmail(); +await userWorker.register("user@example.com"); +await userWorker.sendExampleEmail(); ``` @@ -77,8 +77,8 @@ await userActor.sendExampleEmail(); RivetKit's scheduling capabilities with Resend make it easy to send emails at specific times: -```typescript actors.ts -const emailScheduler = actor({ +```typescript workers.ts +const emailScheduler = worker({ 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 actors.ts -const reminder = actor({ +```typescript workers.ts +const reminder = worker({ 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 actors.ts -const monitor = actor({ +```typescript workers.ts +const monitor = worker({ state: { alertEmail: null as string | null, isHealthy: true, @@ -212,12 +212,12 @@ await systemMonitor.configure("admin@example.com"); ## Testing -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: +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: ```typescript import { test, expect, vi, beforeEach } from "vitest"; -import { setupTest } from "@rivetkit/actor/test"; -import { app } from "../actors/app"; +import { setupTest } from "rivetkit/test"; +import { app } from "../workers/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 actor = await client.user.get(); + const worker = await client.user.get(); // Call the action that should send an email - await actor.someActionThatSendsEmail("user@example.com"); + await worker.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 cd9322f6c..3d533c106 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -30,8 +30,8 @@ import FAQ from "/snippets/landing-faq.mdx"; **Just a library, no SaaS.**

-
- + {/* - -
+
*/} -
-
Supports
-
- {/* Platforms (all-in-one) */} - - Rivet Platform -
Rivet
-
- - Cloudflare Workers -
Cloudflare Workers
-
- - {/* Compute */} - - Node.js -
Node.js
-
- - Bun -
Bun
-
- - {/* Databases */} - - Redis -
Redis
-
- - File System -
File System
-
- - Memory -
Memory
-
- - {/* Languages & Frameworks */} - - TypeScript -
TypeScript
-
- - Python -
Python (Client)
-
- - Rust -
Rust (Client)
-
- - React -
React
-
- - {/* Integrations */} - - Hono -
Hono
-
- - Vitest -
Vitest
-
-
-
+
{/*
window.copyCommand && window.copyCommand(e.currentTarget)}>
-
npx create-actor@latest
+
npx create-worker@latest
*/} - {/* -
- +
+
-
-

Workflows

+
+

Workers

-

Distributed workflow engine for durable business processes.

-

Replaces Temporal, AWS Step Functions, or LangGraph

+

Stateful worker model for building reliable, scalable, and maintainable backend services.

+

Replaces Durable Objects, Orelans, or Akka

- +
-
-

Agents

+
+

Workflows

-

Framework for building, deploying, and orchestrating AI agents in production.

-

Replaces LangChain, LlamaIndex, or AutoGen

+

Distributed workflow engine for durable business processes.

+

Replaces Cloudflare Workflows, Temporal, or AWS Step Functions

- + {/*
-
-

Actors

+
+

Agents

-

Stateful actor model for building reliable, scalable, and maintainable backend services.

-

Replaces Durable Objects, AWS EventBridge, or Orleans

+

Framework for building, deploying, and orchestrating AI agents in production.

+

Replaces LangChain, LlamaIndex, or AutoGen

-
- + */} + {/*
@@ -175,8 +107,8 @@ import FAQ from "/snippets/landing-faq.mdx";
-
-
*/} + */} +
{/*
@@ -227,6 +159,75 @@ import FAQ from "/snippets/landing-faq.mdx"; Have more questions? Jump to our FAQ
*/} +
+ +
+
Supports
+
+ {/* Platforms (all-in-one) */} + + Rivet Platform +
Rivet
+
+ + Cloudflare Workers +
Cloudflare Workers
+
+ + {/* Compute */} + + Node.js +
Node.js
+
+ + Bun +
Bun
+
+ + {/* Databases */} + + Redis +
Redis
+
+ + File System +
File System
+
+ + Memory +
Memory
+
+ + {/* Languages & Frameworks */} + + TypeScript +
TypeScript
+
+ + Python +
Python (Client)
+
+ + Rust +
Rust (Client)
+
+ + React +
React
+
+ + {/* Integrations */} + + Hono +
Hono
+
+ + Vitest +
Vitest
+
+
+
+
@@ -336,10 +337,10 @@ import FAQ from "/snippets/landing-faq.mdx";
-

Performance in every act - thanks to Rivet Actors.

+

Performance in every act - thanks to Rivet Workers.

- + Get Started diff --git a/docs/llm/claude.mdx b/docs/llm/claude.mdx index 7e56941d6..05896be0c 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 an actor 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 worker 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 actors communicate -claude "explain how actors communicate with each other in the coordinate topology" +# Understand how workers communicate +claude "explain how workers communicate with each other in the coordinate topology" # Learn about specific concepts -claude "explain the lifecycle hooks for actors and when each one is called" +claude "explain the lifecycle hooks for workers 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 actor state management -claude "explain how actor state is persisted between restarts" +# Understand worker state management +claude "explain how worker state is persisted between restarts" ``` ### Add New Features ```bash -# Create a new actor implementation -claude "help me create a new actor for managing user sessions" +# Create a new worker implementation +claude "help me create a new worker for managing user sessions" -# Add authentication to an actor -claude "show me how to add authentication to my actor's _onBeforeConnect method" +# Add authentication to a worker +claude "show me how to add authentication to my worker's _onBeforeConnect method" # Implement error handling -claude "help me implement proper error handling for my actor's RPC methods" +claude "help me implement proper error handling for my worker's RPC methods" ``` ### Debug Issues ```bash # Diagnose connection problems -claude "my actor connections are dropping, help me debug why" +claude "my worker connections are dropping, help me debug why" # Fix state persistence issues -claude "my actor state isn't persisting between restarts, what could be wrong?" +claude "my worker state isn't persisting between restarts, what could be wrong?" # Address scaling problems -claude "my actors are using too much memory, how can I optimize them?" +claude "my workers 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 2deb2cacc..e6ca440e9 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 actors communicate -Explain how actors communicate with each other in the coordinate topology +# Understand how workers communicate +Explain how workers communicate with each other in the coordinate topology # Learn about specific concepts -Explain the lifecycle hooks for actors and when each one is called +Explain the lifecycle hooks for workers 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 actor state management -Explain how actor state is persisted between restarts +# Understand worker state management +Explain how worker state is persisted between restarts ``` ### Add New Features ``` -# Create a new actor implementation -Help me create a new actor for managing user sessions +# Create a new worker implementation +Help me create a new worker for managing user sessions -# Add authentication to an actor -Show me how to add authentication to my actor's _onBeforeConnect method +# Add authentication to a worker +Show me how to add authentication to my worker's _onBeforeConnect method # Implement error handling -Help me implement proper error handling for my actor's RPC methods +Help me implement proper error handling for my worker's RPC methods ``` ### Debug Issues ``` # Diagnose connection problems -My actor connections are dropping, help me debug why +My worker connections are dropping, help me debug why # Fix state persistence issues -My actor state isn't persisting between restarts, what could be wrong? +My worker state isn't persisting between restarts, what could be wrong? # Address scaling problems -My actors are using too much memory, how can I optimize them? +My workers 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 4f6d8c653..ddb9e31c6 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 -- **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 an actor to expose callable functions to clients -- **Event**: Asynchronous message sent from an actor to connected clients +- **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 - **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 actor state persistence +- **Cloudflare Workers**: Uses Durable Objects for worker 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 actor owns and manages its own isolated state via `c.state` +- Each worker 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 actor functions -- **Events**: For actors to publish updates to clients -- Actions are defined in the `actions` object of the actor configuration +- **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 - 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 `actor.on(eventName, callback)` +- Clients subscribe to events with `worker.on(eventName, callback)` ## Lifecycle Hooks -- `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 +- `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 - `onBeforeConnect(c)`: Called when new client connects - `onConnect(c)`: Executed after client connection succeeds - `onDisconnect(c)`: Called when client disconnects -## Actor Management +## Worker Management -- App is configured with actors using `setup({ actors: { actorName }})` followed by `serve(app)` -- 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 +- App is configured with workers using `setup({ workers: { workerName }})` followed by `serve(app)` +- 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 ## Scaling and Architecture Guidelines -- 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 +- 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 - 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 actor restarts +- Scheduled events persist across worker restarts ## CORS Configuration @@ -161,7 +161,7 @@ When importing from workspace packages, always check the package's `package.json ## Development Best Practices -- Prefer functional actor pattern with `actor({ ... })` syntax +- Prefer functional worker pattern with `worker({ ... })` 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 05f61bd76..48c65613d 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 actors communicate -Explain how actors communicate with each other in the coordinate topology +# Understand how workers communicate +Explain how workers communicate with each other in the coordinate topology # Learn about specific concepts -Explain the lifecycle hooks for actors and when each one is called +Explain the lifecycle hooks for workers 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 actor state management -Explain how actor state is persisted between restarts +# Understand worker state management +Explain how worker state is persisted between restarts ``` ### Add New Features ``` -# Create a new actor implementation -Help me create a new actor for managing user sessions +# Create a new worker implementation +Help me create a new worker for managing user sessions -# Add authentication to an actor -Show me how to add authentication to my actor's _onBeforeConnect method +# Add authentication to a worker +Show me how to add authentication to my worker's _onBeforeConnect method # Implement error handling -Help me implement proper error handling for my actor's RPC methods +Help me implement proper error handling for my worker's RPC methods ``` ### Debug Issues ``` # Diagnose connection problems -My actor connections are dropping, help me debug why +My worker connections are dropping, help me debug why # Fix state persistence issues -My actor state isn't persisting between restarts, what could be wrong? +My worker state isn't persisting between restarts, what could be wrong? # Address scaling problems -My actors are using too much memory, how can I optimize them? +My workers are using too much memory, how can I optimize them? ``` ## Additional Resources diff --git a/docs/openapi.json b/docs/openapi.json index 9d87e67b8..76276f42b 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.0", "info": { "version": "0.9.0-rc.1", - "title": "ActorCore API" + "title": "WorkerCore API" }, "components": { "schemas": { @@ -11,7 +11,7 @@ "properties": { "i": { "type": "string", - "example": "actor-123" + "example": "worker-123" } }, "required": [ @@ -25,7 +25,7 @@ "nullable": true, "example": { "getForId": { - "actorId": "actor-123" + "workerId": "worker-123" } } } @@ -41,7 +41,7 @@ "nullable": true, "example": { "getForId": { - "actorId": "actor-123" + "workerId": "worker-123" } } }, @@ -64,7 +64,7 @@ "nullable": true, "example": { "type": "message", - "content": "Hello, actor!" + "content": "Hello, worker!" } } } @@ -73,13 +73,13 @@ "parameters": {} }, "paths": { - "/actors/resolve": { + "/workers/resolve": { "post": { "parameters": [ { "schema": { "type": "string", - "description": "Actor query information" + "description": "Worker query information" }, "required": true, "name": "X-AC-Query", @@ -115,7 +115,7 @@ } } }, - "/actors/connect/websocket": { + "/workers/connect/websocket": { "get": { "parameters": [ { @@ -131,7 +131,7 @@ { "schema": { "type": "string", - "description": "Actor query information" + "description": "Worker query information" }, "required": true, "name": "query", @@ -145,7 +145,7 @@ } } }, - "/actors/connect/sse": { + "/workers/connect/sse": { "get": { "parameters": [ { @@ -161,7 +161,7 @@ { "schema": { "type": "string", - "description": "Actor query information" + "description": "Worker query information" }, "required": true, "name": "X-AC-Query", @@ -191,7 +191,7 @@ } } }, - "/actors/actions/{action}": { + "/workers/actions/{action}": { "post": { "parameters": [ { @@ -252,17 +252,17 @@ } } }, - "/actors/message": { + "/workers/message": { "post": { "parameters": [ { "schema": { "type": "string", - "description": "Actor ID (used in some endpoints)", - "example": "actor-123456" + "description": "Worker ID (used in some endpoints)", + "example": "worker-123456" }, "required": true, - "name": "X-AC-Actor", + "name": "X-AC-Worker", "in": "header" }, { diff --git a/docs/platforms/bun.mdx b/docs/platforms/bun.mdx index f3f57db41..a6dab0475 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 "../actors/app"; + import { app } from "../workers/app"; // Start the server with file-system driver (default) serve(app); diff --git a/docs/platforms/cloudflare-workers.mdx b/docs/platforms/cloudflare-workers.mdx index 40eee56b8..4077975f6 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 "../actors/app"; + import { app } from "../workers/app"; // Create handlers for Cloudflare Workers - const { handler, ActorHandler } = createHandler(app); + const { handler, WorkerHandler } = createHandler(app); // Export the handlers for Cloudflare - export { handler as default, ActorHandler }; + export { handler as default, WorkerHandler }; ``` - Create a KV namespace for your actors: + Create a KV namespace for your workers: ```sh - npx wrangler kv:namespace create ACTOR_KV + npx wrangler kv:namespace create WORKER_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": ["ActorHandler"] + "new_classes": ["WorkerHandler"] } ], "durable_objects": { "bindings": [ { - "name": "ACTOR_DO", - "class_name": "ActorHandler" + "name": "WORKER_DO", + "class_name": "WorkerHandler" } ] }, "kv_namespaces": [ { - "binding": "ACTOR_KV", + "binding": "WORKER_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 actor: +You can access Cloudflare-specific features like the [DurableObjectState](https://developers.cloudflare.com/durable-objects/api/state/) and environment bindings from your worker: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const myActor = actor({ +const myWorker = worker({ // Load Cloudflare-specific variables createVars: (c, cloudflare) => ({ ctx: cloudflare.ctx, diff --git a/docs/platforms/nodejs.mdx b/docs/platforms/nodejs.mdx index a943d8d15..768beca7e 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 "../actors/app"; + import { app } from "../workers/app"; // Start the server with file-system driver (default) serve(app); diff --git a/docs/platforms/rivet.mdx b/docs/platforms/rivet.mdx index 6bdaa70ee..0f1519d52 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 actors/app.ts + npx rivetkit/cli@latest deploy rivet workers/app.ts ``` ```sh pnpm - pnpm exec rivetkit/cli@latest deploy rivet actors/app.ts + pnpm exec rivetkit/cli@latest deploy rivet workers/app.ts ``` ```sh yarn - yarn rivetkit/cli@latest deploy rivet actors/app.ts + yarn rivetkit/cli@latest deploy rivet workers/app.ts ``` ```sh bun - bunx rivetkit/cli@latest deploy rivet actors/app.ts + bunx rivetkit/cli@latest deploy rivet workers/app.ts ``` @@ -76,19 +76,19 @@ Rivet provides a fully managed cloud service for running RivetKit, with automati ## Accessing Rivet Context -[Rivet's `ActorContext`](https://rivet.gg/docs/javascript-runtime#the-actor-context-object) can be accessed from `createVars`. +[Rivet's `WorkerContext`](https://rivet.gg/docs/javascript-runtime#the-worker-context-object) can be accessed from `createVars`. ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const myActor = actor({ +const myWorker = worker({ // Load Rivet-specific variables createVars: (c, rivet) => ({ rivet: rivet.ctx, }), actions: { foo: async (c) => { - // Access ActorContext + // Access WorkerContext c.log.info(`Region: ${c.vars.rivet.metadata.region.name}`); await c.vars.rivet.kv.get("foo"); }, @@ -98,7 +98,7 @@ const myActor = actor({ ## Available Regions -Rivet supports deploying your actors to multiple regions automatically. You can specify region preferences in your Rivet project settings in the Rivet Hub. +Rivet supports deploying your workers 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 6970b4a64..8bcadcf5d 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 ACTOR_KV + npx wrangler kv namespace create WORKER_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": "ACTOR_KV", + "binding": "WORKER_KV", "id": "your-namespace-id-here" // Replace with your actual ID } ] @@ -27,7 +27,7 @@ 4. Update `tests/client.ts` (or wherever your client lives) to use the deployed endpoint. Replace the local endpoint in `client.ts` with your Cloudflare Workers URL: ```typescript - import { createClient } from "@rivetkit/actor/client"; + import { createClient } from "rivetkit/client"; import type { App } from "../src/index"; const client = createClient("https://your-worker-subdomain.workers.dev"); diff --git a/docs/snippets/create-actor-cli.mdx b/docs/snippets/create-actor-cli.mdx index 65366ec96..07fac8b22 100644 --- a/docs/snippets/create-actor-cli.mdx +++ b/docs/snippets/create-actor-cli.mdx @@ -1,21 +1,21 @@ ```sh npx -npx create-actor@latest +npx create-worker@latest ``` ```sh npm -npm create actor@latest +npm create worker@latest ``` ```sh pnpm -pnpm create actor@latest +pnpm create worker@latest ``` ```sh yarn -yarn create actor@latest +yarn create worker@latest ``` ```sh bun -bun create actor@latest +bun create worker@latest ``` diff --git a/docs/snippets/examples/ai-agent-js.mdx b/docs/snippets/examples/ai-agent-js.mdx index 0df15dec8..a93bafad4 100644 --- a/docs/snippets/examples/ai-agent-js.mdx +++ b/docs/snippets/examples/ai-agent-js.mdx @@ -1,12 +1,12 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } 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 = actor({ +const aiAgent = worker({ // 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 27c0bf127..688bc0fe7 100644 --- a/docs/snippets/examples/ai-agent-react.mdx +++ b/docs/snippets/examples/ai-agent-react.mdx @@ -1,42 +1,42 @@ ```typescript -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { App } from "../actors/app"; -import type { Message } from "./actor"; +import type { App } from "../workers/app"; +import type { Message } from "./worker"; const client = createClient("http://localhost:6420"); -const { useActor, useActorEvent } = createReactRivetKit(client); +const { useWorker, useWorkerEvent } = createReactRivetKit(client); export function AIAssistant() { - const [{ actor }] = useActor("aiAgent", { tags: { conversationId: "default" } }); + const [{ worker }] = useWorker("aiAgent", { tags: { conversationId: "default" } }); const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [isLoading, setIsLoading] = useState(false); // Load initial messages useEffect(() => { - if (actor) { - actor.getMessages().then(setMessages); + if (worker) { + worker.getMessages().then(setMessages); } - }, [actor]); + }, [worker]); // Listen for real-time messages - useActorEvent({ actor, event: "messageReceived" }, (message) => { + useWorkerEvent({ worker, event: "messageReceived" }, (message) => { setMessages(prev => [...prev, message as Message]); setIsLoading(false); }); const handleSendMessage = async () => { - if (actor && input.trim()) { + if (worker && input.trim()) { setIsLoading(true); // Add user message to UI immediately const userMessage = { role: "user", content: input } as Message; setMessages(prev => [...prev, userMessage]); - // Send to actor (AI response will come through the event) - await actor.sendMessage(input); + // Send to worker (AI response will come through the event) + await worker.sendMessage(input); setInput(""); } }; diff --git a/docs/snippets/examples/ai-agent-sqlite.mdx b/docs/snippets/examples/ai-agent-sqlite.mdx index 47f40aab4..eda07de22 100644 --- a/docs/snippets/examples/ai-agent-sqlite.mdx +++ b/docs/snippets/examples/ai-agent-sqlite.mdx @@ -1,5 +1,5 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } 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 = actor({ +const aiAgent = worker({ sql: drizzle(), actions: { @@ -28,7 +28,7 @@ const aiAgent = actor({ // Add user message to conversation const userMsg = { - conversationId: c.actorId, // Use the actor instance ID + conversationId: c.workerId, // Use the worker 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 bb5954580..e8db01eb3 100644 --- a/docs/snippets/examples/chat-room-js.mdx +++ b/docs/snippets/examples/chat-room-js.mdx @@ -1,9 +1,9 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; export type Message = { sender: string; text: string; timestamp: number; } -const chatRoom = actor({ +const chatRoom = worker({ // 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 36909e89d..78d516c22 100644 --- a/docs/snippets/examples/chat-room-react.mdx +++ b/docs/snippets/examples/chat-room-react.mdx @@ -1,16 +1,16 @@ ```typescript -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { App } from "../actors/app"; -import type { Message } from "./actor"; +import type { App } from "../workers/app"; +import type { Message } from "./worker"; const client = createClient("http://localhost:6420"); -const { useActor, useActorEvent } = createReactRivetKit(client); +const { useWorker, useWorkerEvent } = createReactRivetKit(client); export function ChatRoom({ roomId = "general" }) { // Connect to specific chat room using tags - const [{ actor }] = useActor("chatRoom", { + const [{ worker }] = useWorker("chatRoom", { tags: { roomId } }); @@ -19,20 +19,20 @@ export function ChatRoom({ roomId = "general" }) { // Load initial state useEffect(() => { - if (actor) { + if (worker) { // Load chat history - actor.getHistory().then(setMessages); + worker.getHistory().then(setMessages); } - }, [actor]); + }, [worker]); // Listen for real-time updates from the server - useActorEvent({ actor, event: "newMessage" }, (message) => { + useWorkerEvent({ worker, event: "newMessage" }, (message) => { setMessages(prev => [...prev, message]); }); const sendMessage = () => { - if (actor && input.trim()) { - actor.sendMessage("User", input); + if (worker && input.trim()) { + worker.sendMessage("User", input); setInput(""); } }; diff --git a/docs/snippets/examples/chat-room-sqlite.mdx b/docs/snippets/examples/chat-room-sqlite.mdx index c44b8e33e..38c9c7c38 100644 --- a/docs/snippets/examples/chat-room-sqlite.mdx +++ b/docs/snippets/examples/chat-room-sqlite.mdx @@ -1,11 +1,11 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { messages } from "./schema"; export type Message = { sender: string; text: string; timestamp: number; } -const chatRoom = actor({ +const chatRoom = worker({ sql: drizzle(), actions: { diff --git a/docs/snippets/examples/crdt-js.mdx b/docs/snippets/examples/crdt-js.mdx index 1a3494a73..6614d2d45 100644 --- a/docs/snippets/examples/crdt-js.mdx +++ b/docs/snippets/examples/crdt-js.mdx @@ -1,9 +1,9 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; import * as Y from 'yjs'; import { encodeStateAsUpdate, applyUpdate } from 'yjs'; -const yjsDocument = actor({ +const yjsDocument = worker({ // State: just the serialized Yjs document data state: { docData: "", // Base64 encoded Yjs document @@ -15,7 +15,7 @@ const yjsDocument = actor({ doc: new Y.Doc() }), - // Initialize document from state when actor starts + // Initialize document from state when worker 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 f682c2302..4ff9677cd 100644 --- a/docs/snippets/examples/crdt-react.mdx +++ b/docs/snippets/examples/crdt-react.mdx @@ -1,17 +1,17 @@ ```typescript -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; 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 "../actors/app"; +import type { App } from "../workers/app"; const client = createClient("http://localhost:6420"); -const { useActor, useActorEvent } = createReactRivetKit(client); +const { useWorker, useWorkerEvent } = createReactRivetKit(client); export function YjsEditor({ documentId = "shared-doc" }) { // Connect to specific document using tags - const { actor } = useActor("yjsDocument", { + const { worker } = useWorker("yjsDocument", { tags: { documentId } }); @@ -38,7 +38,7 @@ export function YjsEditor({ documentId = "shared-doc" }) { // Clean up Yjs document yDoc.destroy(); }; - }, [actor]); + }, [worker]); // Set up text observation useEffect(() => { @@ -55,14 +55,14 @@ export function YjsEditor({ documentId = "shared-doc" }) { // Update React state setText(yText.toString()); - if (actor && !updatingFromLocal.current) { + if (worker && !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); - actor.applyUpdate(base64).finally(() => { + worker.applyUpdate(base64).finally(() => { updatingFromLocal.current = false; }); } @@ -70,10 +70,10 @@ export function YjsEditor({ documentId = "shared-doc" }) { }); observationInitialized.current = true; - }, [actor]); + }, [worker]); // Handle initial state from server - useActorEvent({ actor, event: "initialState" }, ({ update }) => { + useWorkerEvent({ worker, 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 - useActorEvent({ actor, event: "update" }, ({ update }) => { + useWorkerEvent({ worker, 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 293e1f969..0e871bfba 100644 --- a/docs/snippets/examples/crdt-sqlite.mdx +++ b/docs/snippets/examples/crdt-sqlite.mdx @@ -1,11 +1,11 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import * as Y from 'yjs'; import { encodeStateAsUpdate, applyUpdate } from 'yjs'; import { documents } from "./schema"; -const yjsDocument = actor({ +const yjsDocument = worker({ sql: drizzle(), // In-memory Yjs objects (not serialized) @@ -13,7 +13,7 @@ const yjsDocument = actor({ doc: new Y.Doc() }), - // Initialize document from state when actor starts + // Initialize document from state when worker 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 0e9355073..445ee338c 100644 --- a/docs/snippets/examples/database-js.mdx +++ b/docs/snippets/examples/database-js.mdx @@ -1,11 +1,11 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; import { authenticate } from "./my-utils"; export type Note = { id: string; content: string; updatedAt: number }; -// User notes actor -const notes = actor({ +// User notes worker +const notes = worker({ state: { notes: [] as Note[] }, diff --git a/docs/snippets/examples/database-react.mdx b/docs/snippets/examples/database-react.mdx index 9238bb68a..da06385fc 100644 --- a/docs/snippets/examples/database-react.mdx +++ b/docs/snippets/examples/database-react.mdx @@ -1,54 +1,54 @@ ```typescript -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; const client = createClient("http://localhost:6420"); -const { useActor, useActorEvent } = createReactRivetKit(client); +const { useWorker, useWorkerEvent } = createReactRivetKit(client); export function NotesApp({ userId }: { userId: string }) { const [notes, setNotes] = useState>([]); const [newNote, setNewNote] = useState(""); - // Connect to actor with auth token - const [{ actor }] = useActor("notes", { + // Connect to worker with auth token + const [{ worker }] = useWorker("notes", { params: { userId, token: "demo-token" } }); // Load initial notes useEffect(() => { - if (actor) { - actor.getNotes().then(setNotes); + if (worker) { + worker.getNotes().then(setNotes); } - }, [actor]); + }, [worker]); // Add a new note const addNote = async () => { - if (actor && newNote.trim()) { - await actor.updateNote({ id: `note-${Date.now()}`, content: newNote }); + if (worker && newNote.trim()) { + await worker.updateNote({ id: `note-${Date.now()}`, content: newNote }); setNewNote(""); } }; // Delete a note const deleteNote = (id: string) => { - if (actor) { - actor.deleteNote({ id }); + if (worker) { + worker.deleteNote({ id }); } }; // Listen for realtime updates - useActorEvent({ actor, event: "noteAdded" }, (note) => { + useWorkerEvent({ worker, event: "noteAdded" }, (note) => { setNotes(notes => [...notes, note]); }); - useActorEvent({ actor, event: "noteUpdated" }, (updatedNote) => { + useWorkerEvent({ worker, event: "noteUpdated" }, (updatedNote) => { setNotes(notes => notes.map(note => note.id === updatedNote.id ? updatedNote : note )); }); - useActorEvent({ actor, event: "noteDeleted" }, ({ id }) => { + useWorkerEvent({ worker, 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 5866a9185..84d9ab000 100644 --- a/docs/snippets/examples/database-sqlite.mdx +++ b/docs/snippets/examples/database-sqlite.mdx @@ -1,13 +1,13 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } 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 actor -const userNotes = actor({ +// User notes worker +const userNotes = worker({ sql: drizzle(), // Authenticate diff --git a/docs/snippets/examples/document-js.mdx b/docs/snippets/examples/document-js.mdx index bb5147394..410601e27 100644 --- a/docs/snippets/examples/document-js.mdx +++ b/docs/snippets/examples/document-js.mdx @@ -1,9 +1,9 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; export type Cursor = { x: number, y: number, userId: string }; -const document = actor({ +const document = worker({ state: { text: "", cursors: {} as Record, diff --git a/docs/snippets/examples/document-react.mdx b/docs/snippets/examples/document-react.mdx index eb908969e..b845b1868 100644 --- a/docs/snippets/examples/document-react.mdx +++ b/docs/snippets/examples/document-react.mdx @@ -1,16 +1,16 @@ ```typescript -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { App } from "../actors/app"; +import type { App } from "../workers/app"; const client = createClient("http://localhost:6420"); -const { useActor, useActorEvent } = createReactRivetKit(client); +const { useWorker, useWorkerEvent } = createReactRivetKit(client); export function DocumentEditor() { - // Connect to actor for this document ID from URL + // Connect to worker for this document ID from URL const documentId = new URLSearchParams(window.location.search).get('id') || 'default-doc'; - const [{ actor, connectionId }] = useActor("document", { tags: { id: documentId } }); + const [{ worker, connectionId }] = useWorker("document", { tags: { id: documentId } }); // Local state const [text, setText] = useState(""); @@ -19,18 +19,18 @@ export function DocumentEditor() { // Load initial document state useEffect(() => { - if (actor) { - actor.getText().then(setText); - actor.getCursors().then(setOtherCursors); + if (worker) { + worker.getText().then(setText); + worker.getCursors().then(setOtherCursors); } - }, [actor]); + }, [worker]); // Listen for updates from other users - useActorEvent({ actor, event: "textUpdated" }, ({ text: newText, userId: senderId }) => { + useWorkerEvent({ worker, event: "textUpdated" }, ({ text: newText, userId: senderId }) => { if (senderId !== connectionId) setText(newText); }); - useActorEvent({ actor, event: "cursorUpdated" }, ({ userId: cursorUserId, x, y }) => { + useWorkerEvent({ worker, 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 (!actor) return; + if (!worker) 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 }); - actor.updateCursor(x, y); + worker.updateCursor(x, y); } }; @@ -62,7 +62,7 @@ export function DocumentEditor() { onChange={(e) => { const newText = e.target.value; setText(newText); - actor?.setText(newText); + worker?.setText(newText); }} placeholder="Start typing..." /> diff --git a/docs/snippets/examples/document-sqlite.mdx b/docs/snippets/examples/document-sqlite.mdx index 194a5c0fd..3ff14b120 100644 --- a/docs/snippets/examples/document-sqlite.mdx +++ b/docs/snippets/examples/document-sqlite.mdx @@ -1,11 +1,11 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { documents, cursors } from "./schema"; export type Cursor = { x: number, y: number, userId: string }; -const document = actor({ +const document = worker({ sql: drizzle(), actions: { diff --git a/docs/snippets/examples/game-js.mdx b/docs/snippets/examples/game-js.mdx index 903224947..73887a79b 100644 --- a/docs/snippets/examples/game-js.mdx +++ b/docs/snippets/examples/game-js.mdx @@ -1,11 +1,11 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } 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 = actor({ +const gameRoom = worker({ state: { players: {} as Record, mapSize: 800 diff --git a/docs/snippets/examples/game-react.mdx b/docs/snippets/examples/game-react.mdx index 8aa3db942..cbbc97329 100644 --- a/docs/snippets/examples/game-react.mdx +++ b/docs/snippets/examples/game-react.mdx @@ -1,21 +1,21 @@ ```tsx -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect, useRef } from "react"; -import type { Player } from "./actor"; +import type { Player } from "./worker"; const client = createClient("http://localhost:6420"); -const { useActor, useActorEvent } = createReactRivetKit(client); +const { useWorker, useWorkerEvent } = createReactRivetKit(client); export function MultiplayerGame() { - const [{ actor, connectionId }] = useActor("gameRoom"); + const [{ worker, connectionId }] = useWorker("gameRoom"); const [players, setPlayers] = useState([]); const canvasRef = useRef(null); const keysPressed = useRef>({}); // Set up game useEffect(() => { - if (!actor) return; + if (!worker) 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; - actor.setInput(input); + worker.setInput(input); }, 50); // Rendering loop @@ -71,10 +71,10 @@ export function MultiplayerGame() { clearInterval(inputInterval); cancelAnimationFrame(animationId); }; - }, [actor, connectionId, players]); + }, [worker, connectionId, players]); // Listen for world updates - useActorEvent({ actor, event: "worldUpdate" }, ({ players: updatedPlayers }) => { + useWorkerEvent({ worker, event: "worldUpdate" }, ({ players: updatedPlayers }) => { setPlayers(updatedPlayers); }); diff --git a/docs/snippets/examples/game-sqlite.mdx b/docs/snippets/examples/game-sqlite.mdx index 0898cefdf..c1f77b3f8 100644 --- a/docs/snippets/examples/game-sqlite.mdx +++ b/docs/snippets/examples/game-sqlite.mdx @@ -1,5 +1,5 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } 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 = actor({ +const gameRoom = worker({ 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 f190355c3..906cea57f 100644 --- a/docs/snippets/examples/rate-js.mdx +++ b/docs/snippets/examples/rate-js.mdx @@ -1,8 +1,8 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; // Simple rate limiter - allows 5 requests per minute -const rateLimiter = actor({ +const rateLimiter = worker({ state: { count: 0, resetAt: 0 diff --git a/docs/snippets/examples/rate-react.mdx b/docs/snippets/examples/rate-react.mdx index 191667769..3a097c4b6 100644 --- a/docs/snippets/examples/rate-react.mdx +++ b/docs/snippets/examples/rate-react.mdx @@ -1,15 +1,15 @@ ```typescript -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState } from "react"; -import type { App } from "../actors/app"; +import type { App } from "../workers/app"; const client = createClient("http://localhost:6420"); -const { useActor } = createReactRivetKit(client); +const { useWorker } = createReactRivetKit(client); export function RateLimiter() { // Connect to API rate limiter for user-123 - const [{ actor }] = useActor("rateLimiter", { tags: { userId: "user-123" } }); + const [{ worker }] = useWorker("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 (!actor) return; + if (!worker) return; - const response = await actor.checkLimit(); + const response = await worker.checkLimit(); setResult(response); }; diff --git a/docs/snippets/examples/rate-sqlite.mdx b/docs/snippets/examples/rate-sqlite.mdx index a4e73bf09..501a45e74 100644 --- a/docs/snippets/examples/rate-sqlite.mdx +++ b/docs/snippets/examples/rate-sqlite.mdx @@ -1,10 +1,10 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { limiters } from "./schema"; // Simple rate limiter - allows 5 requests per minute -const rateLimiter = actor({ +const rateLimiter = worker({ sql: drizzle(), actions: { diff --git a/docs/snippets/examples/stream-js.mdx b/docs/snippets/examples/stream-js.mdx index 924e3be38..a2ec818ca 100644 --- a/docs/snippets/examples/stream-js.mdx +++ b/docs/snippets/examples/stream-js.mdx @@ -1,12 +1,12 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; export type StreamState = { topValues: number[]; }; // Simple top-K stream processor example -const streamProcessor = actor({ +const streamProcessor = worker({ state: { topValues: [] as number[] }, diff --git a/docs/snippets/examples/stream-react.mdx b/docs/snippets/examples/stream-react.mdx index c63da07c8..c2a1daedd 100644 --- a/docs/snippets/examples/stream-react.mdx +++ b/docs/snippets/examples/stream-react.mdx @@ -1,34 +1,34 @@ ```typescript -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { App } from "../actors/app"; -import type { StreamState } from "./actor"; // Import shared types from actor +import type { App } from "../workers/app"; +import type { StreamState } from "./worker"; // Import shared types from worker const client = createClient("http://localhost:6420"); -const { useActor, useActorEvent } = createReactRivetKit(client); +const { useWorker, useWorkerEvent } = createReactRivetKit(client); export function StreamExample() { - const [{ actor }] = useActor("streamProcessor"); + const [{ worker }] = useWorker("streamProcessor"); const [topValues, setTopValues] = useState([]); const [newValue, setNewValue] = useState(0); // Load initial values useEffect(() => { - if (actor) { - actor.getTopValues().then(setTopValues); + if (worker) { + worker.getTopValues().then(setTopValues); } - }, [actor]); + }, [worker]); // Listen for updates from other clients - useActorEvent({ actor, event: "updated" }, ({ topValues }) => { + useWorkerEvent({ worker, event: "updated" }, ({ topValues }) => { setTopValues(topValues); }); // Add a new value to the stream const handleAddValue = () => { - if (actor) { - actor.addValue(newValue).then(setTopValues); + if (worker) { + worker.addValue(newValue).then(setTopValues); setNewValue(0); } }; diff --git a/docs/snippets/examples/stream-sqlite.mdx b/docs/snippets/examples/stream-sqlite.mdx index a2b7dc532..699913ad4 100644 --- a/docs/snippets/examples/stream-sqlite.mdx +++ b/docs/snippets/examples/stream-sqlite.mdx @@ -1,12 +1,12 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } 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 = actor({ +const streamProcessor = worker({ sql: drizzle(), actions: { diff --git a/docs/snippets/examples/sync-js.mdx b/docs/snippets/examples/sync-js.mdx index 6bd859afd..e785111d5 100644 --- a/docs/snippets/examples/sync-js.mdx +++ b/docs/snippets/examples/sync-js.mdx @@ -1,9 +1,9 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; export type Contact = { id: string; name: string; email: string; phone: string; updatedAt: number; } -const contacts = actor({ +const contacts = worker({ // State is automatically persisted state: { contacts: {} diff --git a/docs/snippets/examples/sync-react.mdx b/docs/snippets/examples/sync-react.mdx index 93bf276bf..33f521e8b 100644 --- a/docs/snippets/examples/sync-react.mdx +++ b/docs/snippets/examples/sync-react.mdx @@ -1,14 +1,14 @@ ```tsx -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect, useRef } from "react"; -import type { Contact } from "./actor"; +import type { Contact } from "./worker"; const client = createClient("http://localhost:6420"); -const { useActor, useActorEvent } = createReactRivetKit(client); +const { useWorker, useWorkerEvent } = createReactRivetKit(client); export function ContactsApp() { - const { actor } = useActor("contacts"); + const { worker } = useWorker("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 (!actor) return; + if (!worker) return; - actor.getChanges(0).then(data => { + worker.getChanges(0).then(data => { setContacts(data.changes); lastSyncTime.current = data.timestamp; setSyncStatus("Synced"); }); - }, [actor]); + }, [worker]); // Handle contact events - useActorEvent({ actor, event: "contactsChanged" }, ({ contacts: updatedContacts }) => { + useWorkerEvent({ worker, 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 (!actor) return; + if (!worker) return; const sync = async () => { setSyncStatus("Syncing..."); try { // Get remote changes - const changes = await actor.getChanges(lastSyncTime.current); + const changes = await worker.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 actor.pushChanges(localChanges); + await worker.pushChanges(localChanges); } lastSyncTime.current = changes.timestamp; @@ -87,7 +87,7 @@ export function ContactsApp() { const intervalId = setInterval(sync, 5000); return () => clearInterval(intervalId); - }, [actor, contacts]); + }, [worker, contacts]); // Add new contact (local first) const addContact = () => { @@ -103,8 +103,8 @@ export function ContactsApp() { setContacts(prev => [...prev, newContact]); - if (actor) { - actor.pushChanges([newContact]); + if (worker) { + worker.pushChanges([newContact]); } setName(""); @@ -121,10 +121,10 @@ export function ContactsApp() { : c ); - if (actor) { + if (worker) { const deleted = updatedContacts.find(c => c.id === id); if (deleted) { - actor.pushChanges([deleted]); + worker.pushChanges([deleted]); } } @@ -134,16 +134,16 @@ export function ContactsApp() { // Manual sync const handleSync = async () => { - if (!actor) return; + if (!worker) return; setSyncStatus("Syncing..."); try { // Push all contacts - await actor.pushChanges(contacts); + await worker.pushChanges(contacts); // Get all changes - const changes = await actor.getChanges(0); + const changes = await worker.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 86db417d9..17fd0a5b4 100644 --- a/docs/snippets/examples/sync-sqlite.mdx +++ b/docs/snippets/examples/sync-sqlite.mdx @@ -1,11 +1,11 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } 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 = actor({ +const contactSync = worker({ sql: drizzle(), actions: { diff --git a/docs/snippets/examples/tenant-js.mdx b/docs/snippets/examples/tenant-js.mdx index c6e13b69e..15036fcfc 100644 --- a/docs/snippets/examples/tenant-js.mdx +++ b/docs/snippets/examples/tenant-js.mdx @@ -1,9 +1,9 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; import { authenticate } from "./my-utils"; -// Simple tenant organization actor -const tenant = actor({ +// Simple tenant organization worker +const tenant = worker({ // Example initial state state: { members: [ diff --git a/docs/snippets/examples/tenant-react.mdx b/docs/snippets/examples/tenant-react.mdx index e2854cf69..7446fb67f 100644 --- a/docs/snippets/examples/tenant-react.mdx +++ b/docs/snippets/examples/tenant-react.mdx @@ -1,12 +1,12 @@ ```typescript -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactRivetKit } from "@rivetkit/react"; import { useState, useEffect } from "react"; -import type { App } from "../actors/app"; +import type { App } from "../workers/app"; // Create client and hooks const client = createClient("http://localhost:6420"); -const { useActor } = createReactRivetKit(client); +const { useWorker } = 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 actor with authentication token - const [{ actor }] = useActor("tenant", { + // Connect to tenant worker with authentication token + const [{ worker }] = useWorker("tenant", { params: { token }, tags: { orgId } }); - // Load data when actor is available + // Load data when worker is available useEffect(() => { - if (!actor || !token) return; + if (!worker || !token) return; const loadData = async () => { try { // Get members (available to all users) - const membersList = await actor.getMembers(); + const membersList = await worker.getMembers(); setMembers(membersList); // Try to get invoices (only available to admins) try { - const invoicesList = await actor.getInvoices(); + const invoicesList = await worker.getInvoices(); setInvoices(invoicesList); setError(""); } catch (err: any) { @@ -56,7 +56,7 @@ export function OrgDashboard({ orgId }: { orgId: string }) { }; loadData(); - }, [actor, token]); + }, [worker, 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 7028524f7..7bc563ad4 100644 --- a/docs/snippets/examples/tenant-sqlite.mdx +++ b/docs/snippets/examples/tenant-sqlite.mdx @@ -1,11 +1,11 @@ ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { members, invoices } from "./schema"; import { authenticate } from "./my-utils"; -// Simple tenant organization actor -const tenant = actor({ +// Simple tenant organization worker +const tenant = worker({ sql: drizzle(), // Authentication diff --git a/docs/snippets/landing-comparison-table.mdx b/docs/snippets/landing-comparison-table.mdx index 4cac8baf8..686785c6f 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 actors + Define and call functions that interact with your workers
diff --git a/docs/snippets/landing-faq.mdx b/docs/snippets/landing-faq.mdx index 4d4131b33..e23b7314d 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 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.

+

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.

@@ -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 actors.

+

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.

diff --git a/docs/snippets/landing-manifesto.mdx b/docs/snippets/landing-manifesto.mdx index 68ba9d51b..d8f185826 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 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. + 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.

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 9caddc25b..6de6fe579 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";

- actor.ts + worker.ts Runs on the server
@@ -126,7 +126,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -151,7 +151,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -175,7 +175,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -200,7 +200,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -224,7 +224,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -249,7 +249,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -273,7 +273,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -298,7 +298,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -322,7 +322,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -347,7 +347,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -371,7 +371,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -396,7 +396,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -420,7 +420,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -445,7 +445,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -469,7 +469,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -494,7 +494,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -518,7 +518,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -543,7 +543,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
@@ -567,7 +567,7 @@ import RateReact from "/snippets/examples/rate-react.mdx";
- actor.ts + worker.ts Runs on the server
diff --git a/docs/snippets/landing-tech.mdx b/docs/snippets/landing-tech.mdx index 38f159fa3..ef94c894c 100644 --- a/docs/snippets/landing-tech.mdx +++ b/docs/snippets/landing-tech.mdx @@ -216,7 +216,7 @@ MCP
- Actor-Actor Actions + Worker-Worker Actions
Cancellable Schedules @@ -293,7 +293,7 @@
- create-actor + create-worker
diff --git a/docs/snippets/platform-extra-notes.mdx b/docs/snippets/platform-extra-notes.mdx index b88267b20..7b2938e54 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 actors. Learn more in the [CORS documentation](/concepts/cors). +For production deployments, specify the exact domains that should be allowed to connect to your workers. 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 f6b22258a..b062f0612 100644 --- a/docs/snippets/setup-actor.mdx +++ b/docs/snippets/setup-actor.mdx @@ -1,8 +1,8 @@ ```typescript src/app.ts -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; -// Create actor -const counter = actor({ +// Create worker +const counter = worker({ state: { count: 0 }, actions: { increment: (c, x: number) => { @@ -15,7 +15,7 @@ const counter = actor({ // Create the application export const app = setup({ - actors: { counter }, + workers: { counter }, cors: { origin: "http://localhost:8080" } }); diff --git a/docs/snippets/setup-next-steps.mdx b/docs/snippets/setup-next-steps.mdx index 03229781a..1003f3cb7 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 07c1503f8..1c5a157fb 100644 --- a/docs/snippets/step-define-actor.mdx +++ b/docs/snippets/step-define-actor.mdx @@ -1,11 +1,11 @@ - - Create a file `actors/app.ts` in your project with your actor definition: + + Create a file `workers/app.ts` in your project with your worker definition: - ```typescript actors/app.ts - import { actor, setup } from "@rivetkit/actor"; + ```typescript workers/app.ts + import { worker, setup } from "rivetkit"; - // Create actor - const counter = actor({ + // Create worker + const counter = worker({ state: { count: 0 }, actions: { increment: (c, x: number) => { @@ -18,7 +18,7 @@ // Create the application export const app = setup({ - actors: { counter }, + workers: { 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 38efd0d78..c06fc690f 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 actors/app.ts + npx rivetkit/cli@latest dev workers/app.ts ``` ```sh pnpm - pnpm exec rivetkit/cli@latest dev actors/app.ts + pnpm exec rivetkit/cli@latest dev workers/app.ts ``` ```sh yarn - yarn rivetkit/cli@latest dev actors/app.ts + yarn rivetkit/cli@latest dev workers/app.ts ``` ```sh bun - bunx rivetkit/cli@latest dev actors/app.ts + bunx rivetkit/cli@latest dev workers/app.ts ``` diff --git a/docs/snippets/step-update-client.mdx b/docs/snippets/step-update-client.mdx index 091c5463b..fb2e09db8 100644 --- a/docs/snippets/step-update-client.mdx +++ b/docs/snippets/step-update-client.mdx @@ -12,7 +12,7 @@ ``` ```python Python - client = ActorClient( + client = WorkerClient( # FILL ME IN ) ``` diff --git a/docs/styles/cta.js b/docs/styles/cta.js index b6bf9f7a3..6c4dbcd58 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 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." + "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." ]; function initializeAllCTAs() { diff --git a/docs/styles/particles.js b/docs/styles/particles.js index edf9a925b..a0c1a9810 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.opacityFactor = Math.random(); +// this.opacityFworker = 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.opacityFactor; +// this.opacity = this.opacityBase + Math.max(0, Math.min(1, 1 - (distance - minDistance) / (maxDistance - minDistance))) * this.opacityFworker; // // // 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 factor with smoother distance falloff -// const forceFactor = Math.pow(1 - distance / FORCE_RADIUS, 1.5) * +// // Calculate base force fworker with smoother distance falloff +// const forceFworker = 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) * forceFactor * movementInfluence; -// const fy = (repelDirY * repelForce * 0.5 + mvy * moveForce) * forceFactor * movementInfluence; +// const fx = (repelDirX * repelForce * 0.5 + mvx * moveForce) * forceFworker * movementInfluence; +// const fy = (repelDirY * repelForce * 0.5 + mvy * moveForce) * forceFworker * movementInfluence; // // particle.applyForce(fx, fy); // } diff --git a/docs/styles/style.css b/docs/styles/style.css index 43170b5a0..b46e7eca7 100644 --- a/docs/styles/style.css +++ b/docs/styles/style.css @@ -292,7 +292,6 @@ display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; - margin-top: 48px; } .library-box { diff --git a/docs/actor/actions.mdx b/docs/workers/actions.mdx similarity index 81% rename from docs/actor/actions.mdx rename to docs/workers/actions.mdx index e82a1c4c6..d1585b12e 100644 --- a/docs/actor/actions.mdx +++ b/docs/workers/actions.mdx @@ -3,23 +3,23 @@ title: Actions icon: bolt --- -Actions are how clients & other actors communicate with actors. Actions are defined as functions in the actor configuration and can be called from clients. +Actions are how clients & other workers communicate with workers. Actions are defined as functions in the worker 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 -actor. +worker. ## Writing Actions -Actions are defined in the `actions` object when creating an actor: +Actions are defined in the `actions` object when creating a worker: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const mathUtils = actor({ +const mathUtils = worker({ 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 { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; // Private helper function - not callable by clients const calculateFee = (amount) => { return amount * 0.05; }; -const paymentProcessor = actor({ +const paymentProcessor = worker({ state: { transactions: [] }, @@ -69,7 +69,7 @@ Actions have a single return value. To stream realtime data in response to an ac Calling actions from the client is simple: ```typescript -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420"); @@ -84,15 +84,15 @@ Calling actions from the client are async and require an `await`, even if the ac ### Type Safety -The actor client includes type safety out of the box. When you use `createClient()`, TypeScript automatically infers action parameter and return types: +The worker client includes type safety out of the box. When you use `createClient()`, TypeScript automatically infers action parameter and return types: ```typescript src/index.ts -import { setup } from "@rivetkit/actor"; +import { setup } from "rivetkit"; // Create simple counter -const counter = actor({ +const counter = worker({ state: { count: 0 }, actions: { increment: (c, count: number) => { @@ -104,14 +104,14 @@ const counter = actor({ // Create and export the app const app = setup({ - actors: { counter } + workers: { counter } }); export type App = typeof app; ``` ```typescript client.ts -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420"); @@ -127,7 +127,7 @@ await counter.nonexistentMethod(123); // TypeScript error ## Error Handling -Actors provide robust error handling out of the box for actions. +Workers provide robust error handling out of the box for actions. ### User Errors @@ -141,10 +141,10 @@ For example: -```typescript actor.ts -import { actor, UserError } from "@rivetkit/actor"; +```typescript worker.ts +import { worker, UserError } from "rivetkit"; -const user = actor({ +const user = worker({ state: { users: [] }, actions: { registerUser: (c, username) => { @@ -167,7 +167,7 @@ const user = actor({ ```typescript client.ts try { - await userActor.registerUser("extremely_long_username_that_exceeds_limit"); + await userWorker.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/actor/doc/~/UserError). */} +{/* Read the documentation for `UserError` [here](https://jsr.io/@rivet-gg/worker/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 { actor, UserError } from "@rivetkit/actor"; +import { worker, 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 = actor({ +const counter = worker({ state: { count: 0 }, actions: { increment: (c, params) => { @@ -224,7 +224,7 @@ const counter = actor({ ## Authentication -By default, clients can call all actions on an actor 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 worker 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 { actor, ActionContextOf } from "@rivetkit/actor"; +import { worker, ActionContextOf } from "rivetkit"; -const counter = actor({ +const counter = worker({ state: { count: 0 }, actions: { diff --git a/docs/actor/authentication.mdx b/docs/workers/authentication.mdx similarity index 80% rename from docs/actor/authentication.mdx rename to docs/workers/authentication.mdx index a6ca32a02..5481ddeff 100644 --- a/docs/actor/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 actor. +Authentication can be handled through the `onBeforeConnect` or `createConnState` lifecycle hook, which acts as middleware before allowing clients to interact with your worker. ## Using `onBeforeConnect` or `createConnState` -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. +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. Throwing an error in `onBeforeConnect` or `createConnState` will abort the connection. Here's a basic example: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const exampleActor = actor({ +const exampleWorker = worker({ state: { - // Actor state... + // Worker state... }, createConnState: async (c, { params }) => { @@ -36,7 +36,7 @@ const exampleActor = actor({ }, actions: { - // Actor actions... + // Worker actions... } }); ``` @@ -46,11 +46,11 @@ const exampleActor = actor({ After authentication, you can access the connection state in any action through the context object: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const authenticatedActor = actor({ +const authenticatedWorker = worker({ state: { - // Actor state... + // Worker state... }, createConnState: (c) => { @@ -80,11 +80,11 @@ const authenticatedActor = actor({ ### With API Server Authentication ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const apiAuthenticatedActor = actor({ +const apiAuthenticatedWorker = worker({ state: { - // Actor state... + // Worker state... }, createConnState: async (c, { params }) => { @@ -108,7 +108,7 @@ const apiAuthenticatedActor = actor({ }, actions: { - // Actor actions... + // Worker actions... } }); ``` @@ -118,14 +118,14 @@ When authentication fails, throwing an error in `createConnState` will prevent t ### With JWT Authentication ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; import jwt from "jsonwebtoken"; const JWT_SECRET = process.env.JWT_SECRET; -const jwtAuthenticatedActor = actor({ +const jwtAuthenticatedWorker = worker({ state: { - // Actor state... + // Worker state... }, createConnState: (c, { params }) => { diff --git a/docs/actor/connections.mdx b/docs/workers/connections.mdx similarity index 79% rename from docs/actor/connections.mdx rename to docs/workers/connections.mdx index 5f1813a0a..17cfc539c 100644 --- a/docs/actor/connections.mdx +++ b/docs/workers/connections.mdx @@ -3,20 +3,20 @@ title: Connections icon: network-wired --- -Connections represent client connections to your actor. They provide a way to handle client authentication, manage connection-specific data, and control the connection lifecycle. +Connections represent client connections to your worker. They provide a way to handle client authentication, manage connection-specific data, and control the connection lifecycle. ## Parameters -When clients connect to an actor, they can pass connection parameters that are handled during the connection process. +When clients connect to a worker, they can pass connection parameters that are handled during the connection process. For example: -```typescript actor.ts -import { actor } from "@rivetkit/actor"; +```typescript worker.ts +import { worker } from "rivetkit"; -const gameRoom = actor({ +const gameRoom = worker({ state: {}, // Handle connection setup @@ -39,7 +39,7 @@ const gameRoom = actor({ ``` ```typescript client.ts -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420"); @@ -52,14 +52,14 @@ const gameRoom = await client.gameRoom.get({ ## Connection State -There are two ways to define an actor's connection state: +There are two ways to define a worker's connection state: ### Method 1: `ConnState` constant ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ state: { messages: [] }, // Define default connection state as a constant @@ -84,9 +84,9 @@ const chatRoom = actor({ 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 { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ 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 [Actor Lifecycle](/concepts/lifecycle) for more details. +See the documentation on [Worker 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 { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ state: { users: {} }, actions: { @@ -158,9 +158,9 @@ const chatRoom = actor({ Connections can be disconnected from within an action: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const secureRoom = actor({ +const secureRoom = worker({ state: {}, actions: { @@ -189,4 +189,4 @@ This ensures the underlying network connections close cleanly before continuing. ## Offline & Auto-Reconnection -See [Interacting with Actors](/concepts/interacting-with-actors#offline-and-auto-reconnection) for details on reconnection behavior. +See [Interacting with Workers](/concepts/interacting-with-workers#offline-and-auto-reconnection) for details on reconnection behavior. diff --git a/docs/actor/events.mdx b/docs/workers/events.mdx similarity index 62% rename from docs/actor/events.mdx rename to docs/workers/events.mdx index 982047417..bb1f65339 100644 --- a/docs/actor/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 actors. +Events are used for clients to receive realtime data from workers. -Events are used for actors to publish updates to clients. Clients call actions to communicate with the actor. +Events are used for workers to publish updates to clients. Clients call actions to communicate with the worker. -## Publishing from actors +## Publishing from workers -Actors can publish events to clients using `c.broadcast` and `conn.send`. +Workers can publish events to clients using `c.broadcast` and `conn.send`. ### Broadcasting events -Actors can publish events to all connected clients with `c.broadcast(name, data)`. For example: +Workers can publish events to all connected clients with `c.broadcast(name, data)`. For example: ```typescript chat_room.ts -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ state: {}, actions: { sendMessage: (c, message) => { @@ -31,7 +31,7 @@ const chatRoom = actor({ ``` ```typescript client.ts -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420"); @@ -43,14 +43,14 @@ await chatRoom.sendMessage('Hello, world!'); ### Sending events to specific connections -Actors can send messages to specific client connections. All connections are available through the context object. For example: +Workers can send messages to specific client connections. All connections are available through the context object. For example: ```typescript chat_room.ts -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ state: {}, actions: { sendPrivateMessage: (c, connId, message) => { @@ -64,7 +64,7 @@ const chatRoom = actor({ ``` ```typescript client.ts -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420"); @@ -76,18 +76,18 @@ await chatRoom.sendPrivateMessage(123, 'Hello, world!'); ## Subscribing from clients -Clients can subscribe to events from actors using `on` and `once`. +Clients can subscribe to events from workers using `on` and `once`. ### `on(eventName, callback)` -{/* [Documentation](https://jsr.io/@rivet-gg/actor-client/doc/~/ActorHandleRaw.prototype.on.html) */} +{/* [Documentation](https://jsr.io/@rivet-gg/worker-client/doc/~/WorkerHandleRaw.prototype.on.html) */} -Clients can subscribe to events that will happen repeatedly using `actor.on(name, callback)`. For example: +Clients can subscribe to events that will happen repeatedly using `worker.on(name, callback)`. For example: ```typescript client.ts -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420"); @@ -99,9 +99,9 @@ chatRoom.on('newMessage', ({ message }) => { ``` ```typescript chat_room.ts -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ state: {}, actions: { sendMessage: (c, message) => { @@ -115,14 +115,14 @@ const chatRoom = actor({ ### `once(eventName, callback)` -{/* [Documentation](https://jsr.io/@rivet-gg/actor-client/doc/~/ActorHandleRaw.prototype.once.html) */} +{/* [Documentation](https://jsr.io/@rivet-gg/worker-client/doc/~/WorkerHandleRaw.prototype.once.html) */} -Clients can listen for an event only one time with `actor.once(name, callback)`. For example: +Clients can listen for an event only one time with `worker.once(name, callback)`. For example: ```typescript client.ts -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420"); @@ -137,9 +137,9 @@ await chatRoom.requestJoin(); ``` ```typescript chat_room.ts -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ state: { pendingJoinRequests: [] }, @@ -161,6 +161,6 @@ const chatRoom = actor({ ## Connections -Connections are used to communicate with clients from the actor. +Connections are used to communicate with clients from the worker. Read more about connections [here](/concepts/connections). diff --git a/docs/actor/lifecycle.mdx b/docs/workers/lifecycle.mdx similarity index 70% rename from docs/actor/lifecycle.mdx rename to docs/workers/lifecycle.mdx index 4c7879e08..9b20d9171 100644 --- a/docs/actor/lifecycle.mdx +++ b/docs/workers/lifecycle.mdx @@ -5,32 +5,32 @@ icon: rotate ## Lifecycle Hooks -Actor lifecycle hooks are defined as functions in the actor configuration. +Worker lifecycle hooks are defined as functions in the worker configuration. ### `createState` and `state` -{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onInitialize) */} +{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._onInitialize) */} -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. +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. ### `createVars` and `vars` -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 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 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 { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; // Using vars constant -const counter1 = actor({ +const counter1 = worker({ state: { count: 0 }, vars: { lastAccessTime: 0 }, actions: { /* ... */ } }); // Using createVars function -const counter2 = actor({ +const counter2 = worker({ state: { count: 0 }, createVars: () => { // Initialize with non-serializable objects @@ -43,7 +43,7 @@ const counter2 = actor({ }); // Access driver-specific context -const exampleActor = actor({ +const exampleWorker = worker({ state: { count: 0 }, // Access driver context in createVars createVars: (c, rivet) => ({ @@ -63,16 +63,16 @@ const exampleActor = actor({ 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 { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; // Using state constant -const counter1 = actor({ +const counter1 = worker({ state: { count: 0 }, actions: { /* ... */ } }); // Using createState function -const counter2 = actor({ +const counter2 = worker({ createState: () => { // Initialize with a count of 0 return { count: 0 }; @@ -81,12 +81,12 @@ const counter2 = actor({ }); // Using onCreate -const counter3 = actor({ +const counter3 = worker({ state: { count: 0 }, // Run initialization logic (logging, external service setup, etc.) onCreate: (c) => { - console.log("Counter actor initialized"); + console.log("Counter worker initialized"); // Can perform async operations or setup // No need to return anything }, @@ -97,22 +97,22 @@ const counter3 = actor({ ### `onStart` -{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onStart) */} +{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._onStart) */} -This hook is called any time the actor is started (e.g. after restarting, upgrading code, or crashing). +This hook is called any time the worker is started (e.g. after restarting, upgrading code, or crashing). -This is called after the actor has been initialized but before any connections are accepted. +This is called after the worker 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 { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const counter = actor({ +const counter = worker({ state: { count: 0 }, onStart: (c) => { - console.log('Actor started with count:', c.state.count); + console.log('Worker started with count:', c.state.count); // Set up interval for automatic counting const intervalId = setInterval(() => { @@ -130,14 +130,14 @@ const counter = actor({ ### `onStateChange` -{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onStateChange) */} +{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._onStateChange) */} -Called whenever the actor's state changes. This is often used to broadcast state updates. +Called whenever the worker's state changes. This is often used to broadcast state updates. ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const counter = actor({ +const counter = worker({ state: { count: 0 }, onStateChange: (c, newState) => { @@ -158,7 +158,7 @@ const counter = actor({ ### `createConnState` and `connState` -{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._createConnState) */} +{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.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/actor/doc/~/Actor.prototype._onBeforeConnect) */} +{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._onBeforeConnect) */} -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 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 does NOT return connection state - it's used solely for validation. ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ state: { messages: [] }, // Method 1: Use a static default connection state @@ -209,18 +209,18 @@ const chatRoom = actor({ }); ``` -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. +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. ### `onConnect` -{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onConnect) */} +{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._onConnect) */} Executed after the client has successfully connected. ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ state: { users: {}, messages: [] }, onConnect: (c) => { @@ -241,18 +241,18 @@ const chatRoom = actor({ }); ``` -Messages will not be processed for this actor until this hook succeeds. Errors thrown from this hook will cause the client to disconnect. +Messages will not be processed for this worker until this hook succeeds. Errors thrown from this hook will cause the client to disconnect. ### `onDisconnect` -{/* [Documentation](https://jsr.io/@rivet-gg/actor/doc/~/Actor.prototype._onDisconnect) */} +{/* [Documentation](https://jsr.io/@rivet-gg/worker/doc/~/Worker.prototype._onDisconnect) */} -Called when a client disconnects from the actor. Use this to clean up any connection-specific resources. +Called when a client disconnects from the worker. Use this to clean up any connection-specific resources. ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ state: { users: {}, messages: [] }, onDisconnect: (c) => { @@ -273,14 +273,14 @@ const chatRoom = actor({ }); ``` -## Destroying Actors +## Destroying Workers -Actors can be shut down gracefully with `c.shutdown()`. Clients will be gracefully disconnected. +Workers can be shut down gracefully with `c.shutdown()`. Clients will be gracefully disconnected. ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const temporaryRoom = actor({ +const temporaryRoom = worker({ state: { createdAt: 0, expiresAfterMs: 3600000 // 1 hour @@ -314,7 +314,7 @@ const temporaryRoom = actor({ // Notify all clients c.broadcast("roomClosed", { reason: "Admin closed the room" }); - // Shutdown the actor + // Shutdown the worker c.shutdown(); } } @@ -323,42 +323,42 @@ const temporaryRoom = actor({ This action is permanent and cannot be reverted. -## Using `ActorContext` Type Externally +## Using `WorkerContext` 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 { actor, ActorContextOf } from "@rivetkit/actor"; +import { worker, WorkerContextOf } from "rivetkit"; -const myActor = actor({ +const myWorker = worker({ state: { count: 0 }, // Use external function in lifecycle hook - onStart: (c) => logActorStarted(c) + onStart: (c) => logWorkerStarted(c) }); // Simple external function with typed context -function logActorStarted(c: ActorContextOf) { - console.log(`Actor started with count: ${c.state.count}`); +function logWorkerStarted(c: WorkerContextOf) { + console.log(`Worker started with count: ${c.state.count}`); } ``` -See [Helper Types](/concepts/types) for more details on using `ActorContextOf`. +See [Helper Types](/concepts/types) for more details on using `WorkerContextOf`. ## Full Example ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const counter = actor({ +const counter = worker({ // Initialize state createState: () => ({ count: 0 }), - // Initialize actor (run setup that doesn't affect initial state) + // Initialize worker (run setup that doesn't affect initial state) onCreate: (c) => { - console.log('Counter actor initialized'); + console.log('Counter worker initialized'); // Set up external resources, etc. }, diff --git a/docs/actor/metadata.mdx b/docs/workers/metadata.mdx similarity index 76% rename from docs/actor/metadata.mdx rename to docs/workers/metadata.mdx index 32acd737e..4df0ecfe0 100644 --- a/docs/actor/metadata.mdx +++ b/docs/workers/metadata.mdx @@ -3,7 +3,7 @@ title: Metadata icon: tag --- -Metadata provides information about the currently running actor. +Metadata provides information about the currently running worker. ## Region @@ -20,9 +20,9 @@ For example: ```typescript chat_room.ts -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ state: { messages: [] }, @@ -53,7 +53,7 @@ export default chatRoom; ``` ```typescript client.ts -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420"); @@ -74,12 +74,12 @@ const teamChannel = await client.chatRoom.get({ -## Actor Name +## Worker Name -You can access the actor name with: +You can access the worker name with: ```typescript -const actorName = c.name; +const workerName = c.name; ``` -This is useful when you need to know which actor type is running, especially if you have generic utility functions that are shared between different actor implementations. +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. diff --git a/docs/actor/overview.mdx b/docs/workers/overview.mdx similarity index 54% rename from docs/actor/overview.mdx rename to docs/workers/overview.mdx index a205cefac..0caeb64f6 100644 --- a/docs/actor/overview.mdx +++ b/docs/workers/overview.mdx @@ -1,41 +1,41 @@ --- -title: Rivet Actors +title: Rivet Workers icon: square-info sidebarTitle: "Overview" description: A library for building stateful, scalable, realtime backend applications. --- -import CreateActorCli from "/snippets/create-actor-cli.mdx"; +import CreateWorkerCli from "/snippets/create-worker-cli.mdx"; -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. +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. ## Concepts -The core concepts that power Rivet Actor applications: +The core concepts that power Rivet Worker 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**: 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 an actor +- **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 - **Actions Are Low-Latency**: Actions provide WebSocket-like performance for time-sensitive operations -- **Broadcast Updates With Events**: Actors can publish real-time updates to connected clients +- **Broadcast Updates With Events**: Workers can publish real-time updates to connected clients ## Quickstart Run this to get started: - + ## Code Example -Here's a complete chat room actor that maintains state and handles messages. We'll explore each component in depth throughout this document: +Here's a complete chat room worker that maintains state and handles messages. We'll explore each component in depth throughout this document: ```typescript chat_room.ts -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -// Define a chat room actor -const chatRoom = actor({ - // Initialize state when the actor is first created +// Define a chat room worker +const chatRoom = worker({ + // Initialize state when the worker is first created createState: () => ({ messages: [] }), @@ -63,15 +63,15 @@ export default chatRoom; ## Using the App -To start using your actor, create an app and serve it: +To start using your worker, create an app and serve it: ```typescript app.ts -import { setup, serve } from "@rivetkit/actor"; +import { setup, serve } from "rivetkit"; import chatRoom from "./chat_room"; // Create the application const app = setup({ - actors: { chatRoom } + workers: { chatRoom } }); // Start serving on default port @@ -81,17 +81,17 @@ serve(app); export type App = typeof app; ``` -## Key Actor Components +## Key Worker Components ### State -Actors maintain state that's stored in memory and automatically persisted. State is defined either as a constant or via a `createState` function: +Workers maintain state that's stored in memory and automatically persisted. State is defined either as a constant or via a `createState` function: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; // Method 1: State constant -const counter1 = actor({ +const counter1 = worker({ state: { count: 0 }, actions: { // ... @@ -99,7 +99,7 @@ const counter1 = actor({ }); // Method 2: CreateState function -const counter2 = actor({ +const counter2 = worker({ createState: () => ({ count: 0 }), actions: { // ... @@ -110,9 +110,9 @@ const counter2 = actor({ Update state by modifying `c.state` in your actions: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const counter = actor({ +const counter = worker({ state: { count: 0 }, actions: { // Example of state update in an action @@ -126,16 +126,16 @@ const counter = actor({ These changes are durable and are automatically persisted across updates, restarts, and crashes. -Learn more about [state management](/actor/state). +Learn more about [state management](/worker/state). ### Actions -Actions are functions defined in your actor configuration that clients & other actors can call: +Actions are functions defined in your worker configuration that clients & other workers can call: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const mathUtils = actor({ +const mathUtils = worker({ state: {}, actions: { multiplyByTwo: (c, x) => { @@ -147,16 +147,16 @@ const mathUtils = actor({ 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](/actor/actions). +Learn more about [actions](/worker/actions). ### Events -Actors can broadcast events to connected clients: +Workers can broadcast events to connected clients: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const inventory = actor({ +const inventory = worker({ createState: () => ({ items: [] }), @@ -176,9 +176,9 @@ const inventory = actor({ You can also send events to specific clients: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const messageService = actor({ +const messageService = worker({ state: {}, actions: { sendPrivateMessage: (c, userId, text) => { @@ -192,14 +192,14 @@ const messageService = actor({ }); ``` -Learn more about [events](/actor/events). +Learn more about [events](/worker/events). -## Actor Tags +## Worker Tags -Tags are key-value pairs attached to actors that serve two purposes: +Tags are key-value pairs attached to workers that serve two purposes: -1. **Actor Discovery**: Find specific actors using `client.get(tags)` -2. **Organization**: Group related actors for management purposes +1. **Worker Discovery**: Find specific workers using `client.get(tags)` +2. **Organization**: Group related workers for management purposes For example, you can query chat rooms by tag like: @@ -210,7 +210,7 @@ await client.chatRoom.get({ channel: "random" }); ### Common Tag Patterns ```typescript -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "./src/index"; const client = createClient("http://localhost:6420"); @@ -228,16 +228,16 @@ const document = await client.document.get({ }); ``` -## Actor Lifecycle +## Worker Lifecycle -Actors are created automatically when needed and persist until explicitly shutdown. +Workers are created automatically when needed and persist until explicitly shutdown. -To shut down an actor, use `c.shutdown()` from within an action: +To shut down a worker, use `c.shutdown()` from within an action: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ createState: () => ({ messages: [] }), @@ -246,36 +246,36 @@ const chatRoom = actor({ // Do any cleanup needed c.broadcast("roomClosed"); - // Shutdown the actor + // Shutdown the worker c.shutdown(); } } }); ``` -Learn more about the [actor lifecycle](/actor/lifecycle). +Learn more about the [worker lifecycle](/worker/lifecycle). ## Documentation -Learn more about Rivet Actors: +Learn more about Rivet Workers: - - Get started with Rivet Actors in minutes + + Get started with Rivet Workers in minutes - - Understand how actor state is managed, persisted, and accessed. + + Understand how worker state is managed, persisted, and accessed. - - Define and implement actor actions (RPCs) for client interaction. + + Define and implement worker actions (RPCs) for client interaction. - + Real-time communication with events and broadcasts. - - Managing the creation, execution, and termination of actors. + + Managing the creation, execution, and termination of workers. - - Schedule tasks and alarms with actors for time-based operations. + + Schedule tasks and alarms with workers for time-based operations. \ No newline at end of file diff --git a/docs/actor/quickstart.mdx b/docs/workers/quickstart.mdx similarity index 79% rename from docs/actor/quickstart.mdx rename to docs/workers/quickstart.mdx index e5c3ddef0..c26393c90 100644 --- a/docs/actor/quickstart.mdx +++ b/docs/workers/quickstart.mdx @@ -8,16 +8,16 @@ description: Start building awesome documentation in under 5 minutes ```sh -npm install rivetkit/actor +npm install rivetkit/worker ``` - + ```ts -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; -const myActor = actor({ +const myWorker = worker({ }); ``` @@ -52,13 +52,13 @@ const myActor = actor({ ```ts Hono const app = new Hono(); -app.route("rivetkit", setup({ myWf, myActor })); +app.route("rivetkit", setup({ myWf, myWorker })); serve(app); ``` ```ts Express.js const app = new Hono(); -app.route("rivetkit", setup({ myWf, myActor })); +app.route("rivetkit", setup({ myWf, myWorker })); serve(app); ``` @@ -115,7 +115,7 @@ rivet deploy - + diff --git a/docs/actor/schedule.mdx b/docs/workers/schedule.mdx similarity index 85% rename from docs/actor/schedule.mdx rename to docs/workers/schedule.mdx index 026037308..79c0b7a63 100644 --- a/docs/actor/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 actor scheduler is like `setTimeout`, except the timeout will persist even if the actor restarts, upgrades, or crashes. +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. ## 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 actor restarts, upgrades, or crashes. +Schedules a function to be executed after a specified duration. This function persists across worker 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 actor restarts, upgrades, or crashes. +Schedules a function to be executed at a specific timestamp. This function persists across worker 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 { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const reminderService = actor({ +const reminderService = worker({ state: { reminders: {} }, diff --git a/docs/actor/state.mdx b/docs/workers/state.mdx similarity index 68% rename from docs/actor/state.mdx rename to docs/workers/state.mdx index c86470848..5f644fdf7 100644 --- a/docs/actor/state.mdx +++ b/docs/workers/state.mdx @@ -3,26 +3,26 @@ title: State icon: floppy-disk --- -Actor state provides the best of both worlds: it's stored in-memory and persisted automatically. This lets you work with the data without added latency while still being able to survive crashes & upgrades. +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. **Using External SQL Databases** -Actors can also be used with external SQL databases. This can be useful to integrate actors with existing +Workers can also be used with external SQL databases. This can be useful to integrate workers with existing applications or for storing relational data. Read more [here](/concepts/external-sql). ## Initializing State -There are two ways to define an actor's initial state: +There are two ways to define a worker's initial state: **Method 1: Static Initial State** ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; // Simple state with a constant -const counter = actor({ +const counter = worker({ // Define state as a constant state: { count: 0 }, @@ -35,10 +35,10 @@ const counter = actor({ **Method 2: Dynamic Initial State** ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; // State with initialization logic -const counter = actor({ +const counter = worker({ // Define state using a creation function createState: () => { return { count: 0 }; @@ -50,16 +50,16 @@ const counter = actor({ }); ``` -The `createState` function is called once when the actor is first created. See [Lifecycle](/concepts/lifecycle) for more details. +The `createState` function is called once when the worker 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 { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const counter = actor({ +const counter = worker({ state: { count: 0 }, actions: { @@ -84,14 +84,14 @@ Only state stored in the `state` object will be persisted. Any other variables o ## State Saves -Actors automatically handle persisting state transparently. This happens at the end of every action if the state has changed. +Workers 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 { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; -const criticalProcess = actor({ +const criticalProcess = worker({ state: { steps: [], currentStep: 0 @@ -120,34 +120,34 @@ const criticalProcess = actor({ ## State Isolation -Each actor's state is completely isolated, meaning it cannot be accessed directly by other actors or clients. This allows actors to maintain a high level of security and data integrity, ensuring that state changes are controlled and predictable. +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. -To interact with an actor'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 worker's state, you must use [Actions](/concepts/actions). Actions provide a controlled way to read from and write to the state. -## Sharing State Between Actors +## Sharing State Between Workers -If you need a shared state between multiple actors, you have two options: +If you need a shared state between multiple workers, you have two options: -1. Create an actor that holds the shared state that other actors can make action calls to +1. Create a worker that holds the shared state that other workers 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 actor 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 worker is running or data that cannot be serialized. -`vars` is designed to complement `state`, not replace it. Most actors should use both: `state` for critical business data and `vars` for ephemeral or non-serializable data. +`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. ### Initializing Variables -There are two ways to define an actor's initial vars: +There are two ways to define a worker's initial vars: **Method 1: Static Initial Variables** ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; // Define vars as a constant -const counter = actor({ +const counter = worker({ 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 { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; // Define vars with initialization logic -const counter = actor({ +const counter = worker({ state: { count: 0 }, // Define vars using a creation function @@ -194,10 +194,10 @@ const counter = actor({ Vars can be accessed and modified through the context object with `c.vars`: ```typescript -import { actor } from "@rivetkit/actor"; +import { worker } from "rivetkit"; import { createNanoEvents } from "nanoevents"; -const counter = actor({ +const counter = worker({ // Persistent state - saved to storage state: { count: 0 }, @@ -230,7 +230,7 @@ const counter = actor({ ### When to Use `vars` vs `state` -In practice, most actors will use both: `state` for critical business data and `vars` for ephemeral, non-serializable, or performance-sensitive data. +In practice, most workers 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 actor sleeps, restarts, updates, or crashes -- The information is essential to the actor's core functionality and business logic +- 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 ## Limitations diff --git a/docs/actor/types.mdx b/docs/workers/types.mdx similarity index 58% rename from docs/actor/types.mdx rename to docs/workers/types.mdx index feacdb211..44a079705 100644 --- a/docs/actor/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 actors in a type-safe way. +RivetKit provides several TypeScript helper types to make it easier to work with workers in a type-safe way. ## `Context` Types -When working with actors, you often need to access the context object. RivetKit provides helper types to extract the context types from actor definitions. +When working with workers, you often need to access the context object. RivetKit provides helper types to extract the context types from worker definitions. -### `ActorContextOf` +### `WorkerContextOf` -Extracts the full actor context type from an actor definition. This is the type of the context object (`c`) available in lifecycle hooks such as `onCreate`, `onStart`, etc. +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. ```typescript -import { actor, ActorContextOf } from "@rivetkit/actor"; +import { worker, WorkerContextOf } from "rivetkit"; -const chatRoom = actor({ +const chatRoom = worker({ state: { messages: [] }, actions: { sendMessage: (c, message) => { @@ -25,7 +25,7 @@ const chatRoom = actor({ }); // Extract the chat room context type -type ChatRoomContext = ActorContextOf; +type ChatRoomContext = WorkerContextOf; // 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 an actor definition. This is the type of the context object (`c`) available in action handlers. +Extracts the action context type from a worker definition. This is the type of the context object (`c`) available in action handlers. ```typescript -import { actor, ActionContextOf } from "@rivetkit/actor"; +import { worker, ActionContextOf } from "rivetkit"; -const counter = actor({ +const counter = worker({ state: { count: 0 }, actions: { increment: (c) => { diff --git a/docs/workflows/overview.mdx b/docs/workflows/overview.mdx new file mode 100644 index 000000000..ec51d8b17 --- /dev/null +++ b/docs/workflows/overview.mdx @@ -0,0 +1,8 @@ +--- +title: Rivet Workflows +icon: square-info +sidebarTitle: "Overview" +description: TODO +--- + +TODO diff --git a/examples/chat-room-python/actors/app.ts b/examples/chat-room-python/actors/app.ts index 589544a7b..e48f81b22 100644 --- a/examples/chat-room-python/actors/app.ts +++ b/examples/chat-room-python/actors/app.ts @@ -1,4 +1,4 @@ -import { actor, setup } from "@rivetkit/actor"; +import { actor, setup } from "rivetkit"; // state managed by the actor export interface State { diff --git a/examples/chat-room-python/package.json b/examples/chat-room-python/package.json index af699b5d7..4ec21f0db 100644 --- a/examples/chat-room-python/package.json +++ b/examples/chat-room-python/package.json @@ -9,8 +9,8 @@ "pytest": "pytest tests/test_chat_room.py -v" }, "devDependencies": { - "@rivetkit/actor": "workspace:*", "@types/node": "^22.13.9", + "rivetkit": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.5.2" }, diff --git a/examples/chat-room/actors/app.ts b/examples/chat-room/actors/app.ts index 589544a7b..e48f81b22 100644 --- a/examples/chat-room/actors/app.ts +++ b/examples/chat-room/actors/app.ts @@ -1,4 +1,4 @@ -import { actor, setup } from "@rivetkit/actor"; +import { actor, setup } from "rivetkit"; // state managed by the actor export interface State { diff --git a/examples/chat-room/package.json b/examples/chat-room/package.json index 1cbcc9cb5..863d73b14 100644 --- a/examples/chat-room/package.json +++ b/examples/chat-room/package.json @@ -9,10 +9,10 @@ "test": "vitest run" }, "devDependencies": { - "@rivetkit/actor": "workspace:*", "@types/node": "^22.13.9", "@types/prompts": "^2", "prompts": "^2.4.2", + "rivetkit": "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 eb0a93858..d1e17713c 100644 --- a/examples/chat-room/scripts/cli.ts +++ b/examples/chat-room/scripts/cli.ts @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "../actors/app"; import prompts from "prompts"; diff --git a/examples/chat-room/scripts/connect.ts b/examples/chat-room/scripts/connect.ts index 5720e685c..922ece3a4 100644 --- a/examples/chat-room/scripts/connect.ts +++ b/examples/chat-room/scripts/connect.ts @@ -1,5 +1,5 @@ /// -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "../actors/app"; async function main() { diff --git a/examples/chat-room/tests/chat-room.test.ts b/examples/chat-room/tests/chat-room.test.ts index 70b1d9674..1f00fde11 100644 --- a/examples/chat-room/tests/chat-room.test.ts +++ b/examples/chat-room/tests/chat-room.test.ts @@ -1,5 +1,5 @@ import { test, expect } from "vitest"; -import { setupTest } from "@rivetkit/actor/test"; +import { setupTest } from "rivetkit/test"; import { app } from "../actors/app"; test("chat room should handle messages", async (test) => { diff --git a/examples/counter/actors/app.ts b/examples/counter/actors/app.ts index 12b52fc44..0c20944fc 100644 --- a/examples/counter/actors/app.ts +++ b/examples/counter/actors/app.ts @@ -1,4 +1,4 @@ -import { actor, setup } from "@rivetkit/actor"; +import { actor, setup } from "rivetkit"; const counter = actor({ state: { count: 0 }, diff --git a/examples/counter/package.json b/examples/counter/package.json index 9cb152723..c76b69bdc 100644 --- a/examples/counter/package.json +++ b/examples/counter/package.json @@ -9,8 +9,8 @@ "test": "vitest run" }, "devDependencies": { - "@rivetkit/actor": "workspace:*", "@types/node": "^22.13.9", + "rivetkit": "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 108527bec..ca865a603 100644 --- a/examples/counter/scripts/connect.ts +++ b/examples/counter/scripts/connect.ts @@ -1,5 +1,5 @@ /// -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import type { App } from "../actors/app"; async function main() { diff --git a/examples/counter/tests/counter.test.ts b/examples/counter/tests/counter.test.ts index 7a4924836..d4b16c366 100644 --- a/examples/counter/tests/counter.test.ts +++ b/examples/counter/tests/counter.test.ts @@ -1,5 +1,5 @@ import { test, expect } from "vitest"; -import { setupTest } from "@rivetkit/actor/test"; +import { setupTest } from "rivetkit/test"; import { app } from "../actors/app"; test("it should count", async (test) => { diff --git a/examples/linear-coding-agent/package.json b/examples/linear-coding-agent/package.json index 8ed46b304..2f9bd09ee 100644 --- a/examples/linear-coding-agent/package.json +++ b/examples/linear-coding-agent/package.json @@ -11,13 +11,13 @@ "check-types": "tsc --noEmit" }, "devDependencies": { - "@rivetkit/actor": "workspace:*", "@types/dotenv": "^8.2.3", "@types/express": "^5", "@types/node": "^22.13.9", "@types/prompts": "^2", "concurrently": "^9.1.2", "prompts": "^2.4.2", + "rivetkit": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.5.2", "vitest": "^3.1.1" diff --git a/examples/linear-coding-agent/src/actors/app.ts b/examples/linear-coding-agent/src/actors/app.ts index 0120ac48a..993c8d923 100644 --- a/examples/linear-coding-agent/src/actors/app.ts +++ b/examples/linear-coding-agent/src/actors/app.ts @@ -1,4 +1,4 @@ -import { setup } from "@rivetkit/actor"; +import { setup } from "rivetkit"; import dotenv from "dotenv"; import { codingAgent } from "./coding-agent/mod"; diff --git a/examples/linear-coding-agent/src/actors/coding-agent/mod.ts b/examples/linear-coding-agent/src/actors/coding-agent/mod.ts index 09fbfa7c4..bdcd8b107 100644 --- a/examples/linear-coding-agent/src/actors/coding-agent/mod.ts +++ b/examples/linear-coding-agent/src/actors/coding-agent/mod.ts @@ -1,4 +1,4 @@ -import { type ActionContextOf, type ActorContextOf, actor } from "@rivetkit/actor"; +import { type ActionContextOf, type ActorContextOf, actor } from "rivetkit"; import type { CodingAgentState, CodingAgentVars, diff --git a/examples/linear-coding-agent/src/server/index.ts b/examples/linear-coding-agent/src/server/index.ts index 086915f6e..0bf93866f 100644 --- a/examples/linear-coding-agent/src/server/index.ts +++ b/examples/linear-coding-agent/src/server/index.ts @@ -1,7 +1,7 @@ import { Hono } from "hono"; import { serve } from "@hono/node-server"; import dotenv from "dotenv"; -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { app } from "../actors/app"; import type { App } from "../actors/app"; import type { LinearWebhookEvent } from "../types"; diff --git a/examples/resend-streaks/actors/app.ts b/examples/resend-streaks/actors/app.ts index 1aea7ee3c..75b1e2aad 100644 --- a/examples/resend-streaks/actors/app.ts +++ b/examples/resend-streaks/actors/app.ts @@ -1,5 +1,5 @@ import { TZDate } from "@date-fns/tz"; -import { UserError, actor, setup } from "@rivetkit/actor"; +import { UserError, actor, setup } from "rivetkit"; import { addDays, set } from "date-fns"; import { Resend } from "resend"; diff --git a/examples/resend-streaks/package.json b/examples/resend-streaks/package.json index 19af4f080..26b504499 100644 --- a/examples/resend-streaks/package.json +++ b/examples/resend-streaks/package.json @@ -9,8 +9,8 @@ "test": "vitest run" }, "devDependencies": { - "@rivetkit/actor": "workspace:*", "@types/node": "^22.13.9", + "rivetkit": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.7.3", "vitest": "^3.1.1" diff --git a/examples/resend-streaks/tests/user.test.ts b/examples/resend-streaks/tests/user.test.ts index 3e1bd0c37..537b6b432 100644 --- a/examples/resend-streaks/tests/user.test.ts +++ b/examples/resend-streaks/tests/user.test.ts @@ -1,5 +1,5 @@ import { test, expect, vi, beforeEach } from "vitest"; -import { setupTest } from "@rivetkit/actor/test"; +import { setupTest } from "rivetkit/test"; import { app } from "../actors/app"; // Create mock for send method diff --git a/examples/snippets/ai-agent/App.tsx b/examples/snippets/ai-agent/App.tsx index 24b4a40cf..cf8eb050f 100644 --- a/examples/snippets/ai-agent/App.tsx +++ b/examples/snippets/ai-agent/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactActorCore } from "@rivetkit/react"; import { useState, useEffect } from "react"; import type { App } from "../actors/app"; diff --git a/examples/snippets/ai-agent/actor-json.ts b/examples/snippets/ai-agent/actor-json.ts index 2588cb84e..0e4062d6c 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/actor"; +import { actor } from "rivetkit"; 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 2877282cf..6dda2ec17 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/actor"; +import { actor } from "rivetkit"; 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 41be84e12..9aa3b04b0 100644 --- a/examples/snippets/chat-room/App.tsx +++ b/examples/snippets/chat-room/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactActorCore } from "@rivetkit/react"; import { useState, useEffect } from "react"; import type { App } from "../actors/app"; diff --git a/examples/snippets/chat-room/actor-json.ts b/examples/snippets/chat-room/actor-json.ts index 8b279f4f4..05f88b080 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/actor"; +import { actor } from "rivetkit"; 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 de9f9281a..0c58f1777 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/actor"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { messages } from "./schema"; diff --git a/examples/snippets/crdt/App.tsx b/examples/snippets/crdt/App.tsx index e6c602ae3..f63c33a6f 100644 --- a/examples/snippets/crdt/App.tsx +++ b/examples/snippets/crdt/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactActorCore } from "@rivetkit/react"; import { useState, useEffect, useRef } from "react"; import * as Y from 'yjs'; diff --git a/examples/snippets/crdt/actor-json.ts b/examples/snippets/crdt/actor-json.ts index 0ec15a6aa..a906c329e 100644 --- a/examples/snippets/crdt/actor-json.ts +++ b/examples/snippets/crdt/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; 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 0a6c20463..4487c1810 100644 --- a/examples/snippets/crdt/actor-sqlite.ts +++ b/examples/snippets/crdt/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; 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 a560ef7ac..a414b3c74 100644 --- a/examples/snippets/database/App.tsx +++ b/examples/snippets/database/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactActorCore } 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 3fe717717..a800808bb 100644 --- a/examples/snippets/database/actor-json.ts +++ b/examples/snippets/database/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; 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 d316f2bfa..532cb8bb0 100644 --- a/examples/snippets/database/actor-sqlite.ts +++ b/examples/snippets/database/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; 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 839a701b4..591aa7a74 100644 --- a/examples/snippets/document/App.tsx +++ b/examples/snippets/document/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactActorCore } from "@rivetkit/react"; import { useState, useEffect } from "react"; import type { App } from "../actors/app"; diff --git a/examples/snippets/document/actor-json.ts b/examples/snippets/document/actor-json.ts index 928835d46..d48503ca4 100644 --- a/examples/snippets/document/actor-json.ts +++ b/examples/snippets/document/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; 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 2c34de513..2ef7726d1 100644 --- a/examples/snippets/document/actor-sqlite.ts +++ b/examples/snippets/document/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; 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 28ef6e9c0..f3b46fcac 100644 --- a/examples/snippets/game/App.tsx +++ b/examples/snippets/game/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactActorCore } 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 088f20c42..531c778e6 100644 --- a/examples/snippets/game/actor-json.ts +++ b/examples/snippets/game/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; 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 b58a4f88d..3b059cead 100644 --- a/examples/snippets/game/actor-sqlite.ts +++ b/examples/snippets/game/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; 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 19d49ef59..e9a83ff0e 100644 --- a/examples/snippets/rate/App.tsx +++ b/examples/snippets/rate/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactActorCore } from "@rivetkit/react"; import { useState } from "react"; import type { App } from "../actors/app"; diff --git a/examples/snippets/rate/actor-json.ts b/examples/snippets/rate/actor-json.ts index 41570cfb8..8f799713b 100644 --- a/examples/snippets/rate/actor-json.ts +++ b/examples/snippets/rate/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; // 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 374571216..f6745501b 100644 --- a/examples/snippets/rate/actor-sqlite.ts +++ b/examples/snippets/rate/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { limiters } from "./schema"; diff --git a/examples/snippets/stream/App.tsx b/examples/snippets/stream/App.tsx index 98a947dab..260702d89 100644 --- a/examples/snippets/stream/App.tsx +++ b/examples/snippets/stream/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactActorCore } from "@rivetkit/react"; import { useState, useEffect } from "react"; import type { App } from "../actors/app"; diff --git a/examples/snippets/stream/actor-json.ts b/examples/snippets/stream/actor-json.ts index 63bfb3526..61d6e83a8 100644 --- a/examples/snippets/stream/actor-json.ts +++ b/examples/snippets/stream/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; export type StreamState = { topValues: number[]; diff --git a/examples/snippets/stream/actor-sqlite.ts b/examples/snippets/stream/actor-sqlite.ts index 31356f685..9e7e03029 100644 --- a/examples/snippets/stream/actor-sqlite.ts +++ b/examples/snippets/stream/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; 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 4bac5c41b..560aae398 100644 --- a/examples/snippets/sync/App.tsx +++ b/examples/snippets/sync/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactActorCore } 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 a723d2c24..68d236214 100644 --- a/examples/snippets/sync/actor-json.ts +++ b/examples/snippets/sync/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; 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 638ffeae0..26b679856 100644 --- a/examples/snippets/sync/actor-sqlite.ts +++ b/examples/snippets/sync/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { contacts } from "./schema"; diff --git a/examples/snippets/tenant/App.tsx b/examples/snippets/tenant/App.tsx index b1d69bee3..b1d5132a5 100644 --- a/examples/snippets/tenant/App.tsx +++ b/examples/snippets/tenant/App.tsx @@ -1,4 +1,4 @@ -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactActorCore } from "@rivetkit/react"; import { useState, useEffect } from "react"; import type { App } from "../actors/app"; diff --git a/examples/snippets/tenant/actor-json.ts b/examples/snippets/tenant/actor-json.ts index 1d6c54172..21c071b6d 100644 --- a/examples/snippets/tenant/actor-json.ts +++ b/examples/snippets/tenant/actor-json.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; 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 954ebf8ae..629d402b0 100644 --- a/examples/snippets/tenant/actor-sqlite.ts +++ b/examples/snippets/tenant/actor-sqlite.ts @@ -1,4 +1,4 @@ -import { actor } from "@rivetkit/actor"; +import { actor } from "rivetkit"; import { drizzle } from "@rivetkit/drizzle"; import { members, invoices } from "./schema"; import { authenticate } from "./my-utils"; diff --git a/packages/actor/src/actor/definition.ts b/packages/actor/src/actor/definition.ts deleted file mode 100644 index bc3735e2f..000000000 --- a/packages/actor/src/actor/definition.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - type ActorConfig, - type Actions, -} from "./config"; -import { ActorInstance } from "./instance"; -import { ActorContext } from "./context"; -import type { ActionContext } from "./action"; - -export type AnyActorDefinition = ActorDefinition; - -/** - * Extracts the context type from an ActorDefinition - */ -export type ActorContextOf = - AD extends ActorDefinition - ? ActorContext - : never; - -/** - * Extracts the context type from an ActorDefinition - */ -export type ActionContextOf = - AD extends ActorDefinition - ? ActionContext - : never; - -export class ActorDefinition> { - #config: ActorConfig; - - constructor(config: ActorConfig) { - this.#config = config; - } - - instantiate(): ActorInstance { - return new ActorInstance(this.#config); - } -} diff --git a/packages/actor/src/actor/log.ts b/packages/actor/src/actor/log.ts deleted file mode 100644 index 6f97702ca..000000000 --- a/packages/actor/src/actor/log.ts +++ /dev/null @@ -1,16 +0,0 @@ -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/actor/src/actor/mod.ts b/packages/actor/src/actor/mod.ts deleted file mode 100644 index 877fcd10c..000000000 --- a/packages/actor/src/actor/mod.ts +++ /dev/null @@ -1,28 +0,0 @@ -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>( - input: ActorConfigInput, -): ActorDefinition { - const config = ActorConfigSchema.parse(input) as ActorConfig; - return new ActorDefinition(config); -} diff --git a/packages/actor/src/actor/schedule.ts b/packages/actor/src/actor/schedule.ts deleted file mode 100644 index 8948e8ccf..000000000 --- a/packages/actor/src/actor/schedule.ts +++ /dev/null @@ -1,17 +0,0 @@ -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/actor/src/client/actor-common.ts b/packages/actor/src/client/actor-common.ts deleted file mode 100644 index 2616f656d..000000000 --- a/packages/actor/src/client/actor-common.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { AnyActorDefinition, ActorDefinition } from "@/actor/definition"; -import type * as protoHttpResolve from "@/actor/protocol/http/resolve"; -import type { Encoding } from "@/actor/protocol/serde"; -import type { ActorQuery } from "@/manager/protocol/query"; -import { logger } from "./log"; -import * as errors from "./errors"; -import { sendHttpRequest } from "./utils"; -import { HEADER_ACTOR_QUERY, HEADER_ENCODING } from "@/actor/router-endpoints"; - -/** - * Action function returned by Actor connections and handles. - * - * @typedef {Function} ActorActionFunction - * @template Args - * @template Response - * @param {...Args} args - Arguments for the action function. - * @returns {Promise} - */ -export type ActorActionFunction< - Args extends Array = unknown[], - Response = unknown, -> = ( - ...args: Args extends [unknown, ...infer Rest] ? Rest : Args -) => Promise; - -/** - * Maps action methods from actor definition to typed function signatures. - */ -export type ActorDefinitionActions = - AD extends ActorDefinition - ? { - [K in keyof R]: R[K] extends (...args: infer Args) => infer Return - ? ActorActionFunction - : never; - } - : never; - diff --git a/packages/actor/src/client/actor-handle.ts b/packages/actor/src/client/actor-handle.ts deleted file mode 100644 index 0da6fa69b..000000000 --- a/packages/actor/src/client/actor-handle.ts +++ /dev/null @@ -1,151 +0,0 @@ -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. - * @param {string} name - The name of the action function to call. - * @param {...Args} args - The arguments to pass to the action function. - * @returns {Promise} - A promise that resolves to the response of the action function. - */ - async action = unknown[], Response = unknown>( - name: string, - ...args: Args - ): Promise { - return await this.#driver.action( - undefined, - this.#actorQuery, - this.#encodingKind, - this.#params, - name, - ...args, - ); - } - - /** - * 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(): Promise { - if ( - "getForKey" in this.#actorQuery || - "getOrCreateForKey" in this.#actorQuery - ) { - // TODO: - const actorId = await this.#driver.resolveActorId( - undefined, - this.#actorQuery, - this.#encodingKind, - ); - 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 an 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/actor/src/client/client.ts b/packages/actor/src/client/client.ts deleted file mode 100644 index b0f574158..000000000 --- a/packages/actor/src/client/client.ts +++ /dev/null @@ -1,593 +0,0 @@ -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, - SendHttpMessageOpts, -} from "./actor-conn"; -import { ActorHandle, ActorHandleRaw } from "./actor-handle"; -import { ActorActionFunction } from "./actor-common"; -import { logger } from "./log"; -import type { ActorCoreApp } from "@/mod"; -import type { AnyActorDefinition } from "@/actor/definition"; -import type * as wsToServer from "@/actor/protocol/message/to-server"; -import type { EventSource } from "eventsource"; -import { createHttpClientDriver } from "./http-client-driver"; -import { HonoRequest } from "hono"; - -/** Extract the actor registry from the app definition. */ -export type ExtractActorsFromApp> = - A extends ActorCoreApp ? Actors : never; - -/** Extract the app definition from the client. */ -export type ExtractAppFromClient>> = - C extends Client ? A : never; - -/** - * Represents an actor accessor that provides methods to interact with a specific actor. - */ -export interface ActorAccessor { - /** - * Gets a stateless handle to an 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 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): ActorHandle; - - /** - * Gets a stateless handle to an actor by its key, creating it if necessary. - * The actor name is automatically injected from the property accessor. - * - * @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, - ): ActorHandle; - - /** - * Gets a stateless handle to an actor by its ID. - * - * @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(actorId: string, opts?: GetWithIdOptions): ActorHandle; - - /** - * 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 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>; -} - -/** - * Options for configuring the client. - * @typedef {Object} ClientOptions - */ -export interface ClientOptions { - encoding?: Encoding; - transport?: Transport; -} - -/** - * Options for querying actors. - * @typedef {Object} QueryOptions - * @property {unknown} [parameters] - Parameters to pass to the connection. - */ -export interface QueryOptions { - /** Parameters to pass to the connection. */ - params?: unknown; -} - -/** - * Options for getting an actor by ID. - * @typedef {QueryOptions} GetWithIdOptions - */ -export interface GetWithIdOptions extends QueryOptions {} - -/** - * Options for getting an actor. - * @typedef {QueryOptions} GetOptions - */ -export interface GetOptions extends QueryOptions {} - -/** - * Options for getting or creating an actor. - * @typedef {QueryOptions} GetOrCreateOptions - * @property {string} [createInRegion] - Region to create the actor in if it doesn't exist. - */ -export interface GetOrCreateOptions extends QueryOptions { - /** Region to create the actor in if it doesn't exist. */ - createInRegion?: string; - /** Input data to pass to the actor. */ - createWithInput?: unknown; -} - -/** - * Options for creating an actor. - * @typedef {QueryOptions} CreateOptions - * @property {string} [region] - The region to create the actor in. - */ -export interface CreateOptions extends QueryOptions { - /** The region to create the actor in. */ - region?: string; - /** Input data to pass to the actor. */ - input?: unknown; -} - -/** - * Represents a region to connect to. - * @typedef {Object} Region - * @property {string} id - The region ID. - * @property {string} name - The region name. - * @see {@link https://rivet.gg/docs/edge|Edge Networking} - * @see {@link https://rivet.gg/docs/regions|Available Regions} - */ -export interface Region { - /** - * The region slug. - */ - id: string; - - /** - * The human-friendly region name. - */ - name: string; -} - -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>( - req: HonoRequest | undefined, - actorQuery: ActorQuery, - encoding: Encoding, - params: unknown, - name: string, - ...args: Args - ): Promise; - resolveActorId( - req: HonoRequest | undefined, - actorQuery: ActorQuery, - encodingKind: Encoding, - ): Promise; - connectWebSocket( - req: HonoRequest | undefined, - actorQuery: ActorQuery, - encodingKind: Encoding, - ): Promise; - connectSse( - req: HonoRequest | undefined, - actorQuery: ActorQuery, - encodingKind: Encoding, - params: unknown, - ): Promise; - sendHttpMessage( - req: HonoRequest | undefined, - actorId: string, - encoding: Encoding, - connectionId: string, - connectionToken: string, - message: wsToServer.ToServer, - ): Promise; -} - -/** - * Client for managing & connecting to actors. - * - * @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; - - [ACTOR_CONNS_SYMBOL] = new Set(); - - #driver: ClientDriver; - #encodingKind: Encoding; - [TRANSPORT_SYMBOL]: Transport; - - /** - * Creates an instance of Client. - * - * @param {string} managerEndpoint - The manager endpoint. See {@link https://rivet.gg/docs/setup|Initial Setup} for instructions on getting the manager endpoint. - * @param {ClientOptions} [opts] - Options for configuring the client. - * @see {@link https://rivet.gg/docs/setup|Initial Setup} - */ - public constructor(driver: ClientDriver, opts?: ClientOptions) { - this.#driver = driver; - - this.#encodingKind = opts?.encoding ?? "cbor"; - this[TRANSPORT_SYMBOL] = opts?.transport ?? "websocket"; - } - - /** - * Gets a stateless handle to an actor by its ID. - * - * @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( - name: string, - actorId: string, - opts?: GetWithIdOptions, - ): ActorHandle { - logger().debug("get handle to actor with id", { - name, - actorId, - params: opts?.params, - }); - - const actorQuery = { - getForId: { - actorId, - }, - }; - - const handle = this.#createHandle(opts?.params, actorQuery); - return createActorProxy(handle) as ActorHandle; - } - - /** - * Gets a stateless handle to an actor by its key, but does not create the actor if it doesn't exist. - * - * @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( - name: string, - key?: string | string[], - opts?: GetWithIdOptions, - ): ActorHandle { - // Convert string to array of strings - const keyArray: string[] = typeof key === "string" ? [key] : key || []; - - logger().debug("get handle to actor", { - name, - key: keyArray, - parameters: opts?.params, - }); - - const actorQuery: ActorQuery = { - getForKey: { - name, - key: keyArray, - }, - }; - - const handle = this.#createHandle(opts?.params, actorQuery); - return createActorProxy(handle) as ActorHandle; - } - - /** - * Gets a stateless handle to an actor by its key, creating it if necessary. - * - * @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( - name: string, - key?: string | string[], - opts?: GetOrCreateOptions, - ): ActorHandle { - // Convert string to array of strings - const keyArray: string[] = typeof key === "string" ? [key] : key || []; - - logger().debug("get or create handle to actor", { - name, - key: keyArray, - parameters: opts?.params, - createInRegion: opts?.createInRegion, - }); - - const actorQuery: ActorQuery = { - getOrCreateForKey: { - name, - key: keyArray, - input: opts?.createWithInput, - region: opts?.createInRegion, - }, - }; - - const handle = this.#createHandle(opts?.params, actorQuery); - return createActorProxy(handle) as ActorHandle; - } - - /** - * 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 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( - name: string, - key?: string | string[], - opts?: CreateOptions, - ): Promise> { - // Convert string to array of strings - const keyArray: string[] = typeof key === "string" ? [key] : key || []; - - const createQuery = { - create: { - ...opts, - // Do these last to override `opts` - name, - key: keyArray, - }, - } satisfies ActorQuery; - - logger().debug("create actor handle", { - name, - key: keyArray, - parameters: opts?.params, - create: createQuery.create, - }); - - // Create the actor - const actorId = await this.#driver.resolveActorId( - undefined, - createQuery, - this.#encodingKind, - ); - logger().debug("created actor with ID", { - name, - key: keyArray, - actorId, - }); - - // Create handle with actor ID - const getForIdQuery = { - getForId: { - actorId, - }, - } satisfies ActorQuery; - const handle = this.#createHandle(opts?.params, getForIdQuery); - - const proxy = createActorProxy(handle) as ActorHandle; - - return proxy; - } - - #createHandle(params: unknown, actorQuery: ActorQuery): ActorHandleRaw { - return new ActorHandleRaw( - this, - this.#driver, - params, - this.#encodingKind, - actorQuery, - ); - } - - [CREATE_ACTOR_CONN_PROXY]( - conn: ActorConnRaw, - ): ActorConn { - // Save to connection list - this[ACTOR_CONNS_SYMBOL].add(conn); - - // Start connection - conn[CONNECT_SYMBOL](); - - return createActorProxy(conn) as ActorConn; - } - - /** - * Disconnects from all actors. - * - * @returns {Promise} A promise that resolves when all connections are closed. - */ - async dispose(): Promise { - if (this.#disposed) { - logger().warn("client already disconnected"); - return; - } - this.#disposed = true; - - logger().debug("disposing client"); - - const disposePromises = []; - - // Dispose all connections - for (const conn of this[ACTOR_CONNS_SYMBOL].values()) { - disposePromises.push(conn.dispose()); - } - - await Promise.all(disposePromises); - } -} - -/** - * Client type with actor accessors. - * This adds property accessors for actor names to the ClientRaw base class. - * - * @template A The actor application type. - */ -export type Client> = ClientRaw & { - [K in keyof ExtractActorsFromApp]: ActorAccessor< - ExtractActorsFromApp[K] - >; -}; - -export function createClientWithDriver>( - driver: ClientDriver, - opts?: ClientOptions, -): Client { - const client = new ClientRaw(driver, opts); - - // 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 - if (typeof prop === "symbol" || prop in target) { - const value = Reflect.get(target, prop, receiver); - // Preserve method binding - if (typeof value === "function") { - return value.bind(target); - } - return value; - } - - // Handle actor accessor for string properties (actor names) - if (typeof prop === "string") { - // Return actor accessor object with methods - return { - // Handle methods (stateless action) - get: ( - key?: string | string[], - opts?: GetWithIdOptions, - ): ActorHandle[typeof prop]> => { - return target.get[typeof prop]>( - prop, - key, - opts, - ); - }, - getOrCreate: ( - key?: string | string[], - opts?: GetOptions, - ): ActorHandle[typeof prop]> => { - return target.getOrCreate[typeof prop]>( - prop, - key, - opts, - ); - }, - getForId: ( - actorId: string, - opts?: GetWithIdOptions, - ): ActorHandle[typeof prop]> => { - return target.getForId[typeof prop]>( - prop, - actorId, - opts, - ); - }, - create: async ( - key: string | string[], - opts: CreateOptions = {}, - ): Promise[typeof prop]>> => { - return await target.create[typeof prop]>( - prop, - key, - opts, - ); - }, - } as ActorAccessor[typeof prop]>; - } - - return undefined; - }, - }) as Client; -} - -/** - * Creates a proxy for an actor that enables calling actions without explicitly using `.action`. - **/ -function createActorProxy( - handle: ActorHandleRaw | ActorConnRaw, -): ActorHandle | ActorConn { - // Stores returned action functions for faster calls - const methodCache = new Map(); - return new Proxy(handle, { - get(target: ActorHandleRaw, prop: string | symbol, receiver: unknown) { - // Handle built-in Symbol properties - if (typeof prop === "symbol") { - return Reflect.get(target, prop, receiver); - } - - // Handle built-in Promise methods and existing properties - if (prop === "constructor" || prop in target) { - const value = Reflect.get(target, prop, receiver); - // Preserve method binding - if (typeof value === "function") { - return value.bind(target); - } - return value; - } - - // Create action function that preserves 'this' context - if (typeof prop === "string") { - // If JS is attempting to calling this as a promise, ignore it - if (prop === "then") return undefined; - - let method = methodCache.get(prop); - if (!method) { - method = (...args: unknown[]) => target.action(prop, ...args); - methodCache.set(prop, method); - } - return method; - } - }, - - // Support for 'in' operator - has(target: ActorHandleRaw, prop: string | symbol) { - // All string properties are potentially action functions - if (typeof prop === "string") { - return true; - } - // For symbols, defer to the target's own has behavior - return Reflect.has(target, prop); - }, - - // Support instanceof checks - getPrototypeOf(target: ActorHandleRaw) { - return Reflect.getPrototypeOf(target); - }, - - // Prevent property enumeration of non-existent action methods - ownKeys(target: ActorHandleRaw) { - return Reflect.ownKeys(target); - }, - - // Support proper property descriptors - getOwnPropertyDescriptor(target: ActorHandleRaw, prop: string | symbol) { - const targetDescriptor = Reflect.getOwnPropertyDescriptor(target, prop); - if (targetDescriptor) { - return targetDescriptor; - } - if (typeof prop === "string") { - // Make action methods appear non-enumerable - return { - configurable: true, - enumerable: false, - writable: false, - value: (...args: unknown[]) => target.action(prop, ...args), - }; - } - return undefined; - }, - }) as ActorHandle | ActorConn; -} diff --git a/packages/actor/src/driver-test-suite/tests/actor-driver.ts b/packages/actor/src/driver-test-suite/tests/actor-driver.ts deleted file mode 100644 index 22c89fe24..000000000 --- a/packages/actor/src/driver-test-suite/tests/actor-driver.ts +++ /dev/null @@ -1,16 +0,0 @@ -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); - }); -} \ No newline at end of file diff --git a/packages/actor/src/manager/protocol/mod.ts b/packages/actor/src/manager/protocol/mod.ts deleted file mode 100644 index 5ca94297f..000000000 --- a/packages/actor/src/manager/protocol/mod.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { z } from "zod"; -import { ActorQuerySchema } from "./query"; -import { TransportSchema } from "@/actor/protocol/message/mod"; -export * from "./query"; - -export const ActorsRequestSchema = z.object({ - query: ActorQuerySchema, -}); - -export const ActorsResponseSchema = z.object({ - actorId: z.string(), - supportedTransports: z.array(TransportSchema), -}); - -//export const RivetConfigResponseSchema = z.object({ -// endpoint: z.string(), -// project: z.string().optional(), -// environment: z.string().optional(), -//}); - -export type ActorsRequest = z.infer; -export type ActorsResponse = z.infer; -//export type RivetConfigResponse = z.infer; - diff --git a/packages/actor/src/test/driver/actor.ts b/packages/actor/src/test/driver/actor.ts deleted file mode 100644 index 6dd4bddae..000000000 --- a/packages/actor/src/test/driver/actor.ts +++ /dev/null @@ -1,40 +0,0 @@ -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); - } -} diff --git a/packages/actor/src/test/driver/global-state.ts b/packages/actor/src/test/driver/global-state.ts deleted file mode 100644 index 0f3d69fb0..000000000 --- a/packages/actor/src/test/driver/global-state.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { ActorKey } from "@/mod"; - -export interface ActorState { - id: string; - name: string; - key: ActorKey; - persistedData: unknown; - input?: unknown; -} - -export class TestGlobalState { - #actors: Map = new Map(); - - #getActor(actorId: string): ActorState { - const actor = this.#actors.get(actorId); - if (!actor) { - throw new Error(`Actor does not exist for ID: ${actorId}`); - } - return actor; - } - - readInput(actorId: string): unknown | undefined { - return this.#getActor(actorId).input; - } - - readPersistedData(actorId: string): unknown | undefined { - return this.#getActor(actorId).persistedData; - } - - writePersistedData(actorId: string, data: unknown) { - this.#getActor(actorId).persistedData = data; - } - - createActor( - actorId: string, - name: string, - key: ActorKey, - input?: unknown, - ): void { - // 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(`Actor already exists for ID: ${actorId}`); - } - } - - findActor(filter: (actor: ActorState) => boolean): ActorState | undefined { - for (const actor of this.#actors.values()) { - if (filter(actor)) { - return actor; - } - } - return undefined; - } - - getActor(actorId: string): ActorState | undefined { - return this.#actors.get(actorId); - } - - getAllActors(): ActorState[] { - return Array.from(this.#actors.values()); - } -} diff --git a/packages/actor/src/test/driver/manager.ts b/packages/actor/src/test/driver/manager.ts deleted file mode 100644 index aa1c875cb..000000000 --- a/packages/actor/src/test/driver/manager.ts +++ /dev/null @@ -1,145 +0,0 @@ -import type { - GetForIdInput, - GetWithKeyInput, - GetOrCreateWithKeyInput, - ManagerDriver, - CreateInput, -} from "@/driver-helpers/mod"; -import { ActorAlreadyExists } from "@/actor/errors"; -import type { TestGlobalState } from "./global-state"; -import * as crypto from "node:crypto"; -import { ManagerInspector } from "@/inspector/manager"; -import type { ActorCoreApp } from "@/app/mod"; -import { ActorOutput } from "@/manager/driver"; - -export class TestManagerDriver implements ManagerDriver { - #state: TestGlobalState; - - /** - * @internal - */ - inspector: ManagerInspector = new ManagerInspector(this, { - getAllActors: () => this.#state.getAllActors(), - getAllTypesOfActors: () => Object.keys(this.app.config.actors), - }); - - constructor( - private readonly app: ActorCoreApp, - state: TestGlobalState, - ) { - this.#state = state; - } - - async getForId({ actorId }: GetForIdInput): Promise { - // Validate the actor exists - const actor = this.#state.getActor(actorId); - if (!actor) { - return undefined; - } - - return { - actorId, - name: actor.name, - key: actor.key, - }; - } - - async getWithKey({ - name, - key, - }: GetWithKeyInput): Promise { - // NOTE: This is a slow implementation that checks each actor individually. - // This can be optimized with an index in the future. - - const actor = this.#state.findActor((actor) => { - if (actor.name !== name) { - return false; - } - - // handle empty key - if (key === null || key === undefined) { - return actor.key === null || actor.key === undefined; - } - - // handle array - if (Array.isArray(key)) { - if (!Array.isArray(actor.key)) { - return false; - } - if (key.length !== actor.key.length) { - return false; - } - // Check if all elements in key are in actor.key - for (let i = 0; i < key.length; i++) { - if (key[i] !== actor.key[i]) { - return false; - } - } - return true; - } - - // Handle object - if (typeof key === "object" && !Array.isArray(key)) { - if (typeof actor.key !== "object" || Array.isArray(actor.key)) { - return false; - } - if (actor.key === null) { - return false; - } - - // Check if all keys in key are in actor.key - const keyObj = key as Record; - const actorKeyObj = actor.key as unknown as Record; - for (const k in keyObj) { - if (!(k in actorKeyObj) || keyObj[k] !== actorKeyObj[k]) { - return false; - } - } - return true; - } - - // handle scalar - return key === actor.key; - }); - - if (actor) { - return { - actorId: actor.id, - name, - key: actor.key, - }; - } - - return undefined; - } - - async getOrCreateWithKey( - input: GetOrCreateWithKeyInput, - ): Promise { - const getOutput = await this.getWithKey(input); - if (getOutput) { - return getOutput; - } else { - return await this.createActor(input); - } - } - - 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 actorId = crypto.randomUUID(); - this.#state.createActor(actorId, name, key, input); - - this.inspector.onActorsChange(this.#state.getAllActors()); - - return { - actorId, - name, - key, - }; - } -} diff --git a/packages/actor/src/topologies/partition/mod.ts b/packages/actor/src/topologies/partition/mod.ts deleted file mode 100644 index ca533cc9a..000000000 --- a/packages/actor/src/topologies/partition/mod.ts +++ /dev/null @@ -1 +0,0 @@ -export { PartitionTopologyManager, PartitionTopologyActor } from "./topology"; diff --git a/packages/actor/tests/actor-types.test.ts b/packages/actor/tests/actor-types.test.ts deleted file mode 100644 index fa84f1d2f..000000000 --- a/packages/actor/tests/actor-types.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { describe, it, expect, expectTypeOf } from "vitest"; -import { ActorDefinition, type ActorContextOf } from "@/actor/definition"; -import type { ActorContext } from "@/actor/context"; - -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; - } - - interface TestConnParams { - clientId: string; - } - - interface TestConnState { - lastSeen: number; - } - - interface TestVars { - foo: string; - } - - // 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 ActorDefinition< - TestState, - TestConnParams, - TestConnState, - TestVars, - TestActions - >; - - // Use expectTypeOf to verify our type utility works correctly - expectTypeOf>().toEqualTypeOf< - ActorContext - >(); - - // Make sure that different types are not compatible - interface DifferentState { - value: string; - } - - expectTypeOf>().not.toEqualTypeOf< - ActorContext - >(); - }); - }); -}); diff --git a/packages/actor/README.md b/packages/core/README.md similarity index 100% rename from packages/actor/README.md rename to packages/core/README.md diff --git a/packages/actor/fixtures/driver-test-suite/action-inputs.ts b/packages/core/fixtures/driver-test-suite/action-inputs.ts similarity index 75% rename from packages/actor/fixtures/driver-test-suite/action-inputs.ts rename to packages/core/fixtures/driver-test-suite/action-inputs.ts index 483612194..2c81bf2ef 100644 --- a/packages/actor/fixtures/driver-test-suite/action-inputs.ts +++ b/packages/core/fixtures/driver-test-suite/action-inputs.ts @@ -1,12 +1,12 @@ -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; interface State { initialInput?: unknown; onCreateInput?: unknown; } -// Test actor that can capture input during creation -const inputActor = actor({ +// Test worker that can capture input during creation +const inputWorker = worker({ createState: (c, { input }): State => { return { initialInput: input, @@ -29,7 +29,7 @@ const inputActor = actor({ }); export const app = setup({ - actors: { inputActor }, + workers: { inputWorker }, }); export type App = typeof app; diff --git a/packages/actor/fixtures/driver-test-suite/action-timeout.ts b/packages/core/fixtures/driver-test-suite/action-timeout.ts similarity index 72% rename from packages/actor/fixtures/driver-test-suite/action-timeout.ts rename to packages/core/fixtures/driver-test-suite/action-timeout.ts index 4fa691934..2aa998b71 100644 --- a/packages/actor/fixtures/driver-test-suite/action-timeout.ts +++ b/packages/core/fixtures/driver-test-suite/action-timeout.ts @@ -1,7 +1,7 @@ -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; -// Short timeout actor -const shortTimeoutActor = actor({ +// Short timeout worker +const shortTimeoutWorker = worker({ state: { value: 0 }, options: { action: { @@ -20,8 +20,8 @@ const shortTimeoutActor = actor({ }, }); -// Long timeout actor -const longTimeoutActor = actor({ +// Long timeout worker +const longTimeoutWorker = worker({ state: { value: 0 }, options: { action: { @@ -37,8 +37,8 @@ const longTimeoutActor = actor({ }, }); -// Default timeout actor -const defaultTimeoutActor = actor({ +// Default timeout worker +const defaultTimeoutWorker = worker({ state: { value: 0 }, actions: { normalAction: async (c) => { @@ -48,8 +48,8 @@ const defaultTimeoutActor = actor({ }, }); -// Sync actor (timeout shouldn't apply) -const syncActor = actor({ +// Sync worker (timeout shouldn't apply) +const syncWorker = worker({ state: { value: 0 }, options: { action: { @@ -64,11 +64,11 @@ const syncActor = actor({ }); export const app = setup({ - actors: { - shortTimeoutActor, - longTimeoutActor, - defaultTimeoutActor, - syncActor, + workers: { + shortTimeoutWorker, + longTimeoutWorker, + defaultTimeoutWorker, + syncWorker, }, }); diff --git a/packages/actor/fixtures/driver-test-suite/action-types.ts b/packages/core/fixtures/driver-test-suite/action-types.ts similarity index 86% rename from packages/actor/fixtures/driver-test-suite/action-types.ts rename to packages/core/fixtures/driver-test-suite/action-types.ts index 9a19b96eb..fe920fabd 100644 --- a/packages/actor/fixtures/driver-test-suite/action-types.ts +++ b/packages/core/fixtures/driver-test-suite/action-types.ts @@ -1,7 +1,7 @@ -import { actor, setup, UserError } from "@rivetkit/actor"; +import { worker, setup, UserError } from "rivetkit"; -// Actor with synchronous actions -const syncActor = actor({ +// Worker with synchronous actions +const syncWorker = worker({ state: { value: 0 }, actions: { // Simple synchronous action that returns a value directly @@ -23,8 +23,8 @@ const syncActor = actor({ }, }); -// Actor with asynchronous actions -const asyncActor = actor({ +// Worker with asynchronous actions +const asyncWorker = worker({ state: { value: 0, data: null as any }, actions: { // Async action with a delay @@ -55,8 +55,8 @@ const asyncActor = actor({ }, }); -// Actor with promise actions -const promiseActor = actor({ +// Worker with promise actions +const promiseWorker = worker({ state: { results: [] as string[] }, actions: { // Action that returns a resolved promise @@ -82,10 +82,10 @@ const promiseActor = actor({ }); export const app = setup({ - actors: { - syncActor, - asyncActor, - promiseActor, + workers: { + syncWorker, + asyncWorker, + promiseWorker, }, }); diff --git a/packages/actor/fixtures/driver-test-suite/conn-params.ts b/packages/core/fixtures/driver-test-suite/conn-params.ts similarity index 83% rename from packages/actor/fixtures/driver-test-suite/conn-params.ts rename to packages/core/fixtures/driver-test-suite/conn-params.ts index 7e6eb54e4..6f7c48cc3 100644 --- a/packages/actor/fixtures/driver-test-suite/conn-params.ts +++ b/packages/core/fixtures/driver-test-suite/conn-params.ts @@ -1,6 +1,6 @@ -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; -const counterWithParams = actor({ +const counterWithParams = worker({ state: { count: 0, initializers: [] as string[] }, createConnState: (c, { params }: { params: { name?: string } }) => { return { @@ -27,7 +27,7 @@ const counterWithParams = actor({ }); export const app = setup({ - actors: { counter: counterWithParams }, + workers: { counter: counterWithParams }, }); export type App = typeof app; diff --git a/packages/actor/fixtures/driver-test-suite/conn-state.ts b/packages/core/fixtures/driver-test-suite/conn-state.ts similarity index 95% rename from packages/actor/fixtures/driver-test-suite/conn-state.ts rename to packages/core/fixtures/driver-test-suite/conn-state.ts index 7716980a0..81844b87d 100644 --- a/packages/actor/fixtures/driver-test-suite/conn-state.ts +++ b/packages/core/fixtures/driver-test-suite/conn-state.ts @@ -1,4 +1,4 @@ -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; type ConnState = { username: string; @@ -7,7 +7,7 @@ type ConnState = { createdAt: number; }; -const connStateActor = actor({ +const connStateWorker = worker({ state: { sharedCounter: 0, disconnectionCount: 0, @@ -95,7 +95,7 @@ const connStateActor = actor({ }); export const app = setup({ - actors: { connStateActor }, + workers: { connStateWorker }, }); export type App = typeof app; diff --git a/packages/actor/fixtures/driver-test-suite/counter.ts b/packages/core/fixtures/driver-test-suite/counter.ts similarity index 75% rename from packages/actor/fixtures/driver-test-suite/counter.ts rename to packages/core/fixtures/driver-test-suite/counter.ts index 12b52fc44..5b91c482f 100644 --- a/packages/actor/fixtures/driver-test-suite/counter.ts +++ b/packages/core/fixtures/driver-test-suite/counter.ts @@ -1,6 +1,6 @@ -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; -const counter = actor({ +const counter = worker({ state: { count: 0 }, actions: { increment: (c, x: number) => { @@ -15,7 +15,7 @@ const counter = actor({ }); export const app = setup({ - actors: { counter }, + workers: { counter }, }); export type App = typeof app; diff --git a/packages/actor/fixtures/driver-test-suite/error-handling.ts b/packages/core/fixtures/driver-test-suite/error-handling.ts similarity index 88% rename from packages/actor/fixtures/driver-test-suite/error-handling.ts rename to packages/core/fixtures/driver-test-suite/error-handling.ts index 448d3c62a..b717b8366 100644 --- a/packages/actor/fixtures/driver-test-suite/error-handling.ts +++ b/packages/core/fixtures/driver-test-suite/error-handling.ts @@ -1,6 +1,6 @@ -import { actor, setup, UserError } from "@rivetkit/actor"; +import { worker, setup, UserError } from "rivetkit"; -const errorHandlingActor = actor({ +const errorHandlingWorker = worker({ state: { errorLog: [] as string[], }, @@ -68,15 +68,15 @@ const errorHandlingActor = actor({ }, }, options: { - // Set a short timeout for this actor's actions + // Set a short timeout for this worker's actions action: { timeout: 500, // 500ms timeout for actions }, }, }); -// Actor with custom timeout -const customTimeoutActor = actor({ +// Worker with custom timeout +const customTimeoutWorker = worker({ state: {}, actions: { quickAction: async () => { @@ -96,9 +96,9 @@ const customTimeoutActor = actor({ }); export const app = setup({ - actors: { - errorHandlingActor, - customTimeoutActor, + workers: { + errorHandlingWorker, + customTimeoutWorker, }, }); diff --git a/packages/actor/fixtures/driver-test-suite/lifecycle.ts b/packages/core/fixtures/driver-test-suite/lifecycle.ts similarity index 86% rename from packages/actor/fixtures/driver-test-suite/lifecycle.ts rename to packages/core/fixtures/driver-test-suite/lifecycle.ts index 49f6fa0ef..4eec992d4 100644 --- a/packages/actor/fixtures/driver-test-suite/lifecycle.ts +++ b/packages/core/fixtures/driver-test-suite/lifecycle.ts @@ -1,6 +1,6 @@ -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; -const lifecycleActor = actor({ +const lifecycleWorker = worker({ state: { count: 0, events: [] as string[], @@ -35,7 +35,7 @@ const lifecycleActor = actor({ }); export const app = setup({ - actors: { counter: lifecycleActor }, + workers: { counter: lifecycleWorker }, }); export type App = typeof app; diff --git a/packages/actor/fixtures/driver-test-suite/metadata.ts b/packages/core/fixtures/driver-test-suite/metadata.ts similarity index 80% rename from packages/actor/fixtures/driver-test-suite/metadata.ts rename to packages/core/fixtures/driver-test-suite/metadata.ts index 018b5721c..3adfa4cac 100644 --- a/packages/actor/fixtures/driver-test-suite/metadata.ts +++ b/packages/core/fixtures/driver-test-suite/metadata.ts @@ -1,19 +1,19 @@ -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; // Note: For testing only - metadata API will need to be mocked // in tests since this is implementation-specific -const metadataActor = actor({ +const metadataWorker = worker({ state: { lastMetadata: null as any, - actorName: "", + workerName: "", // 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; + // Store the worker name during initialization + c.state.workerName = c.name; }, actions: { // Set up test tags - this will be called by tests to simulate tags @@ -42,8 +42,8 @@ const metadataActor = actor({ return metadata; }, - // Get the actor name - getActorName: (c) => { + // Get the worker name + getWorkerName: (c) => { return c.name; }, @@ -62,9 +62,9 @@ const metadataActor = actor({ return c.state.storedRegion; }, - // Get the stored actor name (from onStart) - getStoredActorName: (c) => { - return c.state.actorName; + // Get the stored worker name (from onStart) + getStoredWorkerName: (c) => { + return c.state.workerName; }, // Get last retrieved metadata @@ -75,7 +75,7 @@ const metadataActor = actor({ }); export const app = setup({ - actors: { metadataActor }, + workers: { metadataWorker }, }); export type App = typeof app; diff --git a/packages/actor/fixtures/driver-test-suite/scheduled.ts b/packages/core/fixtures/driver-test-suite/scheduled.ts similarity index 94% rename from packages/actor/fixtures/driver-test-suite/scheduled.ts rename to packages/core/fixtures/driver-test-suite/scheduled.ts index 3e650f32b..fd2f606e3 100644 --- a/packages/actor/fixtures/driver-test-suite/scheduled.ts +++ b/packages/core/fixtures/driver-test-suite/scheduled.ts @@ -1,6 +1,6 @@ -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; -const scheduled = actor({ +const scheduled = worker({ state: { lastRun: 0, scheduledCount: 0, @@ -76,7 +76,7 @@ const scheduled = actor({ }); export const app = setup({ - actors: { scheduled }, + workers: { scheduled }, }); export type App = typeof app; diff --git a/packages/actor/fixtures/driver-test-suite/vars.ts b/packages/core/fixtures/driver-test-suite/vars.ts similarity index 68% rename from packages/actor/fixtures/driver-test-suite/vars.ts rename to packages/core/fixtures/driver-test-suite/vars.ts index 8e4852785..57bc3fa27 100644 --- a/packages/actor/fixtures/driver-test-suite/vars.ts +++ b/packages/core/fixtures/driver-test-suite/vars.ts @@ -1,10 +1,10 @@ -import { actor, setup } from "@rivetkit/actor"; +import { worker, setup } from "rivetkit"; -// Actor with static vars -const staticVarActor = actor({ +// Worker with static vars +const staticVarWorker = worker({ state: { value: 0 }, connState: { hello: "world" }, - vars: { counter: 42, name: "test-actor" }, + vars: { counter: 42, name: "test-worker" }, actions: { getVars: (c) => { return c.vars; @@ -15,8 +15,8 @@ const staticVarActor = actor({ }, }); -// Actor with nested vars -const nestedVarActor = actor({ +// Worker with nested vars +const nestedVarWorker = worker({ state: { value: 0 }, connState: { hello: "world" }, vars: { @@ -41,14 +41,14 @@ const nestedVarActor = actor({ }, }); -// Actor with dynamic vars -const dynamicVarActor = actor({ +// Worker with dynamic vars +const dynamicVarWorker = worker({ state: { value: 0 }, connState: { hello: "world" }, createVars: () => { return { random: Math.random(), - computed: `Actor-${Math.floor(Math.random() * 1000)}`, + computed: `Worker-${Math.floor(Math.random() * 1000)}`, }; }, actions: { @@ -58,8 +58,8 @@ const dynamicVarActor = actor({ }, }); -// Actor with unique vars per instance -const uniqueVarActor = actor({ +// Worker with unique vars per instance +const uniqueVarWorker = worker({ state: { value: 0 }, connState: { hello: "world" }, createVars: () => { @@ -74,8 +74,8 @@ const uniqueVarActor = actor({ }, }); -// Actor that uses driver context -const driverCtxActor = actor({ +// Worker that uses driver context +const driverCtxWorker = worker({ state: { value: 0 }, connState: { hello: "world" }, createVars: (c, driverCtx: any) => { @@ -91,12 +91,12 @@ const driverCtxActor = actor({ }); export const app = setup({ - actors: { - staticVarActor, - nestedVarActor, - dynamicVarActor, - uniqueVarActor, - driverCtxActor, + workers: { + staticVarWorker, + nestedVarWorker, + dynamicVarWorker, + uniqueVarWorker, + driverCtxWorker, }, }); diff --git a/packages/actor/package.json b/packages/core/package.json similarity index 85% rename from packages/actor/package.json rename to packages/core/package.json index 496b7f580..7d7eeddfb 100644 --- a/packages/actor/package.json +++ b/packages/core/package.json @@ -1,5 +1,5 @@ { - "name": "@rivetkit/actor", + "name": "rivetkit", "version": "0.9.0-rc.1", "license": "Apache-2.0", "files": [ @@ -43,12 +43,12 @@ }, "./errors": { "import": { - "types": "./dist/actor/errors.d.ts", - "default": "./dist/actor/errors.js" + "types": "./dist/worker/errors.d.ts", + "default": "./dist/worker/errors.js" }, "require": { - "types": "./dist/actor/errors.d.cts", - "default": "./dist/actor/errors.cjs" + "types": "./dist/worker/errors.d.cts", + "default": "./dist/worker/errors.cjs" } }, "./utils": { @@ -121,14 +121,14 @@ "default": "./dist/inspector/mod.cjs" } }, - "./inspector/protocol/actor": { + "./inspector/protocol/worker": { "import": { - "types": "./dist/inspector/protocol/actor/mod.d.ts", - "default": "./dist/inspector/protocol/actor/mod.js" + "types": "./dist/inspector/protocol/worker/mod.d.ts", + "default": "./dist/inspector/protocol/worker/mod.js" }, "require": { - "types": "./dist/inspector/protocol/actor/mod.d.cts", - "default": "./dist/inspector/protocol/actor/mod.cjs" + "types": "./dist/inspector/protocol/worker/mod.d.cts", + "default": "./dist/inspector/protocol/worker/mod.cjs" } }, "./inspector/protocol/manager": { @@ -148,7 +148,7 @@ "sideEffects": false, "scripts": { "dev": "yarn build --watch", - "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/actor/errors.ts src/topologies/coordinate/mod.ts src/topologies/partition/mod.ts src/utils.ts src/driver-helpers/mod.ts src/driver-test-suite/mod.ts src/actor/protocol/inspector/mod.ts src/test/mod.ts src/inspector/protocol/actor/mod.ts src/inspector/protocol/manager/mod.ts src/inspector/mod.ts", + "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/worker/errors.ts src/topologies/coordinate/mod.ts src/topologies/partition/mod.ts src/utils.ts src/driver-helpers/mod.ts src/driver-test-suite/mod.ts src/worker/protocol/inspector/mod.ts src/test/mod.ts src/inspector/protocol/worker/mod.ts src/inspector/protocol/manager/mod.ts src/inspector/mod.ts", "check-types": "tsc --noEmit", "boop": "tsc --outDir dist/test -d", "test": "vitest run", diff --git a/packages/actor/scripts/dump-openapi.ts b/packages/core/scripts/dump-openapi.ts similarity index 87% rename from packages/actor/scripts/dump-openapi.ts rename to packages/core/scripts/dump-openapi.ts index e7a61ce85..85e6be0c9 100644 --- a/packages/actor/scripts/dump-openapi.ts +++ b/packages/core/scripts/dump-openapi.ts @@ -1,10 +1,10 @@ import { createManagerRouter } from "@/manager/router"; import { AppConfig, AppConfigSchema, setup } from "@/mod"; -import { ConnectionHandlers } from "@/actor/router-endpoints"; +import { ConnectionHandlers } from "@/worker/router-endpoints"; import { DriverConfig } from "@/driver-helpers/config"; import { TestGlobalState, - TestActorDriver, + TestWorkerDriver, TestManagerDriver, } from "@/test/driver/mod"; import { OpenAPIHono } from "@hono/zod-openapi"; @@ -13,13 +13,13 @@ import * as fs from "node:fs/promises"; import { resolve } from "node:path"; function main() { - const appConfig: AppConfig = AppConfigSchema.parse({ actors: {} }); + const appConfig: AppConfig = AppConfigSchema.parse({ workers: {} }); const app = setup(appConfig); const memoryState = new TestGlobalState(); const driverConfig: DriverConfig = { drivers: { - actor: new TestActorDriver(memoryState), + worker: new TestWorkerDriver(memoryState), manager: new TestManagerDriver(app, memoryState), }, getUpgradeWebSocket: () => () => unimplemented(), @@ -52,7 +52,7 @@ function main() { openapi: "3.0.0", info: { version: VERSION, - title: "ActorCore API", + title: "WorkerCore API", }, }); diff --git a/packages/actor/src/app/config.ts b/packages/core/src/app/config.ts similarity index 70% rename from packages/actor/src/app/config.ts rename to packages/core/src/app/config.ts index 88d392bb4..c6c2fd595 100644 --- a/packages/actor/src/app/config.ts +++ b/packages/core/src/app/config.ts @@ -1,16 +1,16 @@ -//! These configs configs hold anything that's not platform-specific about running actors. +//! These configs configs hold anything that's not platform-specific about running workers. import { z } from "zod"; import type { cors } from "hono/cors"; -import { ActorDefinition, AnyActorDefinition } from "@/actor/definition"; +import { WorkerDefinition, AnyWorkerDefinition } from "@/worker/definition"; import { InspectorConfigSchema } from "@/inspector/config"; // Define CORS options schema type CorsOptions = NonNullable[0]>; -export const ActorPeerConfigSchema = z.object({ +export const WorkerPeerConfigSchema = z.object({ /** - * How long the actor leader holds a lease for. + * How long the worker leader holds a lease for. * * Milliseconds **/ @@ -40,22 +40,22 @@ export const ActorPeerConfigSchema = z.object({ */ messageAckTimeout: z.number().optional().default(1000), }); -export type ActorPeerConfig = z.infer; +export type WorkerPeerConfig = z.infer; -export const ActorsSchema = z.record( +export const WorkersSchema = z.record( z.string(), - z.custom>(), + z.custom>(), ); -export type Actors = z.infer; +export type Workers = z.infer; -/** Base config used for the actor config across all platforms. */ +/** Base config used for the worker config across all platforms. */ export const AppConfigSchema = z.object({ - actors: z.record(z.string(), z.custom()), + workers: z.record(z.string(), z.custom()), /** CORS configuration for the router. Uses Hono's CORS middleware options. */ cors: z.custom().optional(), - /** Base path used to build URLs from. This is specifically used when returning the endpoint to connect to for actors. */ + /** Base path used to build URLs from. This is specifically used when returning the endpoint to connect to for workers. */ basePath: z.string().optional(), /** This goes in the URL so it needs to be short. */ @@ -67,13 +67,13 @@ export const AppConfigSchema = z.object({ webSocketInitTimeout: z.number().optional().default(5_000), /** Peer configuration for coordinated topology. */ - actorPeer: ActorPeerConfigSchema.optional().default({}), + workerPeer: WorkerPeerConfigSchema.optional().default({}), /** Inspector configuration. */ inspector: InspectorConfigSchema.optional().default({ enabled: false }), }); export type AppConfig = z.infer; -export type AppConfigInput = Omit< +export type AppConfigInput = Omit< z.input, - "actors" -> & { actors: A }; + "workers" +> & { workers: A }; diff --git a/packages/actor/src/app/inline-client-driver.ts b/packages/core/src/app/inline-client-driver.ts similarity index 65% rename from packages/actor/src/app/inline-client-driver.ts rename to packages/core/src/app/inline-client-driver.ts index c8a2f2fe0..37503d38e 100644 --- a/packages/actor/src/app/inline-client-driver.ts +++ b/packages/core/src/app/inline-client-driver.ts @@ -1,23 +1,23 @@ -import * as errors from "@/actor/errors"; -import * as protoHttpAction from "@/actor/protocol/http/action"; +import * as errors from "@/worker/errors"; +import * as protoHttpAction from "@/worker/protocol/http/action"; import { logger } from "./log"; import type { EventSource } from "eventsource"; -import type * as wsToServer from "@/actor/protocol/message/to-server"; -import { type Encoding, serialize } from "@/actor/protocol/serde"; +import type * as wsToServer from "@/worker/protocol/message/to-server"; +import { type Encoding, serialize } from "@/worker/protocol/serde"; import { HEADER_CONN_PARAMS, HEADER_ENCODING, type ConnectionHandlers, -} from "@/actor/router-endpoints"; +} from "@/worker/router-endpoints"; 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 { ActorQuery } from "@/manager/protocol/query"; -import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; +import { WorkerQuery } from "@/manager/protocol/query"; +import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; import { sendHttpRequest, serializeWithEncoding } from "@/client/utils"; -import { ActionRequest, ActionResponse } from "@/actor/protocol/http/action"; -import { assertUnreachable } from "@/actor/utils"; +import { ActionRequest, ActionResponse } from "@/worker/protocol/http/action"; +import { assertUnreachable } from "@/worker/utils"; /** * Client driver that calls the manager driver inline. @@ -48,20 +48,20 @@ export function createInlineClientDriver( const driver: ClientDriver = { action: async = unknown[], Response = unknown>( req: HonoRequest | undefined, - actorQuery: ActorQuery, + workerQuery: WorkerQuery, encoding: Encoding, params: unknown, actionName: string, ...args: Args ): Promise => { - // Get the actor ID and meta - const { actorId, meta } = await queryActor( + // Get the worker ID and meta + const { workerId, meta } = await queryWorker( req, - actorQuery, + workerQuery, managerDriver, ); - logger().debug("found actor for action", { actorId, meta }); - invariant(actorId, "Missing actor ID"); + logger().debug("found worker for action", { workerId, meta }); + invariant(workerId, "Missing worker ID"); // Invoke the action logger().debug("handling action", { actionName, encoding }); @@ -71,7 +71,7 @@ export function createInlineClientDriver( params, actionName, actionArgs: args, - actorId, + workerId, }); return output as Response; } else if ("custom" in routingHandler) { @@ -79,7 +79,7 @@ export function createInlineClientDriver( ActionRequest, ActionResponse >({ - url: `http://actor/action/${encodeURIComponent(actionName)}`, + url: `http://worker/action/${encodeURIComponent(actionName)}`, method: "POST", headers: { [HEADER_ENCODING]: encoding, @@ -91,7 +91,7 @@ export function createInlineClientDriver( encoding: encoding, customFetch: routingHandler.custom.sendRequest.bind( undefined, - actorId, + workerId, meta, ), }); @@ -102,22 +102,22 @@ export function createInlineClientDriver( } }, - resolveActorId: async ( + resolveWorkerId: async ( req: HonoRequest | undefined, - actorQuery: ActorQuery, + workerQuery: WorkerQuery, _encodingKind: Encoding, ): Promise => { - // Get the actor ID and meta - const { actorId } = await queryActor(req, actorQuery, managerDriver); - logger().debug("resolved actor", { actorId }); - invariant(actorId, "missing actor ID"); + // Get the worker ID and meta + const { workerId } = await queryWorker(req, workerQuery, managerDriver); + logger().debug("resolved worker", { workerId }); + invariant(workerId, "missing worker ID"); - return actorId; + return workerId; }, connectWebSocket: async ( req: HonoRequest | undefined, - actorQuery: ActorQuery, + workerQuery: WorkerQuery, encodingKind: Encoding, ): Promise => { throw "UNIMPLEMENTED"; @@ -125,7 +125,7 @@ export function createInlineClientDriver( connectSse: async ( req: HonoRequest | undefined, - actorQuery: ActorQuery, + workerQuery: WorkerQuery, encodingKind: Encoding, params: unknown, ): Promise => { @@ -134,7 +134,7 @@ export function createInlineClientDriver( sendHttpMessage: async ( req: HonoRequest | undefined, - actorId: string, + workerId: string, encoding: Encoding, connectionId: string, connectionToken: string, @@ -148,34 +148,34 @@ export function createInlineClientDriver( } /** - * Query the manager driver to get or create an actor based on the provided query + * Query the manager driver to get or create a worker based on the provided query */ -export async function queryActor( +export async function queryWorker( req: HonoRequest | undefined, - query: ActorQuery, + query: WorkerQuery, driver: ManagerDriver, -): Promise<{ actorId: string; meta?: unknown }> { - logger().debug("querying actor", { query }); - let actorOutput: { actorId: string; meta?: unknown }; +): Promise<{ workerId: string; meta?: unknown }> { + logger().debug("querying worker", { query }); + let workerOutput: { workerId: string; meta?: unknown }; if ("getForId" in query) { const output = await driver.getForId({ req, - actorId: query.getForId.actorId, + workerId: query.getForId.workerId, }); - if (!output) throw new errors.ActorNotFound(query.getForId.actorId); - actorOutput = output; + if (!output) throw new errors.WorkerNotFound(query.getForId.workerId); + workerOutput = output; } else if ("getForKey" in query) { - const existingActor = await driver.getWithKey({ + const existingWorker = await driver.getWithKey({ req, name: query.getForKey.name, key: query.getForKey.key, }); - if (!existingActor) { - throw new errors.ActorNotFound( + if (!existingWorker) { + throw new errors.WorkerNotFound( `${query.getForKey.name}:${JSON.stringify(query.getForKey.key)}`, ); } - actorOutput = existingActor; + workerOutput = existingWorker; } else if ("getOrCreateForKey" in query) { const getOrCreateOutput = await driver.getOrCreateWithKey({ req, @@ -184,29 +184,29 @@ export async function queryActor( input: query.getOrCreateForKey.input, region: query.getOrCreateForKey.region, }); - actorOutput = { - actorId: getOrCreateOutput.actorId, + workerOutput = { + workerId: getOrCreateOutput.workerId, meta: getOrCreateOutput.meta, }; } else if ("create" in query) { - const createOutput = await driver.createActor({ + const createOutput = await driver.createWorker({ req, name: query.create.name, key: query.create.key, input: query.create.input, region: query.create.region, }); - actorOutput = { - actorId: createOutput.actorId, + workerOutput = { + workerId: createOutput.workerId, meta: createOutput.meta, }; } else { throw new errors.InvalidRequest("Invalid query format"); } - logger().debug("actor query result", { - actorId: actorOutput.actorId, - meta: actorOutput.meta, + logger().debug("worker query result", { + workerId: workerOutput.workerId, + meta: workerOutput.meta, }); - return { actorId: actorOutput.actorId, meta: actorOutput.meta }; + return { workerId: workerOutput.workerId, meta: workerOutput.meta }; } diff --git a/packages/actor/src/app/log.ts b/packages/core/src/app/log.ts similarity index 72% rename from packages/actor/src/app/log.ts rename to packages/core/src/app/log.ts index d56c5aa74..539a71f50 100644 --- a/packages/actor/src/app/log.ts +++ b/packages/core/src/app/log.ts @@ -1,6 +1,6 @@ import { getLogger } from "@/common//log"; -export const LOGGER_NAME = "actor-app"; +export const LOGGER_NAME = "worker-app"; export function logger() { return getLogger(LOGGER_NAME); diff --git a/packages/actor/src/app/mod.ts b/packages/core/src/app/mod.ts similarity index 69% rename from packages/actor/src/app/mod.ts rename to packages/core/src/app/mod.ts index c65f90c2b..2b652bb44 100644 --- a/packages/actor/src/app/mod.ts +++ b/packages/core/src/app/mod.ts @@ -1,11 +1,11 @@ import { - type Actors, + type Workers, type AppConfig, type AppConfigInput, AppConfigSchema, } from "./config"; -export class ActorCoreApp { +export class WorkerCoreApp { #config: AppConfig; public get config(): AppConfig { @@ -17,11 +17,11 @@ export class ActorCoreApp { } } -export function setup( +export function setup( input: AppConfigInput, -): ActorCoreApp { +): WorkerCoreApp { const config = AppConfigSchema.parse(input); - return new ActorCoreApp(config); + return new WorkerCoreApp(config); } export type { AppConfig }; diff --git a/packages/core/src/client/client.ts b/packages/core/src/client/client.ts new file mode 100644 index 000000000..1a09ffbd1 --- /dev/null +++ b/packages/core/src/client/client.ts @@ -0,0 +1,593 @@ +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, + SendHttpMessageOpts, +} from "./worker-conn"; +import { WorkerHandle, WorkerHandleRaw } from "./worker-handle"; +import { WorkerActionFunction } from "./worker-common"; +import { logger } from "./log"; +import type { WorkerCoreApp } from "@/mod"; +import type { AnyWorkerDefinition } from "@/worker/definition"; +import type * as wsToServer from "@/worker/protocol/message/to-server"; +import type { EventSource } from "eventsource"; +import { createHttpClientDriver } from "./http-client-driver"; +import { HonoRequest } from "hono"; + +/** Extract the worker registry from the app definition. */ +export type ExtractWorkersFromApp> = + A extends WorkerCoreApp ? Workers : never; + +/** Extract the app definition from the client. */ +export type ExtractAppFromClient>> = + C extends Client ? A : never; + +/** + * Represents a worker accessor that provides methods to interact with a specific worker. + */ +export interface WorkerAccessor { + /** + * 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. + * + * @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. + */ + get(key?: string | string[], opts?: GetWithIdOptions): WorkerHandle; + + /** + * Gets a stateless handle to a worker by its key, creating it if necessary. + * The worker 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. + */ + getOrCreate( + key?: string | string[], + opts?: GetOrCreateOptions, + ): WorkerHandle; + + /** + * Gets a stateless handle to a worker 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. + */ + getForId(workerId: string, opts?: GetWithIdOptions): WorkerHandle; + + /** + * 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. + * + * @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. + */ + create( + key?: string | string[], + opts?: CreateOptions, + ): Promise>; +} + +/** + * Options for configuring the client. + * @typedef {Object} ClientOptions + */ +export interface ClientOptions { + encoding?: Encoding; + transport?: Transport; +} + +/** + * Options for querying workers. + * @typedef {Object} QueryOptions + * @property {unknown} [parameters] - Parameters to pass to the connection. + */ +export interface QueryOptions { + /** Parameters to pass to the connection. */ + params?: unknown; +} + +/** + * Options for getting a worker by ID. + * @typedef {QueryOptions} GetWithIdOptions + */ +export interface GetWithIdOptions extends QueryOptions {} + +/** + * Options for getting a worker. + * @typedef {QueryOptions} GetOptions + */ +export interface GetOptions extends QueryOptions {} + +/** + * Options for getting or creating a worker. + * @typedef {QueryOptions} GetOrCreateOptions + * @property {string} [createInRegion] - Region to create the worker in if it doesn't exist. + */ +export interface GetOrCreateOptions extends QueryOptions { + /** Region to create the worker in if it doesn't exist. */ + createInRegion?: string; + /** Input data to pass to the worker. */ + createWithInput?: unknown; +} + +/** + * Options for creating a worker. + * @typedef {QueryOptions} CreateOptions + * @property {string} [region] - The region to create the worker in. + */ +export interface CreateOptions extends QueryOptions { + /** The region to create the worker in. */ + region?: string; + /** Input data to pass to the worker. */ + input?: unknown; +} + +/** + * Represents a region to connect to. + * @typedef {Object} Region + * @property {string} id - The region ID. + * @property {string} name - The region name. + * @see {@link https://rivet.gg/docs/edge|Edge Networking} + * @see {@link https://rivet.gg/docs/regions|Available Regions} + */ +export interface Region { + /** + * The region slug. + */ + id: string; + + /** + * The human-friendly region name. + */ + name: string; +} + +export const WORKER_CONNS_SYMBOL = Symbol("workerConns"); +export const CREATE_WORKER_CONN_PROXY = Symbol("createWorkerConnProxy"); +export const TRANSPORT_SYMBOL = Symbol("transport"); + +export interface ClientDriver { + action = unknown[], Response = unknown>( + req: HonoRequest | undefined, + workerQuery: WorkerQuery, + encoding: Encoding, + params: unknown, + name: string, + ...args: Args + ): Promise; + resolveWorkerId( + req: HonoRequest | undefined, + workerQuery: WorkerQuery, + encodingKind: Encoding, + ): Promise; + connectWebSocket( + req: HonoRequest | undefined, + workerQuery: WorkerQuery, + encodingKind: Encoding, + ): Promise; + connectSse( + req: HonoRequest | undefined, + workerQuery: WorkerQuery, + encodingKind: Encoding, + params: unknown, + ): Promise; + sendHttpMessage( + req: HonoRequest | undefined, + workerId: string, + encoding: Encoding, + connectionId: string, + connectionToken: string, + message: wsToServer.ToServer, + ): Promise; +} + +/** + * Client for managing & connecting to workers. + * + * @template A The workers map type that defines the available workers. + * @see {@link https://rivet.gg/docs/manage|Create & Manage Workers} + */ +export class ClientRaw { + #disposed = false; + + [WORKER_CONNS_SYMBOL] = new Set(); + + #driver: ClientDriver; + #encodingKind: Encoding; + [TRANSPORT_SYMBOL]: Transport; + + /** + * Creates an instance of Client. + * + * @param {string} managerEndpoint - The manager endpoint. See {@link https://rivet.gg/docs/setup|Initial Setup} for instructions on getting the manager endpoint. + * @param {ClientOptions} [opts] - Options for configuring the client. + * @see {@link https://rivet.gg/docs/setup|Initial Setup} + */ + public constructor(driver: ClientDriver, opts?: ClientOptions) { + this.#driver = driver; + + this.#encodingKind = opts?.encoding ?? "cbor"; + this[TRANSPORT_SYMBOL] = opts?.transport ?? "websocket"; + } + + /** + * Gets a stateless handle to a worker 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. + */ + getForId( + name: string, + workerId: string, + opts?: GetWithIdOptions, + ): WorkerHandle { + logger().debug("get handle to worker with id", { + name, + workerId, + params: opts?.params, + }); + + const workerQuery = { + getForId: { + workerId, + }, + }; + + const handle = this.#createHandle(opts?.params, workerQuery); + return createWorkerProxy(handle) as WorkerHandle; + } + + /** + * Gets a stateless handle to a worker by its key, but does not create the worker 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. + */ + get( + name: string, + key?: string | string[], + opts?: GetWithIdOptions, + ): WorkerHandle { + // Convert string to array of strings + const keyArray: string[] = typeof key === "string" ? [key] : key || []; + + logger().debug("get handle to worker", { + name, + key: keyArray, + parameters: opts?.params, + }); + + const workerQuery: WorkerQuery = { + getForKey: { + name, + key: keyArray, + }, + }; + + const handle = this.#createHandle(opts?.params, workerQuery); + return createWorkerProxy(handle) as WorkerHandle; + } + + /** + * Gets a stateless handle to a worker 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. + */ + getOrCreate( + name: string, + key?: string | string[], + opts?: GetOrCreateOptions, + ): WorkerHandle { + // Convert string to array of strings + const keyArray: string[] = typeof key === "string" ? [key] : key || []; + + logger().debug("get or create handle to worker", { + name, + key: keyArray, + parameters: opts?.params, + createInRegion: opts?.createInRegion, + }); + + const workerQuery: WorkerQuery = { + getOrCreateForKey: { + name, + key: keyArray, + input: opts?.createWithInput, + region: opts?.createInRegion, + }, + }; + + const handle = this.#createHandle(opts?.params, workerQuery); + return createWorkerProxy(handle) as WorkerHandle; + } + + /** + * 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. + * + * @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. + */ + async create( + name: string, + key?: string | string[], + opts?: CreateOptions, + ): Promise> { + // Convert string to array of strings + const keyArray: string[] = typeof key === "string" ? [key] : key || []; + + const createQuery = { + create: { + ...opts, + // Do these last to override `opts` + name, + key: keyArray, + }, + } satisfies WorkerQuery; + + logger().debug("create worker handle", { + name, + key: keyArray, + parameters: opts?.params, + create: createQuery.create, + }); + + // Create the worker + const workerId = await this.#driver.resolveWorkerId( + undefined, + createQuery, + this.#encodingKind, + ); + logger().debug("created worker with ID", { + name, + key: keyArray, + workerId, + }); + + // Create handle with worker ID + const getForIdQuery = { + getForId: { + workerId, + }, + } satisfies WorkerQuery; + const handle = this.#createHandle(opts?.params, getForIdQuery); + + const proxy = createWorkerProxy(handle) as WorkerHandle; + + return proxy; + } + + #createHandle(params: unknown, workerQuery: WorkerQuery): WorkerHandleRaw { + return new WorkerHandleRaw( + this, + this.#driver, + params, + this.#encodingKind, + workerQuery, + ); + } + + [CREATE_WORKER_CONN_PROXY]( + conn: WorkerConnRaw, + ): WorkerConn { + // Save to connection list + this[WORKER_CONNS_SYMBOL].add(conn); + + // Start connection + conn[CONNECT_SYMBOL](); + + return createWorkerProxy(conn) as WorkerConn; + } + + /** + * Disconnects from all workers. + * + * @returns {Promise} A promise that resolves when all connections are closed. + */ + async dispose(): Promise { + if (this.#disposed) { + logger().warn("client already disconnected"); + return; + } + this.#disposed = true; + + logger().debug("disposing client"); + + const disposePromises = []; + + // Dispose all connections + for (const conn of this[WORKER_CONNS_SYMBOL].values()) { + disposePromises.push(conn.dispose()); + } + + await Promise.all(disposePromises); + } +} + +/** + * Client type with worker accessors. + * This adds property accessors for worker names to the ClientRaw base class. + * + * @template A The worker application type. + */ +export type Client> = ClientRaw & { + [K in keyof ExtractWorkersFromApp]: WorkerAccessor< + ExtractWorkersFromApp[K] + >; +}; + +export function createClientWithDriver>( + driver: ClientDriver, + opts?: ClientOptions, +): Client { + const client = new ClientRaw(driver, opts); + + // Create proxy for accessing workers by name + return new Proxy(client, { + get: (target: ClientRaw, prop: string | symbol, receiver: unknown) => { + // Get the real property if it exists + if (typeof prop === "symbol" || prop in target) { + const value = Reflect.get(target, prop, receiver); + // Preserve method binding + if (typeof value === "function") { + return value.bind(target); + } + return value; + } + + // Handle worker accessor for string properties (worker names) + if (typeof prop === "string") { + // Return worker accessor object with methods + return { + // Handle methods (stateless action) + get: ( + key?: string | string[], + opts?: GetWithIdOptions, + ): WorkerHandle[typeof prop]> => { + return target.get[typeof prop]>( + prop, + key, + opts, + ); + }, + getOrCreate: ( + key?: string | string[], + opts?: GetOptions, + ): WorkerHandle[typeof prop]> => { + return target.getOrCreate[typeof prop]>( + prop, + key, + opts, + ); + }, + getForId: ( + workerId: string, + opts?: GetWithIdOptions, + ): WorkerHandle[typeof prop]> => { + return target.getForId[typeof prop]>( + prop, + workerId, + opts, + ); + }, + create: async ( + key: string | string[], + opts: CreateOptions = {}, + ): Promise[typeof prop]>> => { + return await target.create[typeof prop]>( + prop, + key, + opts, + ); + }, + } as WorkerAccessor[typeof prop]>; + } + + return undefined; + }, + }) as Client; +} + +/** + * Creates a proxy for a worker that enables calling actions without explicitly using `.action`. + **/ +function createWorkerProxy( + handle: WorkerHandleRaw | WorkerConnRaw, +): WorkerHandle | WorkerConn { + // Stores returned action functions for faster calls + const methodCache = new Map(); + return new Proxy(handle, { + get(target: WorkerHandleRaw, prop: string | symbol, receiver: unknown) { + // Handle built-in Symbol properties + if (typeof prop === "symbol") { + return Reflect.get(target, prop, receiver); + } + + // Handle built-in Promise methods and existing properties + if (prop === "constructor" || prop in target) { + const value = Reflect.get(target, prop, receiver); + // Preserve method binding + if (typeof value === "function") { + return value.bind(target); + } + return value; + } + + // Create action function that preserves 'this' context + if (typeof prop === "string") { + // If JS is attempting to calling this as a promise, ignore it + if (prop === "then") return undefined; + + let method = methodCache.get(prop); + if (!method) { + method = (...args: unknown[]) => target.action(prop, ...args); + methodCache.set(prop, method); + } + return method; + } + }, + + // Support for 'in' operator + has(target: WorkerHandleRaw, prop: string | symbol) { + // All string properties are potentially action functions + if (typeof prop === "string") { + return true; + } + // For symbols, defer to the target's own has behavior + return Reflect.has(target, prop); + }, + + // Support instanceof checks + getPrototypeOf(target: WorkerHandleRaw) { + return Reflect.getPrototypeOf(target); + }, + + // Prevent property enumeration of non-existent action methods + ownKeys(target: WorkerHandleRaw) { + return Reflect.ownKeys(target); + }, + + // Support proper property descriptors + getOwnPropertyDescriptor(target: WorkerHandleRaw, prop: string | symbol) { + const targetDescriptor = Reflect.getOwnPropertyDescriptor(target, prop); + if (targetDescriptor) { + return targetDescriptor; + } + if (typeof prop === "string") { + // Make action methods appear non-enumerable + return { + configurable: true, + enumerable: false, + writable: false, + value: (...args: unknown[]) => target.action(prop, ...args), + }; + } + return undefined; + }, + }) as WorkerHandle | WorkerConn; +} diff --git a/packages/actor/src/client/errors.ts b/packages/core/src/client/errors.ts similarity index 55% rename from packages/actor/src/client/errors.ts rename to packages/core/src/client/errors.ts index 79840c2c9..63f3a3218 100644 --- a/packages/actor/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 ActorClientError extends Error {} +export class WorkerClientError extends Error {} -export class InternalError extends ActorClientError {} +export class InternalError extends WorkerClientError {} -export class ManagerError extends ActorClientError { +export class ManagerError extends WorkerClientError { constructor(error: string, opts?: ErrorOptions) { super(`Manager error: ${error}`, opts); } } -export class ConnParamsTooLong extends ActorClientError { +export class ConnParamsTooLong extends WorkerClientError { constructor() { super( `Connection parameters must be less than ${MAX_CONN_PARAMS_SIZE} bytes`, @@ -18,13 +18,13 @@ export class ConnParamsTooLong extends ActorClientError { } } -export class MalformedResponseMessage extends ActorClientError { +export class MalformedResponseMessage extends WorkerClientError { constructor(cause?: unknown) { super(`Malformed response message: ${cause}`, { cause }); } } -export class ActorError extends ActorClientError { +export class WorkerError extends WorkerClientError { constructor( public readonly code: string, message: string, @@ -34,14 +34,14 @@ export class ActorError extends ActorClientError { } } -export class HttpRequestError extends ActorClientError { +export class HttpRequestError extends WorkerClientError { constructor(message: string, opts?: { cause?: unknown }) { super(`HTTP request error: ${message}`, { cause: opts?.cause }); } } -export class ActorConnDisposed extends ActorClientError { +export class WorkerConnDisposed extends WorkerClientError { constructor() { - super("Attempting to interact with a disposed actor connection."); + super("Attempting to interact with a disposed worker connection."); } } diff --git a/packages/actor/src/client/http-client-driver.ts b/packages/core/src/client/http-client-driver.ts similarity index 72% rename from packages/actor/src/client/http-client-driver.ts rename to packages/core/src/client/http-client-driver.ts index ef7a4da47..c4becd631 100644 --- a/packages/actor/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 "@/actor/protocol/serde"; -import type { ActorQuery } from "@/manager/protocol/query"; +import type { Encoding } from "@/worker/protocol/serde"; +import type { WorkerQuery } from "@/manager/protocol/query"; import * as errors from "./errors"; import { logger } from "./log"; -import type * as wsToServer from "@/actor/protocol/message/to-server"; -import type * as protoHttpResolve from "@/actor/protocol/http/resolve"; +import type * as wsToServer from "@/worker/protocol/message/to-server"; +import type * as protoHttpResolve from "@/worker/protocol/http/resolve"; import { assertUnreachable, httpUserAgent } from "@/utils"; import { - HEADER_ACTOR_ID, - HEADER_ACTOR_QUERY, + HEADER_WORKER_ID, + HEADER_WORKER_QUERY, HEADER_CONN_ID, HEADER_CONN_PARAMS, HEADER_CONN_TOKEN, HEADER_ENCODING, -} from "@/actor/router-endpoints"; +} from "@/worker/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 "@/actor/protocol/http/action"; -import type { ActionResponse } from "@/actor/protocol/message/to-client"; +import type { ActionRequest } from "@/worker/protocol/http/action"; +import type { ActionResponse } from "@/worker/protocol/message/to-client"; import { ClientDriver } from "./client"; import { HonoRequest } from "hono"; @@ -47,25 +47,25 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { const driver: ClientDriver = { action: async = unknown[], Response = unknown>( _req: HonoRequest | undefined, - actorQuery: ActorQuery, + workerQuery: WorkerQuery, encoding: Encoding, params: unknown, name: string, ...args: Args ): Promise => { - logger().debug("actor handle action", { + logger().debug("worker handle action", { name, args, - query: actorQuery, + query: workerQuery, }); const responseData = await sendHttpRequest( { - url: `${managerEndpoint}/actors/actions/${encodeURIComponent(name)}`, + url: `${managerEndpoint}/workers/actions/${encodeURIComponent(name)}`, method: "POST", headers: { [HEADER_ENCODING]: encoding, - [HEADER_ACTOR_QUERY]: JSON.stringify(actorQuery), + [HEADER_WORKER_QUERY]: JSON.stringify(workerQuery), ...(params !== undefined ? { [HEADER_CONN_PARAMS]: JSON.stringify(params) } : {}), @@ -78,37 +78,37 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { return responseData.o as Response; }, - resolveActorId: async ( + resolveWorkerId: async ( _req: HonoRequest | undefined, - actorQuery: ActorQuery, + workerQuery: WorkerQuery, encodingKind: Encoding, ): Promise => { - logger().debug("resolving actor ID", { query: actorQuery }); + logger().debug("resolving worker ID", { query: workerQuery }); try { const result = await sendHttpRequest< Record, protoHttpResolve.ResolveResponse >({ - url: `${managerEndpoint}/actors/resolve`, + url: `${managerEndpoint}/workers/resolve`, method: "POST", headers: { [HEADER_ENCODING]: encodingKind, - [HEADER_ACTOR_QUERY]: JSON.stringify(actorQuery), + [HEADER_WORKER_QUERY]: JSON.stringify(workerQuery), }, body: {}, encoding: encodingKind, }); - logger().debug("resolved actor ID", { actorId: result.i }); + logger().debug("resolved worker ID", { workerId: result.i }); return result.i; } catch (error) { - logger().error("failed to resolve actor ID", { error }); - if (error instanceof errors.ActorError) { + logger().error("failed to resolve worker ID", { error }); + if (error instanceof errors.WorkerError) { throw error; } else { throw new errors.InternalError( - `Failed to resolve actor ID: ${String(error)}`, + `Failed to resolve worker ID: ${String(error)}`, ); } } @@ -116,16 +116,16 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { connectWebSocket: async ( _req: HonoRequest | undefined, - actorQuery: ActorQuery, + workerQuery: WorkerQuery, encodingKind: Encoding, ): Promise => { const { WebSocket } = await dynamicImports; - const actorQueryStr = encodeURIComponent(JSON.stringify(actorQuery)); + const workerQueryStr = encodeURIComponent(JSON.stringify(workerQuery)); const endpoint = managerEndpoint .replace(/^http:/, "ws:") .replace(/^https:/, "wss:"); - const url = `${endpoint}/actors/connect/websocket?encoding=${encodingKind}&query=${actorQueryStr}`; + const url = `${endpoint}/workers/connect/websocket?encoding=${encodingKind}&query=${workerQueryStr}`; logger().debug("connecting to websocket", { url }); const ws = new WebSocket(url); @@ -145,13 +145,13 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { connectSse: async ( _req: HonoRequest | undefined, - actorQuery: ActorQuery, + workerQuery: WorkerQuery, encodingKind: Encoding, params: unknown, ): Promise => { const { EventSource } = await dynamicImports; - const url = `${managerEndpoint}/actors/connect/sse`; + const url = `${managerEndpoint}/workers/connect/sse`; logger().debug("connecting to sse", { url }); const eventSource = new EventSource(url, { @@ -162,7 +162,7 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { ...init?.headers, "User-Agent": httpUserAgent(), [HEADER_ENCODING]: encodingKind, - [HEADER_ACTOR_QUERY]: JSON.stringify(actorQuery), + [HEADER_WORKER_QUERY]: JSON.stringify(workerQuery), ...(params !== undefined ? { [HEADER_CONN_PARAMS]: JSON.stringify(params) } : {}), @@ -176,7 +176,7 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { sendHttpMessage: async ( _req: HonoRequest | undefined, - actorId: string, + workerId: string, encoding: Encoding, connectionId: string, connectionToken: string, @@ -185,12 +185,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}/actors/message`, { + const res = await fetch(`${managerEndpoint}/workers/message`, { method: "POST", headers: { "User-Agent": httpUserAgent(), [HEADER_ENCODING]: encoding, - [HEADER_ACTOR_ID]: actorId, + [HEADER_WORKER_ID]: workerId, [HEADER_CONN_ID]: connectionId, [HEADER_CONN_TOKEN]: connectionToken, }, diff --git a/packages/actor/src/client/log.ts b/packages/core/src/client/log.ts similarity index 70% rename from packages/actor/src/client/log.ts rename to packages/core/src/client/log.ts index 09823be08..aa3c582df 100644 --- a/packages/actor/src/client/log.ts +++ b/packages/core/src/client/log.ts @@ -1,6 +1,6 @@ import { getLogger } from "@/common//log"; -export const LOGGER_NAME = "actor-client"; +export const LOGGER_NAME = "worker-client"; export function logger() { return getLogger(LOGGER_NAME); diff --git a/packages/actor/src/client/mod.ts b/packages/core/src/client/mod.ts similarity index 51% rename from packages/actor/src/client/mod.ts rename to packages/core/src/client/mod.ts index 584c2c9bb..979b7c3b4 100644 --- a/packages/actor/src/client/mod.ts +++ b/packages/core/src/client/mod.ts @@ -1,51 +1,51 @@ -import type { ActorCoreApp } from "@/app/mod"; +import type { WorkerCoreApp } from "@/app/mod"; import { type Client, type ClientOptions, createClientWithDriver } from "./client"; import { createHttpClientDriver } from "./http-client-driver"; export type { Client, - ActorAccessor, + WorkerAccessor, ClientOptions, CreateOptions, GetOptions, GetWithIdOptions, QueryOptions, Region, - ExtractActorsFromApp, + ExtractWorkersFromApp, ExtractAppFromClient, ClientRaw, } from "./client"; -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 { 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 { CreateRequest } from "@/manager/protocol/query"; export { - ActorClientError, + WorkerClientError, InternalError, ManagerError, ConnParamsTooLong, MalformedResponseMessage, - ActorError, + WorkerError, } from "@/client/errors"; export { - AnyActorDefinition, - ActorDefinition, -} from "@/actor/definition"; + AnyWorkerDefinition, + WorkerDefinition, +} from "@/worker/definition"; /** - * Creates a client with the actor accessor proxy. + * Creates a client with the worker accessor proxy. * - * @template A The actor application type. + * @template A The worker 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.myActor.connect()` syntax. + * @returns {Client} - A proxied client that supports the `client.myWorker.connect()` syntax. */ -export function createClient>( +export function createClient>( endpoint: string, opts?: ClientOptions, ): Client { diff --git a/packages/actor/src/client/test.ts b/packages/core/src/client/test.ts similarity index 100% rename from packages/actor/src/client/test.ts rename to packages/core/src/client/test.ts diff --git a/packages/actor/src/client/utils.ts b/packages/core/src/client/utils.ts similarity index 95% rename from packages/actor/src/client/utils.ts rename to packages/core/src/client/utils.ts index aeb3a4c57..b1ba3a266 100644 --- a/packages/actor/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 { ActorError, HttpRequestError } from "./errors"; -import { ResponseError } from "@/actor/protocol/http/error"; +import { WorkerError, HttpRequestError } from "./errors"; +import { ResponseError } from "@/worker/protocol/http/error"; import { logger } from "./log"; export type WebSocketMessage = string | Blob | ArrayBuffer | Uint8Array; @@ -113,7 +113,7 @@ export async function sendHttpRequest< } // Throw structured error - throw new ActorError(responseData.c, responseData.m, responseData.md); + throw new WorkerError(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-common.ts b/packages/core/src/client/worker-common.ts new file mode 100644 index 000000000..b2ef87dc0 --- /dev/null +++ b/packages/core/src/client/worker-common.ts @@ -0,0 +1,37 @@ +import type { AnyWorkerDefinition, WorkerDefinition } from "@/worker/definition"; +import type * as protoHttpResolve from "@/worker/protocol/http/resolve"; +import type { Encoding } from "@/worker/protocol/serde"; +import type { WorkerQuery } from "@/manager/protocol/query"; +import { logger } from "./log"; +import * as errors from "./errors"; +import { sendHttpRequest } from "./utils"; +import { HEADER_WORKER_QUERY, HEADER_ENCODING } from "@/worker/router-endpoints"; + +/** + * Action function returned by Worker connections and handles. + * + * @typedef {Function} WorkerActionFunction + * @template Args + * @template Response + * @param {...Args} args - Arguments for the action function. + * @returns {Promise} + */ +export type WorkerActionFunction< + Args extends Array = unknown[], + Response = unknown, +> = ( + ...args: Args extends [unknown, ...infer Rest] ? Rest : Args +) => Promise; + +/** + * Maps action methods from worker definition to typed function signatures. + */ +export type WorkerDefinitionActions = + AD extends WorkerDefinition + ? { + [K in keyof R]: R[K] extends (...args: infer Args) => infer Return + ? WorkerActionFunction + : never; + } + : never; + diff --git a/packages/actor/src/client/actor-conn.ts b/packages/core/src/client/worker-conn.ts similarity index 88% rename from packages/actor/src/client/actor-conn.ts rename to packages/core/src/client/worker-conn.ts index 5bf304c76..c9fbda5c2 100644 --- a/packages/actor/src/client/actor-conn.ts +++ b/packages/core/src/client/worker-conn.ts @@ -1,18 +1,18 @@ -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 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 { 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 { ActorQuery } from "@/manager/protocol/query"; +import type { WorkerQuery } from "@/manager/protocol/query"; import * as cbor from "cbor-x"; import pRetry from "p-retry"; import { - ACTOR_CONNS_SYMBOL, + WORKER_CONNS_SYMBOL, ClientDriver, type ClientRaw, TRANSPORT_SYMBOL, @@ -21,15 +21,15 @@ import * as errors from "./errors"; import { logger } from "./log"; import { type WebSocketMessage as ConnMessage, messageLength, serializeWithEncoding } from "./utils"; import { - HEADER_ACTOR_ID, - HEADER_ACTOR_QUERY, + HEADER_WORKER_ID, + HEADER_WORKER_QUERY, HEADER_CONN_ID, HEADER_CONN_TOKEN, HEADER_ENCODING, HEADER_CONN_PARAMS, -} from "@/actor/router-endpoints"; +} from "@/worker/router-endpoints"; import type { EventSource } from "eventsource"; -import { ActorDefinitionActions } from "./actor-common"; +import { WorkerDefinitionActions } from "./worker-common"; interface ActionInFlight { name: string; @@ -52,9 +52,9 @@ export type EventUnsubscribe = () => void; /** * A function that handles connection errors. * - * @typedef {Function} ActorErrorCallback + * @typedef {Function} WorkerErrorCallback */ -export type ActorErrorCallback = (error: errors.ActorError) => void; +export type WorkerErrorCallback = (error: errors.WorkerError) => void; export interface SendHttpMessageOpts { ephemeral: boolean; @@ -65,11 +65,11 @@ export type ConnTransport = { websocket: WebSocket } | { sse: EventSource }; export const CONNECT_SYMBOL = Symbol("connect"); /** - * Provides underlying functions for {@link ActorConn}. See {@link ActorConn} for using type-safe remote procedure calls. + * Provides underlying functions for {@link WorkerConn}. See {@link WorkerConn} for using type-safe remote procedure calls. * - * @see {@link ActorConn} + * @see {@link WorkerConn} */ -export class ActorConnRaw { +export class WorkerConnRaw { #disposed = false; /* Will be aborted on dispose. */ @@ -79,7 +79,7 @@ export class ActorConnRaw { #connecting = false; // These will only be set on SSE driver - #actorId?: string; + #workerId?: string; #connectionId?: string; #connectionToken?: string; @@ -91,7 +91,7 @@ export class ActorConnRaw { // biome-ignore lint/suspicious/noExplicitAny: Unknown subscription type #eventSubscriptions = new Map>>(); - #errorHandlers = new Set(); + #errorHandlers = new Set(); #actionIdCounter = 0; @@ -109,14 +109,14 @@ export class ActorConnRaw { #driver: ClientDriver; #params: unknown; #encodingKind: Encoding; - #actorQuery: ActorQuery; + #workerQuery: WorkerQuery; // TODO: ws message queue /** * Do not call this directly. * - * Creates an instance of ActorConnRaw. + * Creates an instance of WorkerConnRaw. * * @protected */ @@ -125,21 +125,21 @@ export class ActorConnRaw { private driver: ClientDriver, private params: unknown, private encodingKind: Encoding, - private actorQuery: ActorQuery, + private workerQuery: WorkerQuery, ) { this.#client = client; this.#driver = driver; this.#params = params; this.#encodingKind = encodingKind; - this.#actorQuery = actorQuery; + this.#workerQuery = workerQuery; this.#keepNodeAliveInterval = setInterval(() => 60_000); } /** - * Call a raw action connection. See {@link ActorConn} for type-safe action calls. + * Call a raw action connection. See {@link WorkerConn} for type-safe action calls. * - * @see {@link ActorConn} + * @see {@link WorkerConn} * @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. @@ -252,7 +252,7 @@ enc async #connectWebSocket() { const ws = await this.#driver.connectWebSocket( undefined, - this.#actorQuery, + this.#workerQuery, this.#encodingKind, ); this.#transport = { websocket: ws }; @@ -283,7 +283,7 @@ enc async #connectSse() { const eventSource = await this.#driver.connectSse( undefined, - this.#actorQuery, + this.#workerQuery, this.#encodingKind, this.#params, ); @@ -349,11 +349,11 @@ enc if ("i" in response.b) { // This is only called for SSE - this.#actorId = response.b.i.ai; + this.#workerId = response.b.i.ai; this.#connectionId = response.b.i.ci; this.#connectionToken = response.b.i.ct; logger().trace("received init message", { - actorId: this.#actorId, + workerId: this.#workerId, connectionId: this.#connectionId, }); this.#handleOnOpen(); @@ -372,7 +372,7 @@ enc metadata, }); - inFlight.reject(new errors.ActorError(code, message, metadata)); + inFlight.reject(new errors.WorkerError(code, message, metadata)); } else { logger().warn("connection error", { code, @@ -381,21 +381,21 @@ enc }); // Create a connection error - const actorError = new errors.ActorError(code, message, metadata); + const workerError = new errors.WorkerError(code, message, metadata); // If we have an onOpenPromise, reject it with the error if (this.#onOpenPromise) { - this.#onOpenPromise.reject(actorError); + this.#onOpenPromise.reject(workerError); } // Reject any in-flight requests for (const [id, inFlight] of this.#actionsInFlight.entries()) { - inFlight.reject(actorError); + inFlight.reject(workerError); this.#actionsInFlight.delete(id); } // Dispatch to error handler if registered - this.#dispatchActorError(actorError); + this.#dispatchWorkerError(workerError); } } else if ("ar" in response.b) { // Action response OK @@ -454,7 +454,7 @@ enc // Automatically reconnect. Skip if already attempting to connect. if (!this.#disposed && !this.#connecting) { - // TODO: Fetch actor to check if it's destroyed + // TODO: Fetch worker to check if it's destroyed // TODO: Add backoff for reconnect // TODO: Add a way of preserving connection ID for connection state @@ -502,7 +502,7 @@ enc } } - #dispatchActorError(error: errors.ActorError) { + #dispatchWorkerError(error: errors.WorkerError) { // Call all registered error handlers for (const handler of [...this.#errorHandlers]) { try { @@ -581,10 +581,10 @@ enc /** * Subscribes to connection errors. * - * @param {ActorErrorCallback} callback - The callback function to execute when a connection error occurs. + * @param {WorkerErrorCallback} callback - The callback function to execute when a connection error occurs. * @returns {() => void} - A function to unsubscribe from the error handler. */ - onError(callback: ActorErrorCallback): () => void { + onError(callback: WorkerErrorCallback): () => void { this.#errorHandlers.add(callback); // Return unsubscribe function @@ -595,7 +595,7 @@ enc #sendMessage(message: wsToServer.ToServer, opts?: SendHttpMessageOpts) { if (this.#disposed) { - throw new errors.ActorConnDisposed(); + throw new errors.WorkerConnDisposed(); } let queueMessage = false; @@ -647,12 +647,12 @@ enc opts?: SendHttpMessageOpts, ) { try { - if (!this.#actorId || !this.#connectionId || !this.#connectionToken) + if (!this.#workerId || !this.#connectionId || !this.#connectionToken) throw new errors.InternalError("Missing connection ID or token."); const res = await this.#driver.sendHttpMessage( undefined, - this.#actorId, + this.#workerId, this.#encodingKind, this.#connectionId, this.#connectionToken, @@ -729,7 +729,7 @@ enc } /** - * Disconnects from the actor. + * Disconnects from the worker. * * @returns {Promise} A promise that resolves when the socket is gracefully closed. */ @@ -742,7 +742,7 @@ enc } this.#disposed = true; - logger().debug("disposing actor"); + logger().debug("disposing worker"); // Clear interval so NodeJS process can exit clearInterval(this.#keepNodeAliveInterval); @@ -751,7 +751,7 @@ enc this.#abortController.abort(); // Remove from registry - this.#client[ACTOR_CONNS_SYMBOL].delete(this); + this.#client[WORKER_CONNS_SYMBOL].delete(this); // Disconnect transport cleanly if (!this.#transport) { @@ -787,19 +787,19 @@ enc } /** - * Connection to an actor. Allows calling actor's remote procedure calls with inferred types. See {@link ActorConnRaw} for underlying methods. + * Connection to a worker. Allows calling worker's remote procedure calls with inferred types. See {@link WorkerConnRaw} for underlying methods. * * @example * ``` * const room = client.connect(...etc...); - * // This calls the action named `sendMessage` on the `ChatRoom` actor. + * // This calls the action named `sendMessage` on the `ChatRoom` worker. * await room.sendMessage('Hello, world!'); * ``` * * Private methods (e.g. those starting with `_`) are automatically excluded. * - * @template AD The actor class that this connection is for. - * @see {@link ActorConnRaw} + * @template AD The worker class that this connection is for. + * @see {@link WorkerConnRaw} */ -export type ActorConn = ActorConnRaw & - ActorDefinitionActions; +export type WorkerConn = WorkerConnRaw & + WorkerDefinitionActions; diff --git a/packages/core/src/client/worker-handle.ts b/packages/core/src/client/worker-handle.ts new file mode 100644 index 000000000..cf381b0fe --- /dev/null +++ b/packages/core/src/client/worker-handle.ts @@ -0,0 +1,151 @@ +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. + * @param {string} name - The name of the action function to call. + * @param {...Args} args - The arguments to pass to the action function. + * @returns {Promise} - A promise that resolves to the response of the action function. + */ + async action = unknown[], Response = unknown>( + name: string, + ...args: Args + ): Promise { + return await this.#driver.action( + undefined, + this.#workerQuery, + this.#encodingKind, + this.#params, + name, + ...args, + ); + } + + /** + * 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(): Promise { + if ( + "getForKey" in this.#workerQuery || + "getOrCreateForKey" in this.#workerQuery + ) { + // TODO: + const workerId = await this.#driver.resolveWorkerId( + undefined, + this.#workerQuery, + this.#encodingKind, + ); + 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/actor/src/common/eventsource.ts b/packages/core/src/common/eventsource.ts similarity index 100% rename from packages/actor/src/common/eventsource.ts rename to packages/core/src/common/eventsource.ts diff --git a/packages/actor/src/common/log-levels.ts b/packages/core/src/common/log-levels.ts similarity index 100% rename from packages/actor/src/common/log-levels.ts rename to packages/core/src/common/log-levels.ts diff --git a/packages/actor/src/common/log.ts b/packages/core/src/common/log.ts similarity index 100% rename from packages/actor/src/common/log.ts rename to packages/core/src/common/log.ts diff --git a/packages/actor/src/common/logfmt.ts b/packages/core/src/common/logfmt.ts similarity index 100% rename from packages/actor/src/common/logfmt.ts rename to packages/core/src/common/logfmt.ts diff --git a/packages/actor/src/common/network.ts b/packages/core/src/common/network.ts similarity index 100% rename from packages/actor/src/common/network.ts rename to packages/core/src/common/network.ts diff --git a/packages/actor/src/common/router.ts b/packages/core/src/common/router.ts similarity index 84% rename from packages/actor/src/common/router.ts rename to packages/core/src/common/router.ts index 7de56b18f..45a5cab96 100644 --- a/packages/actor/src/common/router.ts +++ b/packages/core/src/common/router.ts @@ -1,9 +1,9 @@ import type { Context as HonoContext, Next } from "hono"; import { getLogger, Logger } from "./log"; import { deconstructError } from "./utils"; -import { getRequestEncoding } from "@/actor/router-endpoints"; -import { serialize } from "@/actor/protocol/serde"; -import { ResponseError } from "@/actor/protocol/http/error"; +import { getRequestEncoding } from "@/worker/router-endpoints"; +import { serialize } from "@/worker/protocol/serde"; +import { ResponseError } from "@/worker/protocol/http/error"; export function logger() { return getLogger("router"); @@ -31,7 +31,7 @@ export function loggerMiddleware(logger: Logger) { } export function handleRouteNotFound(c: HonoContext) { - return c.text("Not Found (ActorCore)", 404); + return c.text("Not Found (WorkerCore)", 404); } export function handleRouteError(error: unknown, c: HonoContext) { diff --git a/packages/actor/src/common/utils.ts b/packages/core/src/common/utils.ts similarity index 90% rename from packages/actor/src/common/utils.ts rename to packages/core/src/common/utils.ts index 35bd8a202..0ee637805 100644 --- a/packages/actor/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 "@/actor/errors"; +import * as errors from "@/worker/errors"; import type { Logger } from "./log"; // Maximum size of a key component in bytes @@ -8,9 +8,9 @@ import type { Logger } from "./log"; // Cloudflare's maximum key size is 512 bytes, so we need to be significantly smaller export const MAX_KEY_SIZE = 128; -export const ActorKeySchema = z.array(z.string().max(MAX_KEY_SIZE)); +export const WorkerKeySchema = z.array(z.string().max(MAX_KEY_SIZE)); -export type ActorKey = z.infer; +export type WorkerKey = z.infer; export interface RivetEnvironment { project?: string; @@ -135,7 +135,7 @@ export function deconstructError( let code: string; let message: string; let metadata: unknown = undefined; - if (errors.ActorError.isActorError(error) && error.public) { + if (errors.WorkerError.isWorkerError(error) && error.public) { statusCode = 400; code = error.code; message = String(error); @@ -151,7 +151,7 @@ export function deconstructError( code = errors.INTERNAL_ERROR_CODE; message = errors.INTERNAL_ERROR_DESCRIPTION; metadata = { - //url: `https://hub.rivet.gg/projects/${actorMetadata.project.slug}/environments/${actorMetadata.environment.slug}/actors?actorId=${actorMetadata.actor.id}`, + //url: `https://hub.rivet.gg/projects/${workerMetadata.project.slug}/environments/${workerMetadata.environment.slug}/workers?workerId=${workerMetadata.worker.id}`, } satisfies errors.InternalErrorMetadata; logger.warn("internal error", { @@ -166,7 +166,7 @@ export function deconstructError( export function stringifyError(error: unknown): string { if (error instanceof Error) { - if (process.env._ACTOR_CORE_ERROR_STACK === "1") { + if (process.env._WORKER_CORE_ERROR_STACK === "1") { return `${error.name}: ${error.message}${error.stack ? `\n${error.stack}` : ""}`; } else { return `${error.name}: ${error.message}`; diff --git a/packages/actor/src/common/websocket.ts b/packages/core/src/common/websocket.ts similarity index 100% rename from packages/actor/src/common/websocket.ts rename to packages/core/src/common/websocket.ts diff --git a/packages/actor/src/driver-helpers/config.ts b/packages/core/src/driver-helpers/config.ts similarity index 80% rename from packages/actor/src/driver-helpers/config.ts rename to packages/core/src/driver-helpers/config.ts index b58fa1f3b..46aca7ee5 100644 --- a/packages/actor/src/driver-helpers/config.ts +++ b/packages/core/src/driver-helpers/config.ts @@ -1,6 +1,6 @@ //! These configs configs hold anything specific to the driver. //! -//! This should only include parameters that affect the low-level infrastructure and does not affect behavior of actors. Configuring parameters in this block should not tweak how actors behave at all. +//! This should only include parameters that affect the low-level infrastructure and does not affect behavior of workers. Configuring parameters in this block should not tweak how workers behave at all. //! //! For example, Rivet doesn't expose this functionality to the user at all and is completely configured automatically. @@ -12,7 +12,7 @@ import type { } from "hono"; import type { CoordinateDriver } from "@/topologies/coordinate/driver"; import type { ManagerDriver } from "@/manager/driver"; -import type { ActorDriver } from "@/actor/driver"; +import type { WorkerDriver } from "@/worker/driver"; export const TopologySchema = z.enum(["standalone", "partition", "coordinate"]); export type Topology = z.infer; @@ -21,13 +21,13 @@ export type GetUpgradeWebSocket = ( app: Hono, ) => (createEvents: (c: HonoContext) => any) => HonoHandler; -/** Base config used for the actor config across all platforms. */ +/** Base config used for the worker config across all platforms. */ export const DriverConfigSchema = z.object({ topology: TopologySchema.optional(), // Default value depends on the platform selected drivers: z .object({ manager: z.custom().optional(), - actor: z.custom().optional(), + worker: z.custom().optional(), coordinate: z.custom().optional(), }) .optional() diff --git a/packages/actor/src/driver-helpers/mod.ts b/packages/core/src/driver-helpers/mod.ts similarity index 62% rename from packages/actor/src/driver-helpers/mod.ts rename to packages/core/src/driver-helpers/mod.ts index 18285faee..f49f60893 100644 --- a/packages/actor/src/driver-helpers/mod.ts +++ b/packages/core/src/driver-helpers/mod.ts @@ -1,19 +1,19 @@ export { type DriverConfig, DriverConfigSchema } from "./config"; -export type { ActorInstance, AnyActorInstance } from "@/actor/instance"; +export type { WorkerInstance, AnyWorkerInstance } from "@/worker/instance"; export { AttemptAcquireLease, ExtendLeaseOutput, - GetActorLeaderOutput, + GetWorkerLeaderOutput, NodeMessageCallback, CoordinateDriver, - StartActorAndAcquireLeaseOutput, + StartWorkerAndAcquireLeaseOutput, } from "@/topologies/coordinate/driver"; -export { ActorDriver } from "@/actor/driver"; +export { WorkerDriver } from "@/worker/driver"; export { ManagerDriver, CreateInput, GetForIdInput, GetWithKeyInput, GetOrCreateWithKeyInput, - ActorOutput, + WorkerOutput, } from "@/manager/driver"; diff --git a/packages/actor/src/driver-test-suite/log.ts b/packages/core/src/driver-test-suite/log.ts similarity index 100% rename from packages/actor/src/driver-test-suite/log.ts rename to packages/core/src/driver-test-suite/log.ts diff --git a/packages/actor/src/driver-test-suite/mod.ts b/packages/core/src/driver-test-suite/mod.ts similarity index 79% rename from packages/actor/src/driver-test-suite/mod.ts rename to packages/core/src/driver-test-suite/mod.ts index dc78c5fa4..d953fbfa5 100644 --- a/packages/actor/src/driver-test-suite/mod.ts +++ b/packages/core/src/driver-test-suite/mod.ts @@ -1,15 +1,15 @@ import { serve as honoServe } from "@hono/node-server"; import { - ActorDriver, + WorkerDriver, CoordinateDriver, DriverConfig, ManagerDriver, } from "@/driver-helpers/mod"; -import { runActorDriverTests } from "./tests/actor-driver"; +import { runWorkerDriverTests } from "./tests/worker-driver"; import { runManagerDriverTests } from "./tests/manager-driver"; import { describe } from "vitest"; import { - type ActorCoreApp, + type WorkerCoreApp, CoordinateTopology, StandaloneTopology, } from "@/mod"; @@ -18,13 +18,13 @@ import invariant from "invariant"; import { bundleRequire } from "bundle-require"; import { getPort } from "@/test/mod"; import { Client, Transport } from "@/client/mod"; -import { runActorConnTests } from "./tests/actor-conn"; -import { runActorHandleTests } from "./tests/actor-handle"; +import { runWorkerConnTests } from "./tests/worker-conn"; +import { runWorkerHandleTests } from "./tests/worker-handle"; import { runActionFeaturesTests } from "./tests/action-features"; -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 { 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 { ClientDriver } from "@/client/client"; export interface DriverTestConfig { @@ -71,29 +71,29 @@ export function runDriverTests( }; describe(`client type (${clientType})`, () => { - runActorDriverTests(driverTestConfig); + runWorkerDriverTests(driverTestConfig); runManagerDriverTests(driverTestConfig); for (const transport of ["websocket", "sse"] as Transport[]) { describe(`transport (${transport})`, () => { - runActorConnTests({ + runWorkerConnTests({ ...driverTestConfig, transport, }); - runActorConnStateTests({ ...driverTestConfig, transport }); + runWorkerConnStateTests({ ...driverTestConfig, transport }); }); } - runActorHandleTests(driverTestConfig); + runWorkerHandleTests(driverTestConfig); runActionFeaturesTests(driverTestConfig); - runActorVarsTests(driverTestConfig); + runWorkerVarsTests(driverTestConfig); - runActorMetadataTests(driverTestConfig); + runWorkerMetadataTests(driverTestConfig); - runActorErrorHandlingTests(driverTestConfig); + runWorkerErrorHandlingTests(driverTestConfig); }); } } @@ -105,8 +105,8 @@ export function runDriverTests( */ export async function createTestRuntime( appPath: string, - driverFactory: (app: ActorCoreApp) => Promise<{ - actorDriver: ActorDriver; + driverFworkery: (app: WorkerCoreApp) => Promise<{ + workerDriver: WorkerDriver; managerDriver: ManagerDriver; coordinateDriver?: CoordinateDriver; cleanup?: () => Promise; @@ -120,17 +120,17 @@ export async function createTestRuntime( // Build drivers const { - actorDriver, + workerDriver, managerDriver, coordinateDriver, cleanup: driverCleanup, - } = await driverFactory(app); + } = await driverFworkery(app); // Build driver config let injectWebSocket: NodeWebSocket["injectWebSocket"] | undefined; const config: DriverConfig = { drivers: { - actor: actorDriver, + worker: workerDriver, manager: managerDriver, coordinate: coordinateDriver, }, diff --git a/packages/actor/src/driver-test-suite/test-apps.ts b/packages/core/src/driver-test-suite/test-apps.ts similarity index 100% rename from packages/actor/src/driver-test-suite/test-apps.ts rename to packages/core/src/driver-test-suite/test-apps.ts diff --git a/packages/actor/src/driver-test-suite/tests/action-features.ts b/packages/core/src/driver-test-suite/tests/action-features.ts similarity index 84% rename from packages/actor/src/driver-test-suite/tests/action-features.ts rename to packages/core/src/driver-test-suite/tests/action-features.ts index cc559aa88..f8461a9ec 100644 --- a/packages/actor/src/driver-test-suite/tests/action-features.ts +++ b/packages/core/src/driver-test-suite/tests/action-features.ts @@ -7,7 +7,7 @@ import { type ActionTimeoutApp, type ActionTypesApp, } from "../test-apps"; -import { ActorError } from "@/client/errors"; +import { WorkerError } from "@/client/errors"; export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { describe("Action Features", () => { @@ -23,14 +23,14 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { ); // The quick action should complete successfully - const quickResult = await client.shortTimeoutActor + const quickResult = await client.shortTimeoutWorker .getOrCreate() .quickAction(); expect(quickResult).toBe("quick response"); // The slow action should throw a timeout error await expect( - client.shortTimeoutActor.getOrCreate().slowAction(), + client.shortTimeoutWorker.getOrCreate().slowAction(), ).rejects.toThrow("Action timed out"); }); @@ -42,7 +42,7 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { ); // This action should complete within the default timeout - const result = await client.defaultTimeoutActor + const result = await client.defaultTimeoutWorker .getOrCreate() .normalAction(); expect(result).toBe("normal response"); @@ -56,24 +56,24 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { ); // Synchronous action should not be affected by timeout - const result = await client.syncActor.getOrCreate().syncAction(); + const result = await client.syncWorker.getOrCreate().syncAction(); expect(result).toBe("sync response"); }); - test("should allow configuring different timeouts for different actors", async (c) => { + test("should allow configuring different timeouts for different workers", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, ACTION_TIMEOUT_APP_PATH, ); - // The short timeout actor should fail + // The short timeout worker should fail await expect( - client.shortTimeoutActor.getOrCreate().slowAction(), + client.shortTimeoutWorker.getOrCreate().slowAction(), ).rejects.toThrow("Action timed out"); - // The longer timeout actor should succeed - const result = await client.longTimeoutActor + // The longer timeout worker should succeed + const result = await client.longTimeoutWorker .getOrCreate() .delayedAction(); expect(result).toBe("delayed response"); @@ -88,7 +88,7 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { ACTION_TYPES_APP_PATH, ); - const instance = client.syncActor.getOrCreate(); + const instance = client.syncWorker.getOrCreate(); // Test increment action let result = await instance.increment(5); @@ -115,7 +115,7 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { ACTION_TYPES_APP_PATH, ); - const instance = client.asyncActor.getOrCreate(); + const instance = client.asyncWorker.getOrCreate(); // Test delayed increment const result = await instance.delayedIncrement(5); @@ -135,8 +135,8 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { await instance.asyncWithError(true); expect.fail("did not error"); } catch (error) { - expect(error).toBeInstanceOf(ActorError); - expect((error as ActorError).message).toBe("Intentional error"); + expect(error).toBeInstanceOf(WorkerError); + expect((error as WorkerError).message).toBe("Intentional error"); } }); @@ -147,7 +147,7 @@ export function runActionFeaturesTests(driverTestConfig: DriverTestConfig) { ACTION_TYPES_APP_PATH, ); - const instance = client.promiseActor.getOrCreate(); + const instance = client.promiseWorker.getOrCreate(); // Test resolved promise const resolvedValue = await instance.resolvedPromise(); diff --git a/packages/actor/src/driver-test-suite/tests/manager-driver.ts b/packages/core/src/driver-test-suite/tests/manager-driver.ts similarity index 78% rename from packages/actor/src/driver-test-suite/tests/manager-driver.ts rename to packages/core/src/driver-test-suite/tests/manager-driver.ts index fa90d1f06..cac701ee0 100644 --- a/packages/actor/src/driver-test-suite/tests/manager-driver.ts +++ b/packages/core/src/driver-test-suite/tests/manager-driver.ts @@ -1,6 +1,6 @@ import { describe, test, expect, vi } from "vitest"; import { setupDriverTest } from "../utils"; -import { ActorError } from "@/client/mod"; +import { WorkerError } from "@/client/mod"; import { COUNTER_APP_PATH, ACTION_INPUTS_APP_PATH, @@ -12,23 +12,23 @@ import { DriverTestConfig } from "../mod"; export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { describe("Manager Driver Tests", () => { describe("Client Connection Methods", () => { - test("connect() - finds or creates an actor", async (c) => { + test("connect() - finds or creates a worker", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, COUNTER_APP_PATH, ); - // Basic connect() with no parameters creates a default actor + // Basic connect() with no parameters creates a default worker const counterA = client.counter.getOrCreate(); await counterA.increment(5); - // Get the same actor again to verify state persisted + // Get the same worker 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 actor with specific parameters + // Connect with key creates a new worker with specific parameters const counterB = client.counter.getOrCreate(["counter-b", "testing"]); await counterB.increment(10); @@ -36,74 +36,74 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { expect(countB).toBe(10); }); - test("throws ActorAlreadyExists when creating duplicate actors", async (c) => { + test("throws WorkerAlreadyExists when creating duplicate workers", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, COUNTER_APP_PATH, ); - // Create a unique actor with specific key - const uniqueKey = ["duplicate-actor-test", crypto.randomUUID()]; + // Create a unique worker with specific key + const uniqueKey = ["duplicate-worker-test", crypto.randomUUID()]; const counter = client.counter.getOrCreate(uniqueKey); await counter.increment(5); - // Expect duplicate actor + // Expect duplicate worker try { await client.counter.create(uniqueKey); expect.fail("did not error on duplicate create"); } catch (err) { - expect(err).toBeInstanceOf(ActorError); - expect((err as ActorError).code).toBe("actor_already_exists"); + expect(err).toBeInstanceOf(WorkerError); + expect((err as WorkerError).code).toBe("worker_already_exists"); } - // Verify the original actor still works and has its state + // Verify the original worker still works and has its state const count = await counter.increment(0); expect(count).toBe(5); }); }); describe("Connection Options", () => { - test("get without create prevents actor creation", async (c) => { + test("get without create prevents worker creation", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, COUNTER_APP_PATH, ); - // Try to get a nonexistent actor with no create + // Try to get a nonexistent worker with no create const nonexistentId = `nonexistent-${crypto.randomUUID()}`; - // Should fail when actor doesn't exist + // Should fail when worker doesn't exist try { await client.counter.get([nonexistentId]).resolve(); expect.fail("did not error for get"); } catch (err) { - expect(err).toBeInstanceOf(ActorError); - expect((err as ActorError).code).toBe("actor_not_found"); + expect(err).toBeInstanceOf(WorkerError); + expect((err as WorkerError).code).toBe("worker_not_found"); } - // Create the actor + // Create the worker const createdCounter = client.counter.getOrCreate(nonexistentId); await createdCounter.increment(3); - // Now no create should work since the actor exists + // Now no create should work since the worker exists const retrievedCounter = client.counter.get(nonexistentId); const count = await retrievedCounter.increment(0); expect(count).toBe(3); }); - test("connection params are passed to actors", async (c) => { + test("connection params are passed to workers", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, COUNTER_APP_PATH, ); - // Create an 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. + // 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. // This test just ensures the params are accepted by the driver. const counter = client.counter.getOrCreate(undefined, { params: { @@ -119,8 +119,8 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { }); }); - describe("Actor Creation & Retrieval", () => { - test("creates and retrieves actors by ID", async (c) => { + describe("Worker Creation & Retrieval", () => { + test("creates and retrieves workers by ID", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -130,17 +130,17 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { // Create a unique ID for this test const uniqueId = `test-counter-${crypto.randomUUID()}`; - // Create actor with specific ID + // Create worker with specific ID const counter = client.counter.getOrCreate([uniqueId]); await counter.increment(10); - // Retrieve the same actor by ID and verify state + // Retrieve the same worker 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 actor during creation", async (c) => { + test("passes input to worker during creation", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -149,18 +149,18 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { // Test data to pass as input const testInput = { - name: "test-actor", + name: "test-worker", value: 42, nested: { foo: "bar" }, }; - // Create actor with input - const actor = await client.inputActor.create(undefined, { + // Create worker with input + const worker = await client.inputWorker.create(undefined, { input: testInput, }); // Verify both createState and onCreate received the input - const inputs = await actor.getInputs(); + const inputs = await worker.getInputs(); // Input should be available in createState expect(inputs.initialInput).toEqual(testInput); @@ -176,11 +176,11 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { ACTION_INPUTS_APP_PATH, ); - // Create actor without providing input - const actor = await client.inputActor.create(); + // Create worker without providing input + const worker = await client.inputWorker.create(); // Get inputs and verify they're undefined - const inputs = await actor.getInputs(); + const inputs = await worker.getInputs(); // Should be undefined in createState expect(inputs.initialInput).toBeUndefined(); @@ -189,7 +189,7 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { expect(inputs.onCreateInput).toBeUndefined(); }); - test("getOrCreate passes input to actor during creation", async (c) => { + test("getOrCreate passes input to worker during creation", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -207,12 +207,12 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { }; // Use getOrCreate with input - const actor = client.inputActor.getOrCreate(uniqueKey, { + const worker = client.inputWorker.getOrCreate(uniqueKey, { createWithInput: testInput, }); // Verify both createState and onCreate received the input - const inputs = await actor.getInputs(); + const inputs = await worker.getInputs(); // Input should be available in createState expect(inputs.initialInput).toEqual(testInput); @@ -221,9 +221,9 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { expect(inputs.onCreateInput).toEqual(testInput); // Verify that calling getOrCreate again with the same key - // returns the existing actor and doesn't create a new one - const existingActor = client.inputActor.getOrCreate(uniqueKey); - const existingInputs = await existingActor.getInputs(); + // returns the existing worker and doesn't create a new one + const existingWorker = client.inputWorker.getOrCreate(uniqueKey); + const existingInputs = await existingWorker.getInputs(); // Should still have the original inputs expect(existingInputs.initialInput).toEqual(testInput); @@ -231,13 +231,13 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { }); // TODO: Correctly test region for each provider - //test("creates and retrieves actors with region", async (c) => { + //test("creates and retrieves workers with region", async (c) => { // const { client } = await setupDriverTest(c, // driverTestConfig, // COUNTER_APP_PATH // ); // - // // Create actor with a specific region + // // Create worker with a specific region // const counter = client.counter.getOrCreate({ // create: { // key: ["metadata-test", "testing"], @@ -258,14 +258,14 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { }); describe("Key Matching", () => { - test("matches actors only with exactly the same keys", async (c) => { + test("matches workers only with exactly the same keys", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, COUNTER_APP_PATH, ); - // Create actor with multiple keys + // Create worker with multiple keys const originalCounter = client.counter.getOrCreate([ "counter-match", "test", @@ -282,7 +282,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 actor + // Should NOT match with subset of keys - should create new worker const subsetMatchCounter = client.counter.getOrCreate([ "counter-match", "test", @@ -290,7 +290,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 actor + // Should NOT match with just one key - should create new worker const singleKeyCounter = client.counter.getOrCreate(["counter-match"]); const singleKeyCount = await singleKeyCounter.increment(0); expect(singleKeyCount).toBe(0); // Should be a new counter with 0 @@ -303,7 +303,7 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { COUNTER_APP_PATH, ); - // Create actor with string key + // Create worker with string key const stringKeyCounter = client.counter.getOrCreate("string-key-test"); await stringKeyCounter.increment(7); @@ -320,7 +320,7 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { COUNTER_APP_PATH, ); - // Create actor with undefined key + // Create worker with undefined key const undefinedKeyCounter = client.counter.getOrCreate(undefined); await undefinedKeyCounter.increment(12); @@ -335,7 +335,7 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { expect(noKeyCount).toBe(12); }); - test("no keys does not match actors with keys", async (c) => { + test("no keys does not match workers with keys", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -355,7 +355,7 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { expect(count).toBe(10); }); - test("actors with keys match actors with no keys", async (c) => { + test("workers with keys match workers with no keys", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -378,9 +378,9 @@ export function runManagerDriverTests(driverTestConfig: DriverTestConfig) { }); }); - describe("Multiple Actor Instances", () => { - // TODO: This test is flakey https://github.com/rivet-gg/actor-core/issues/873 - test("creates multiple actor instances of the same type", async (c) => { + describe("Multiple Worker Instances", () => { + // TODO: This test is flakey https://github.com/rivet-gg/worker-core/issues/873 + test("creates multiple worker instances of the same type", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, diff --git a/packages/actor/src/driver-test-suite/tests/actor-conn-state.ts b/packages/core/src/driver-test-suite/tests/worker-conn-state.ts similarity index 89% rename from packages/actor/src/driver-test-suite/tests/actor-conn-state.ts rename to packages/core/src/driver-test-suite/tests/worker-conn-state.ts index e36c73446..6920ffa9c 100644 --- a/packages/actor/src/driver-test-suite/tests/actor-conn-state.ts +++ b/packages/core/src/driver-test-suite/tests/worker-conn-state.ts @@ -6,10 +6,10 @@ import { type ConnStateApp, } from "../test-apps"; -export function runActorConnStateTests( +export function runWorkerConnStateTests( driverTestConfig: DriverTestConfig ) { - describe("Actor Connection State Tests", () => { + describe("Worker Connection State Tests", () => { describe("Connection State Initialization", () => { test("should retrieve connection state", async (c) => { const { client } = await setupDriverTest( @@ -18,8 +18,8 @@ export function runActorConnStateTests( CONN_STATE_APP_PATH, ); - // Connect to the actor - const connection = client.connStateActor.getOrCreate().connect(); + // Connect to the worker + const connection = client.connStateWorker.getOrCreate().connect(); // Get the connection state const connState = await connection.getConnectionState(); @@ -43,7 +43,7 @@ export function runActorConnStateTests( ); // Connect with custom parameters - const connection = client.connStateActor.getOrCreate([], { + const connection = client.connStateWorker.getOrCreate([], { params: { username: "testuser", role: "admin" @@ -71,11 +71,11 @@ export function runActorConnStateTests( ); // Create multiple connections - const conn1 = client.connStateActor.getOrCreate([], { + const conn1 = client.connStateWorker.getOrCreate([], { params: { username: "user1" } }).connect(); - const conn2 = client.connStateActor.getOrCreate([], { + const conn2 = client.connStateWorker.getOrCreate([], { params: { username: "user2" } }).connect(); @@ -106,14 +106,14 @@ export function runActorConnStateTests( ); // Create two connections - const handle = client.connStateActor.getOrCreate(); + const handle = client.connStateWorker.getOrCreate(); const conn1 = handle.connect(); const conn2 = handle.connect(); // Get state1 for reference const state1 = await conn1.getConnectionState(); - // Get connection IDs tracked by the actor + // Get connection IDs tracked by the worker const connectionIds = await conn1.getConnectionIds(); // There should be at least 2 connections tracked @@ -127,15 +127,15 @@ export function runActorConnStateTests( await conn2.dispose(); }); - test("should identify different connections in the same actor", async (c) => { + test("should identify different connections in the same worker", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, CONN_STATE_APP_PATH, ); - // Create two connections to the same actor - const handle = client.connStateActor.getOrCreate(); + // Create two connections to the same worker + const handle = client.connStateWorker.getOrCreate(); const conn1 = handle.connect(); const conn2 = handle.connect(); @@ -165,7 +165,7 @@ export function runActorConnStateTests( ); // Create a connection - const handle = client.connStateActor.getOrCreate(); + const handle = client.connStateWorker.getOrCreate(); const conn = handle.connect(); // Get the connection state @@ -200,7 +200,7 @@ export function runActorConnStateTests( ); // Create a connection - const conn = client.connStateActor.getOrCreate().connect(); + const conn = client.connStateWorker.getOrCreate().connect(); // Get the initial state const initialState = await conn.getConnectionState(); @@ -235,7 +235,7 @@ export function runActorConnStateTests( ); // Create two connections - const handle = client.connStateActor.getOrCreate(); + const handle = client.connStateWorker.getOrCreate(); const conn1 = handle.connect(); const conn2 = handle.connect(); diff --git a/packages/actor/src/driver-test-suite/tests/actor-conn.ts b/packages/core/src/driver-test-suite/tests/worker-conn.ts similarity index 93% rename from packages/actor/src/driver-test-suite/tests/actor-conn.ts rename to packages/core/src/driver-test-suite/tests/worker-conn.ts index 37ee79d6c..d347a9f96 100644 --- a/packages/actor/src/driver-test-suite/tests/actor-conn.ts +++ b/packages/core/src/driver-test-suite/tests/worker-conn.ts @@ -10,8 +10,8 @@ import { type LifecycleApp, } from "../test-apps"; -export function runActorConnTests(driverTestConfig: DriverTestConfig) { - describe("Actor Connection Tests", () => { +export function runWorkerConnTests(driverTestConfig: DriverTestConfig) { + describe("Worker Connection Tests", () => { describe("Connection Methods", () => { test("should connect using .get().connect()", async (c) => { const { client } = await setupDriverTest( @@ -20,7 +20,7 @@ export function runActorConnTests(driverTestConfig: DriverTestConfig) { COUNTER_APP_PATH, ); - // Create actor + // Create worker await client.counter.create(["test-get"]); // Get a handle and connect @@ -42,13 +42,13 @@ export function runActorConnTests(driverTestConfig: DriverTestConfig) { COUNTER_APP_PATH, ); - // Create an actor first to get its ID + // Create a worker first to get its ID const handle = client.counter.getOrCreate(["test-get-for-id"]); await handle.increment(3); - const actorId = await handle.resolve(); + const workerId = await handle.resolve(); - // Get a new handle using the actor ID and connect - const idHandle = client.counter.getForId(actorId); + // Get a new handle using the worker ID and connect + const idHandle = client.counter.getForId(workerId); const connection = idHandle.connect(); // Verify connection works and state is preserved @@ -66,7 +66,7 @@ export function runActorConnTests(driverTestConfig: DriverTestConfig) { COUNTER_APP_PATH, ); - // Get or create actor and connect + // Get or create worker and connect const handle = client.counter.getOrCreate(["test-get-or-create"]); const connection = handle.connect(); @@ -85,7 +85,7 @@ export function runActorConnTests(driverTestConfig: DriverTestConfig) { COUNTER_APP_PATH, ); - // Create actor and connect + // Create worker and connect const handle = await client.counter.create(["test-create"]); const connection = handle.connect(); @@ -106,7 +106,7 @@ export function runActorConnTests(driverTestConfig: DriverTestConfig) { COUNTER_APP_PATH, ); - // Create actor and connect + // Create worker and connect const handle = client.counter.getOrCreate(["test-broadcast"]); const connection = handle.connect(); @@ -135,7 +135,7 @@ export function runActorConnTests(driverTestConfig: DriverTestConfig) { COUNTER_APP_PATH, ); - // Create actor and connect + // Create worker and connect const handle = client.counter.getOrCreate(["test-once"]); const connection = handle.connect(); @@ -164,7 +164,7 @@ export function runActorConnTests(driverTestConfig: DriverTestConfig) { COUNTER_APP_PATH, ); - // Create actor and connect + // Create worker and connect const handle = client.counter.getOrCreate(["test-unsubscribe"]); const connection = handle.connect(); diff --git a/packages/core/src/driver-test-suite/tests/worker-driver.ts b/packages/core/src/driver-test-suite/tests/worker-driver.ts new file mode 100644 index 000000000..f2da28d93 --- /dev/null +++ b/packages/core/src/driver-test-suite/tests/worker-driver.ts @@ -0,0 +1,16 @@ +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/actor/src/driver-test-suite/tests/actor-error-handling.ts b/packages/core/src/driver-test-suite/tests/worker-error-handling.ts similarity index 82% rename from packages/actor/src/driver-test-suite/tests/actor-error-handling.ts rename to packages/core/src/driver-test-suite/tests/worker-error-handling.ts index e10786255..9a40f3f7a 100644 --- a/packages/actor/src/driver-test-suite/tests/actor-error-handling.ts +++ b/packages/core/src/driver-test-suite/tests/worker-error-handling.ts @@ -3,8 +3,8 @@ import type { DriverTestConfig } from "../mod"; import { setupDriverTest } from "../utils"; import { ERROR_HANDLING_APP_PATH, type ErrorHandlingApp } from "../test-apps"; -export function runActorErrorHandlingTests(driverTestConfig: DriverTestConfig) { - describe("Actor Error Handling Tests", () => { +export function runWorkerErrorHandlingTests(driverTestConfig: DriverTestConfig) { + describe("Worker Error Handling Tests", () => { describe("UserError Handling", () => { test("should handle simple UserError with message", async (c) => { const { client } = await setupDriverTest( @@ -14,7 +14,7 @@ export function runActorErrorHandlingTests(driverTestConfig: DriverTestConfig) { ); // Try to call an action that throws a simple UserError - const handle = client.errorHandlingActor.getOrCreate(); + const handle = client.errorHandlingWorker.getOrCreate(); try { await handle.throwSimpleError(); @@ -38,7 +38,7 @@ export function runActorErrorHandlingTests(driverTestConfig: DriverTestConfig) { ); // Try to call an action that throws a detailed UserError - const handle = client.errorHandlingActor.getOrCreate(); + const handle = client.errorHandlingWorker.getOrCreate(); try { await handle.throwDetailedError(); @@ -64,7 +64,7 @@ export function runActorErrorHandlingTests(driverTestConfig: DriverTestConfig) { ); // Try to call an action that throws an internal error - const handle = client.errorHandlingActor.getOrCreate(); + const handle = client.errorHandlingWorker.getOrCreate(); try { await handle.throwInternalError(); @@ -89,9 +89,9 @@ export function runActorErrorHandlingTests(driverTestConfig: DriverTestConfig) { ); // Call an action that should time out - const handle = client.errorHandlingActor.getOrCreate(); + const handle = client.errorHandlingWorker.getOrCreate(); - // This should throw a timeout error because errorHandlingActor has + // This should throw a timeout error because errorHandlingWorker has // a 500ms timeout and this action tries to run for much longer const timeoutPromise = handle.timeoutAction(); @@ -113,27 +113,27 @@ export function runActorErrorHandlingTests(driverTestConfig: DriverTestConfig) { ); // Call an action with a delay shorter than the timeout - const handle = client.errorHandlingActor.getOrCreate(); + const handle = client.errorHandlingWorker.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 actors", async (c) => { + test("should respect different timeouts for different workers", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, ERROR_HANDLING_APP_PATH, ); - // The following actors have different timeout settings: - // customTimeoutActor: 200ms timeout - // standardTimeoutActor: default timeout (much longer) + // The following workers have different timeout settings: + // customTimeoutWorker: 200ms timeout + // standardTimeoutWorker: default timeout (much longer) // This should fail - 300ms delay with 200ms timeout try { - await client.customTimeoutActor.getOrCreate().slowAction(); + await client.customTimeoutWorker.getOrCreate().slowAction(); // Should not reach here expect(true).toBe(false); } catch (error: any) { @@ -141,7 +141,7 @@ export function runActorErrorHandlingTests(driverTestConfig: DriverTestConfig) { } // This should succeed - 50ms delay with 200ms timeout - const quickResult = await client.customTimeoutActor + const quickResult = await client.customTimeoutWorker .getOrCreate() .quickAction(); expect(quickResult).toBe("Quick action completed"); @@ -156,7 +156,7 @@ export function runActorErrorHandlingTests(driverTestConfig: DriverTestConfig) { ERROR_HANDLING_APP_PATH, ); - const handle = client.errorHandlingActor.getOrCreate(); + const handle = client.errorHandlingWorker.getOrCreate(); // Trigger an error try { @@ -165,7 +165,7 @@ export function runActorErrorHandlingTests(driverTestConfig: DriverTestConfig) { // Ignore error } - // Actor should still work after error + // Worker should still work after error const result = await handle.successfulAction(); expect(result).toBe("success"); }); diff --git a/packages/actor/src/driver-test-suite/tests/actor-handle.ts b/packages/core/src/driver-test-suite/tests/worker-handle.ts similarity index 84% rename from packages/actor/src/driver-test-suite/tests/actor-handle.ts rename to packages/core/src/driver-test-suite/tests/worker-handle.ts index 74ffdf6e6..aadc339d2 100644 --- a/packages/actor/src/driver-test-suite/tests/actor-handle.ts +++ b/packages/core/src/driver-test-suite/tests/worker-handle.ts @@ -8,17 +8,17 @@ import { type LifecycleApp, } from "../test-apps"; -export function runActorHandleTests(driverTestConfig: DriverTestConfig) { - describe("Actor Handle Tests", () => { +export function runWorkerHandleTests(driverTestConfig: DriverTestConfig) { + describe("Worker Handle Tests", () => { describe("Access Methods", () => { - test("should use .get() to access an actor", async (c) => { + test("should use .get() to access a worker", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, COUNTER_APP_PATH, ); - // Create actor first + // Create worker first await client.counter.create(["test-get-handle"]); // Access using get @@ -32,20 +32,20 @@ export function runActorHandleTests(driverTestConfig: DriverTestConfig) { expect(retrievedCount).toBe(5); }); - test("should use .getForId() to access an actor by ID", async (c) => { + test("should use .getForId() to access a worker by ID", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, COUNTER_APP_PATH, ); - // Create an actor first to get its ID + // Create a worker first to get its ID const handle = client.counter.getOrCreate(["test-get-for-id-handle"]); await handle.increment(3); - const actorId = await handle.resolve(); + const workerId = await handle.resolve(); // Access using getForId - const idHandle = client.counter.getForId(actorId); + const idHandle = client.counter.getForId(workerId); // Verify Action works and state is preserved const count = await idHandle.getCount(); @@ -55,14 +55,14 @@ export function runActorHandleTests(driverTestConfig: DriverTestConfig) { expect(newCount).toBe(7); }); - test("should use .getOrCreate() to access or create an actor", async (c) => { + test("should use .getOrCreate() to access or create a worker", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, COUNTER_APP_PATH, ); - // Access using getOrCreate - should create the actor + // Access using getOrCreate - should create the worker const handle = client.counter.getOrCreate([ "test-get-or-create-handle", ]); @@ -71,7 +71,7 @@ export function runActorHandleTests(driverTestConfig: DriverTestConfig) { const count = await handle.increment(7); expect(count).toBe(7); - // Get the same actor again - should retrieve existing actor + // Get the same worker again - should retrieve existing worker const sameHandle = client.counter.getOrCreate([ "test-get-or-create-handle", ]); @@ -86,7 +86,7 @@ export function runActorHandleTests(driverTestConfig: DriverTestConfig) { COUNTER_APP_PATH, ); - // Create actor and get handle + // Create worker and get handle const handle = await client.counter.create(["test-create-handle"]); // Verify Action works @@ -119,14 +119,14 @@ export function runActorHandleTests(driverTestConfig: DriverTestConfig) { expect(retrievedCount).toBe(8); }); - test("should handle independent handles to the same actor", async (c) => { + test("should handle independent handles to the same worker", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, COUNTER_APP_PATH, ); - // Create two handles to the same actor + // Create two handles to the same worker const handle1 = client.counter.getOrCreate(["test-multiple-handles"]); const handle2 = client.counter.get(["test-multiple-handles"]); @@ -144,7 +144,7 @@ export function runActorHandleTests(driverTestConfig: DriverTestConfig) { expect(checkCount).toBe(7); }); - test("should resolve an actor's ID", async (c) => { + test("should resolve a worker's ID", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -153,45 +153,45 @@ export function runActorHandleTests(driverTestConfig: DriverTestConfig) { const handle = client.counter.getOrCreate(["test-resolve-id"]); - // Call an action to ensure actor exists + // Call an action to ensure worker exists await handle.increment(1); // Resolve the ID - const actorId = await handle.resolve(); + const workerId = await handle.resolve(); // Verify we got a valid ID (string) - expect(typeof actorId).toBe("string"); - expect(actorId).not.toBe(""); + expect(typeof workerId).toBe("string"); + expect(workerId).not.toBe(""); - // Verify we can use this ID to get the actor - const idHandle = client.counter.getForId(actorId); + // Verify we can use this ID to get the worker + const idHandle = client.counter.getForId(workerId); const count = await idHandle.getCount(); expect(count).toBe(1); }); }); describe("Lifecycle Hooks", () => { - test("should trigger lifecycle hooks on actor creation", async (c) => { + test("should trigger lifecycle hooks on worker creation", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, LIFECYCLE_APP_PATH, ); - // Get or create a new actor - this should trigger onStart + // Get or create a new worker - this should trigger onStart const handle = client.counter.getOrCreate(["test-lifecycle-handle"]); // Verify onStart was triggered const initialEvents = await handle.getEvents(); expect(initialEvents).toContain("onStart"); - // Create a separate handle to the same actor + // Create a separate handle to the same worker const sameHandle = client.counter.getOrCreate([ "test-lifecycle-handle", ]); // Verify events still include onStart but don't duplicate it - // (onStart should only be called once when the actor is first created) + // (onStart should only be called once when the worker is first created) const events = await sameHandle.getEvents(); expect(events).toContain("onStart"); expect(events.filter((e) => e === "onStart").length).toBe(1); @@ -258,7 +258,7 @@ export function runActorHandleTests(driverTestConfig: DriverTestConfig) { // Create a normal handle to view events const viewHandle = client.counter.getOrCreate(["test-lifecycle-multi-handle"]); - // Create two tracking handles to the same actor + // Create two tracking handles to the same worker const trackingHandle1 = client.counter.getOrCreate( ["test-lifecycle-multi-handle"], { params: { trackLifecycle: true } } diff --git a/packages/actor/src/driver-test-suite/tests/actor-metadata.ts b/packages/core/src/driver-test-suite/tests/worker-metadata.ts similarity index 72% rename from packages/actor/src/driver-test-suite/tests/actor-metadata.ts rename to packages/core/src/driver-test-suite/tests/worker-metadata.ts index 24448ef1e..ebf005eb9 100644 --- a/packages/actor/src/driver-test-suite/tests/actor-metadata.ts +++ b/packages/core/src/driver-test-suite/tests/worker-metadata.ts @@ -6,43 +6,43 @@ import { type MetadataApp, } from "../test-apps"; -export function runActorMetadataTests( +export function runWorkerMetadataTests( driverTestConfig: DriverTestConfig ) { - describe("Actor Metadata Tests", () => { - describe("Actor Name", () => { - test("should provide access to actor name", async (c) => { + describe("Worker Metadata Tests", () => { + describe("Worker Name", () => { + test("should provide access to worker name", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, METADATA_APP_PATH, ); - // Get the actor name - const handle = client.metadataActor.getOrCreate(); - const actorName = await handle.getActorName(); + // Get the worker name + const handle = client.metadataWorker.getOrCreate(); + const workerName = await handle.getWorkerName(); // Verify it matches the expected name - expect(actorName).toBe("metadataActor"); + expect(workerName).toBe("metadataWorker"); }); - test("should preserve actor name in state during onStart", async (c) => { + test("should preserve worker name in state during onStart", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, METADATA_APP_PATH, ); - // Get the stored actor name - const handle = client.metadataActor.getOrCreate(); - const storedName = await handle.getStoredActorName(); + // Get the stored worker name + const handle = client.metadataWorker.getOrCreate(); + const storedName = await handle.getStoredWorkerName(); // Verify it was stored correctly - expect(storedName).toBe("metadataActor"); + expect(storedName).toBe("metadataWorker"); }); }); - describe("Actor Tags", () => { + describe("Worker Tags", () => { test("should provide access to tags", async (c) => { const { client } = await setupDriverTest( c, @@ -50,8 +50,8 @@ export function runActorMetadataTests( METADATA_APP_PATH, ); - // Create actor and set up test tags - const handle = client.metadataActor.getOrCreate(); + // Create worker and set up test tags + const handle = client.metadataWorker.getOrCreate(); await handle.setupTestTags({ "env": "test", "purpose": "metadata-test" @@ -74,10 +74,10 @@ export function runActorMetadataTests( METADATA_APP_PATH, ); - // Create actor and set up test tags - const handle = client.metadataActor.getOrCreate(); + // Create worker and set up test tags + const handle = client.metadataWorker.getOrCreate(); await handle.setupTestTags({ - "category": "test-actor", + "category": "test-worker", "version": "1.0" }); @@ -87,7 +87,7 @@ export function runActorMetadataTests( const nonexistent = await handle.getTag("nonexistent"); // Verify the tag values - expect(category).toBe("test-actor"); + expect(category).toBe("test-worker"); expect(version).toBe("1.0"); expect(nonexistent).toBeNull(); }); @@ -101,8 +101,8 @@ export function runActorMetadataTests( METADATA_APP_PATH, ); - // Create actor and set up test metadata - const handle = client.metadataActor.getOrCreate(); + // Create worker and set up test metadata + const handle = client.metadataWorker.getOrCreate(); await handle.setupTestTags({ "type": "metadata-test" }); await handle.setupTestRegion("us-west-1"); @@ -111,7 +111,7 @@ export function runActorMetadataTests( // Verify structure of metadata expect(metadata).toHaveProperty("name"); - expect(metadata.name).toBe("metadataActor"); + expect(metadata.name).toBe("metadataWorker"); expect(metadata).toHaveProperty("tags"); expect(metadata.tags).toHaveProperty("type"); @@ -131,8 +131,8 @@ export function runActorMetadataTests( METADATA_APP_PATH, ); - // Create actor and set up test region - const handle = client.metadataActor.getOrCreate(); + // Create worker and set up test region + const handle = client.metadataWorker.getOrCreate(); await handle.setupTestRegion("eu-central-1"); // Get the region diff --git a/packages/actor/src/driver-test-suite/tests/actor-schedule.ts b/packages/core/src/driver-test-suite/tests/worker-schedule.ts similarity index 94% rename from packages/actor/src/driver-test-suite/tests/actor-schedule.ts rename to packages/core/src/driver-test-suite/tests/worker-schedule.ts index 9fe8a73e6..1c9ec2b22 100644 --- a/packages/actor/src/driver-test-suite/tests/actor-schedule.ts +++ b/packages/core/src/driver-test-suite/tests/worker-schedule.ts @@ -6,10 +6,10 @@ import { type ScheduledApp, } from "../test-apps"; -export function runActorScheduleTests( +export function runWorkerScheduleTests( driverTestConfig: DriverTestConfig ) { - describe("Actor Schedule Tests", () => { + describe("Worker Schedule Tests", () => { describe("Scheduled Alarms", () => { test("executes c.schedule.at() with specific timestamp", async (c) => { const { client } = await setupDriverTest( @@ -60,7 +60,7 @@ export function runActorScheduleTests( expect(scheduledCount).toBe(1); }); - test("scheduled tasks persist across actor restarts", async (c) => { + test("scheduled tasks persist across worker restarts", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -74,7 +74,7 @@ export function runActorScheduleTests( // Wait a little so the schedule is stored but hasn't triggered yet await waitFor(driverTestConfig, 50); - // Get a new reference to simulate actor restart + // Get a new reference to simulate worker restart const newInstance = client.scheduled.getOrCreate(); // Verify the schedule still exists but hasn't run yet diff --git a/packages/actor/src/driver-test-suite/tests/actor-state.ts b/packages/core/src/driver-test-suite/tests/worker-state.ts similarity index 80% rename from packages/actor/src/driver-test-suite/tests/actor-state.ts rename to packages/core/src/driver-test-suite/tests/worker-state.ts index 3718f5e8f..a6ec99b20 100644 --- a/packages/actor/src/driver-test-suite/tests/actor-state.ts +++ b/packages/core/src/driver-test-suite/tests/worker-state.ts @@ -6,12 +6,12 @@ import { type CounterApp, } from "../test-apps"; -export function runActorStateTests( +export function runWorkerStateTests( driverTestConfig: DriverTestConfig ) { - describe("Actor State Tests", () => { + describe("Worker State Tests", () => { describe("State Persistence", () => { - test("persists state between actor instances", async (c) => { + test("persists state between worker instances", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -23,30 +23,30 @@ export function runActorStateTests( const initialCount = await counterInstance.increment(5); expect(initialCount).toBe(5); - // Get a fresh reference to the same actor and verify state persisted + // Get a fresh reference to the same worker and verify state persisted const sameInstance = client.counter.getOrCreate(); const persistedCount = await sameInstance.increment(3); expect(persistedCount).toBe(8); }); - test("restores state after actor disconnect/reconnect", async (c) => { + test("restores state after worker disconnect/reconnect", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, COUNTER_APP_PATH, ); - // Create actor and set initial state + // Create worker and set initial state const counterInstance = client.counter.getOrCreate(); await counterInstance.increment(5); - // Reconnect to the same actor + // Reconnect to the same worker const reconnectedInstance = client.counter.getOrCreate(); const persistedCount = await reconnectedInstance.increment(0); expect(persistedCount).toBe(5); }); - test("maintains separate state for different actors", async (c) => { + test("maintains separate state for different workers", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, diff --git a/packages/actor/src/driver-test-suite/tests/actor-vars.ts b/packages/core/src/driver-test-suite/tests/worker-vars.ts similarity index 78% rename from packages/actor/src/driver-test-suite/tests/actor-vars.ts rename to packages/core/src/driver-test-suite/tests/worker-vars.ts index 2b616b5e7..abfd2a027 100644 --- a/packages/actor/src/driver-test-suite/tests/actor-vars.ts +++ b/packages/core/src/driver-test-suite/tests/worker-vars.ts @@ -3,8 +3,8 @@ import type { DriverTestConfig } from "../mod"; import { setupDriverTest } from "../utils"; import { VARS_APP_PATH, type VarsApp } from "../test-apps"; -export function runActorVarsTests(driverTestConfig: DriverTestConfig) { - describe("Actor Variables", () => { +export function runWorkerVarsTests(driverTestConfig: DriverTestConfig) { + describe("Worker Variables", () => { describe("Static vars", () => { test("should provide access to static vars", async (c) => { const { client } = await setupDriverTest( @@ -13,20 +13,20 @@ export function runActorVarsTests(driverTestConfig: DriverTestConfig) { VARS_APP_PATH, ); - const instance = client.staticVarActor.getOrCreate(); + const instance = client.staticVarWorker.getOrCreate(); // Test accessing vars const result = await instance.getVars(); - expect(result).toEqual({ counter: 42, name: "test-actor" }); + expect(result).toEqual({ counter: 42, name: "test-worker" }); // Test accessing specific var property const name = await instance.getName(); - expect(name).toBe("test-actor"); + expect(name).toBe("test-worker"); }); }); describe("Deep cloning of static vars", () => { - test("should deep clone static vars between actor instances", async (c) => { + test("should deep clone static vars between worker instances", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -34,8 +34,8 @@ export function runActorVarsTests(driverTestConfig: DriverTestConfig) { ); // Create two separate instances - const instance1 = client.nestedVarActor.getOrCreate(["instance1"]); - const instance2 = client.nestedVarActor.getOrCreate(["instance2"]); + const instance1 = client.nestedVarWorker.getOrCreate(["instance1"]); + const instance2 = client.nestedVarWorker.getOrCreate(["instance2"]); // Modify vars in the first instance const modifiedVars = await instance1.modifyNested(); @@ -60,7 +60,7 @@ export function runActorVarsTests(driverTestConfig: DriverTestConfig) { ); // Create an instance - const instance = client.dynamicVarActor.getOrCreate(); + const instance = client.dynamicVarWorker.getOrCreate(); // Test accessing dynamically created vars const vars = await instance.getVars(); @@ -68,7 +68,7 @@ export function runActorVarsTests(driverTestConfig: DriverTestConfig) { expect(vars).toHaveProperty("computed"); expect(typeof vars.random).toBe("number"); expect(typeof vars.computed).toBe("string"); - expect(vars.computed).toMatch(/^Actor-\d+$/); + expect(vars.computed).toMatch(/^Worker-\d+$/); }); test("should create different vars for different instances", async (c) => { @@ -79,8 +79,8 @@ export function runActorVarsTests(driverTestConfig: DriverTestConfig) { ); // Create two separate instances - const instance1 = client.uniqueVarActor.getOrCreate(["test1"]); - const instance2 = client.uniqueVarActor.getOrCreate(["test2"]); + const instance1 = client.uniqueVarWorker.getOrCreate(["test1"]); + const instance2 = client.uniqueVarWorker.getOrCreate(["test2"]); // Get vars from both instances const vars1 = await instance1.getVars(); @@ -100,7 +100,7 @@ export function runActorVarsTests(driverTestConfig: DriverTestConfig) { ); // Create an instance - const instance = client.driverCtxActor.getOrCreate(); + const instance = client.driverCtxWorker.getOrCreate(); // Test accessing driver context through vars const vars = await instance.getVars(); diff --git a/packages/actor/src/driver-test-suite/utils.ts b/packages/core/src/driver-test-suite/utils.ts similarity index 90% rename from packages/actor/src/driver-test-suite/utils.ts rename to packages/core/src/driver-test-suite/utils.ts index 4d2a0dfa9..c5461609c 100644 --- a/packages/actor/src/driver-test-suite/utils.ts +++ b/packages/core/src/driver-test-suite/utils.ts @@ -1,13 +1,13 @@ -import type { ActorCoreApp } from "@/mod"; +import type { WorkerCoreApp } from "@/mod"; import { type TestContext, vi } from "vitest"; import { createClient, type Client } from "@/client/mod"; import type { DriverTestConfig } from "./mod"; -import { assertUnreachable } from "@/actor/utils"; +import { assertUnreachable } from "@/worker/utils"; import { createInlineClientDriver } from "@/app/inline-client-driver"; import { createClientWithDriver } from "@/client/client"; // Must use `TestContext` since global hooks do not work when running concurrently -export async function setupDriverTest>( +export async function setupDriverTest>( c: TestContext, driverTestConfig: DriverTestConfig, appPath: string, diff --git a/packages/actor/src/inspector/common.ts b/packages/core/src/inspector/common.ts similarity index 95% rename from packages/actor/src/inspector/common.ts rename to packages/core/src/inspector/common.ts index c2cce6110..8833fe480 100644 --- a/packages/actor/src/inspector/common.ts +++ b/packages/core/src/inspector/common.ts @@ -1,10 +1,10 @@ -import type { ConnId } from "@/actor/connection"; +import type { ConnId } from "@/worker/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 "@/actor/errors"; +import * as errors from "@/worker/errors"; import type { ZodSchema } from "zod"; interface ConnectInspectorOpts { @@ -22,7 +22,7 @@ export type InspectorConnHandler = ( ) => Promise>; /** - * Represents a connection to an actor. + * Represents a connection to a worker. * @internal */ export class InspectorConnection { @@ -47,7 +47,7 @@ export class InspectorConnection { } /** - * Provides a unified interface for inspecting actor and managers. + * Provides a unified interface for inspecting worker and managers. */ export class Inspector { /** diff --git a/packages/actor/src/inspector/config.ts b/packages/core/src/inspector/config.ts similarity index 100% rename from packages/actor/src/inspector/config.ts rename to packages/core/src/inspector/config.ts diff --git a/packages/actor/src/inspector/manager.ts b/packages/core/src/inspector/manager.ts similarity index 77% rename from packages/actor/src/inspector/manager.ts rename to packages/core/src/inspector/manager.ts index 618c8f806..4e759669a 100644 --- a/packages/actor/src/inspector/manager.ts +++ b/packages/core/src/inspector/manager.ts @@ -5,7 +5,7 @@ import { ToServerSchema, } from "@/inspector/protocol/manager/mod"; import { logger } from "@/manager/log"; -import * as errors from "@/actor/errors"; +import * as errors from "@/worker/errors"; import { createInspectorRoute, Inspector, @@ -14,11 +14,11 @@ import { } from "./common"; import type { InspectorConfig } from "./config"; import type { ManagerDriver } from "@/manager/driver"; -import { throttle } from "@/actor/utils"; +import { throttle } from "@/worker/utils"; export type ManagerInspectorConnHandler = InspectorConnHandler; -interface Actor { +interface Worker { id: string; name: string; key: string[]; @@ -46,34 +46,34 @@ export function createManagerInspectorRouter( } /** - * Represents a connection to an actor. + * Represents a connection to a worker. * @internal */ export type ManagerInspectorConnection = InspectorConnection; /** - * Provides a unified interface for inspecting actor external and internal state. + * Provides a unified interface for inspecting worker external and internal state. */ export class ManagerInspector extends Inspector { /** - * Inspected actor instance. + * Inspected worker instance. * @internal */ readonly driver: ManagerDriver; /** - * Notify all inspector listeners of an actor's state change. + * Notify all inspector listeners of a worker's state change. * @param state - The new state. */ - public onActorsChange = throttle((actors: Actor[]) => { - this.broadcast({ type: "actors", actors }); + public onWorkersChange = throttle((workers: Worker[]) => { + this.broadcast({ type: "workers", workers }); }, 500); constructor( driver: ManagerDriver, private readonly hooks: { - getAllActors: () => Actor[]; - getAllTypesOfActors: () => string[]; + getAllWorkers: () => Worker[]; + getAllTypesOfWorkers: () => string[]; }, ) { super(); @@ -99,8 +99,8 @@ export class ManagerInspector extends Inspector { if (message.type === "info") { return connection.send({ type: "info", - actors: this.hooks.getAllActors(), - types: this.hooks.getAllTypesOfActors(), + workers: this.hooks.getAllWorkers(), + types: this.hooks.getAllTypesOfWorkers(), }); } diff --git a/packages/actor/src/inspector/mod.ts b/packages/core/src/inspector/mod.ts similarity index 51% rename from packages/actor/src/inspector/mod.ts rename to packages/core/src/inspector/mod.ts index 388f1e810..088ff8cce 100644 --- a/packages/actor/src/inspector/mod.ts +++ b/packages/core/src/inspector/mod.ts @@ -1,2 +1,2 @@ export { ManagerInspector } from "./manager"; -export { ActorInspector } from "./actor"; +export { WorkerInspector } from "./worker"; diff --git a/packages/actor/src/inspector/protocol/actor/mod.ts b/packages/core/src/inspector/protocol/manager/mod.ts similarity index 100% rename from packages/actor/src/inspector/protocol/actor/mod.ts rename to packages/core/src/inspector/protocol/manager/mod.ts diff --git a/packages/actor/src/inspector/protocol/manager/to-client.ts b/packages/core/src/inspector/protocol/manager/to-client.ts similarity index 67% rename from packages/actor/src/inspector/protocol/manager/to-client.ts rename to packages/core/src/inspector/protocol/manager/to-client.ts index ecc2ebb6a..a19b4fcd1 100644 --- a/packages/actor/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 ActorSchema = z.object({ +const WorkerSchema = z.object({ id: z.string(), name: z.string(), key: z.array(z.string()), }); -export type Actor = z.infer; +export type Worker = z.infer; export const ToClientSchema = z.discriminatedUnion("type", [ z.object({ type: z.literal("info"), - actors: z.array(ActorSchema), + workers: z.array(WorkerSchema), types: z.array(z.string()), }), z.object({ - type: z.literal("actors"), - actors: z.array(ActorSchema), + type: z.literal("workers"), + workers: z.array(WorkerSchema), }), z.object({ type: z.literal("error"), diff --git a/packages/actor/src/inspector/protocol/manager/to-server.ts b/packages/core/src/inspector/protocol/manager/to-server.ts similarity index 90% rename from packages/actor/src/inspector/protocol/manager/to-server.ts rename to packages/core/src/inspector/protocol/manager/to-server.ts index f5b6a749c..2fd65cc60 100644 --- a/packages/actor/src/inspector/protocol/manager/to-server.ts +++ b/packages/core/src/inspector/protocol/manager/to-server.ts @@ -6,7 +6,7 @@ export const ToServerSchema = z.discriminatedUnion("type", [ }), z.object({ type: z.literal("destroy"), - actorId: z.string(), + workerId: z.string(), }), ]); diff --git a/packages/actor/src/inspector/protocol/manager/mod.ts b/packages/core/src/inspector/protocol/worker/mod.ts similarity index 100% rename from packages/actor/src/inspector/protocol/manager/mod.ts rename to packages/core/src/inspector/protocol/worker/mod.ts diff --git a/packages/actor/src/inspector/protocol/actor/to-client.ts b/packages/core/src/inspector/protocol/worker/to-client.ts similarity index 100% rename from packages/actor/src/inspector/protocol/actor/to-client.ts rename to packages/core/src/inspector/protocol/worker/to-client.ts diff --git a/packages/actor/src/inspector/protocol/actor/to-server.ts b/packages/core/src/inspector/protocol/worker/to-server.ts similarity index 100% rename from packages/actor/src/inspector/protocol/actor/to-server.ts rename to packages/core/src/inspector/protocol/worker/to-server.ts diff --git a/packages/actor/src/inspector/actor.ts b/packages/core/src/inspector/worker.ts similarity index 52% rename from packages/actor/src/inspector/actor.ts rename to packages/core/src/inspector/worker.ts index 9ff3f3cc7..ab917ceaf 100644 --- a/packages/actor/src/inspector/actor.ts +++ b/packages/core/src/inspector/worker.ts @@ -1,14 +1,14 @@ -import type { AnyActorInstance } from "@/actor/instance"; -import type { AnyConn, ConnId } from "@/actor/connection"; -import { throttle } from "@/actor/utils"; +import type { AnyWorkerInstance } from "@/worker/instance"; +import type { AnyConn, ConnId } from "@/worker/connection"; +import { throttle } from "@/worker/utils"; import type { UpgradeWebSocket } from "hono/ws"; -import * as errors from "@/actor/errors"; +import * as errors from "@/worker/errors"; import { type ToClient, type ToServer, ToServerSchema, -} from "@/inspector/protocol/actor/mod"; -import { logger } from "@/actor/log"; +} from "@/inspector/protocol/worker/mod"; +import { logger } from "@/worker/log"; import { createInspectorRoute, Inspector, @@ -17,18 +17,18 @@ import { } from "./common"; import type { InspectorConfig } from "./config"; -export type ActorInspectorConnHandler = InspectorConnHandler; +export type WorkerInspectorConnHandler = InspectorConnHandler; /** - * Create a router for the actor inspector. + * Create a router for the worker inspector. * @internal */ -export function createActorInspectorRouter( +export function createWorkerInspectorRouter( upgradeWebSocket: UpgradeWebSocket | undefined, - onConnect: ActorInspectorConnHandler | undefined, + onConnect: WorkerInspectorConnHandler | undefined, config: InspectorConfig, ) { - return createInspectorRoute({ + return createInspectorRoute({ upgradeWebSocket, onConnect, config, @@ -38,23 +38,23 @@ export function createActorInspectorRouter( } /** - * Represents a connection to an actor. + * Represents a connection to a worker. * @internal */ -export type ActorInspectorConnection = InspectorConnection; +export type WorkerInspectorConnection = InspectorConnection; /** - * Provides a unified interface for inspecting actor external and internal state. + * Provides a unified interface for inspecting worker external and internal state. */ -export class ActorInspector extends Inspector { +export class WorkerInspector extends Inspector { /** - * Inspected actor instance. + * Inspected worker instance. * @internal */ - readonly actor: AnyActorInstance; + readonly worker: AnyWorkerInstance; /** - * Notify all inspector listeners of an actor's state change. + * Notify all inspector listeners of a worker's state change. * @param state - The new state. * @internal */ @@ -64,7 +64,7 @@ export class ActorInspector extends Inspector { /** * - * Notify all inspector listeners of an actor's connections change. + * Notify all inspector listeners of a worker's connections change. * @param connections - The new connections. * @internal */ @@ -72,22 +72,22 @@ export class ActorInspector extends Inspector { this.broadcast(this.#createInfoMessage()); }, 500); - constructor(actor: AnyActorInstance) { + constructor(worker: AnyWorkerInstance) { super(); - this.actor = actor; + this.worker = worker; } /** * Process a message from a connection. * @internal */ - processMessage(connection: ActorInspectorConnection, message: ToServer) { + processMessage(connection: WorkerInspectorConnection, message: ToServer) { super.processMessage(connection, message); if (message.type === "info") { return connection.send(this.#createInfoMessage()); } if (message.type === "setState") { - this.actor.state = message.state; + this.worker.state = message.state; return; } @@ -100,7 +100,7 @@ export class ActorInspector extends Inspector { #createInfoMessage(): ToClient { return { type: "info", - connections: Array.from(this.actor.conns).map(([id, connection]) => ({ + connections: Array.from(this.worker.conns).map(([id, connection]) => ({ id, parameters: connection.params, state: { @@ -108,10 +108,10 @@ export class ActorInspector extends Inspector { enabled: connection._stateEnabled, }, })), - actions: this.actor.actions, + actions: this.worker.actions, state: { - value: this.actor.stateEnabled ? this.actor.state : undefined, - enabled: this.actor.stateEnabled, + value: this.worker.stateEnabled ? this.worker.state : undefined, + enabled: this.worker.stateEnabled, }, }; } diff --git a/packages/actor/src/manager/driver.ts b/packages/core/src/manager/driver.ts similarity index 60% rename from packages/actor/src/manager/driver.ts rename to packages/core/src/manager/driver.ts index d95713f89..36897954e 100644 --- a/packages/actor/src/manager/driver.ts +++ b/packages/core/src/manager/driver.ts @@ -1,30 +1,30 @@ -import type { ActorKey } from "@/common/utils"; +import type { WorkerKey } from "@/common/utils"; import type { ManagerInspector } from "@/inspector/manager"; import type { Env, Context as HonoContext, HonoRequest } from "hono"; export interface ManagerDriver { - getForId(input: GetForIdInput): Promise; - getWithKey(input: GetWithKeyInput): Promise; - getOrCreateWithKey(input: GetOrCreateWithKeyInput): Promise; - createActor(input: CreateInput): Promise; + getForId(input: GetForIdInput): Promise; + getWithKey(input: GetWithKeyInput): Promise; + getOrCreateWithKey(input: GetOrCreateWithKeyInput): Promise; + createWorker(input: CreateInput): Promise; inspector?: ManagerInspector; } export interface GetForIdInput { req?: HonoRequest | undefined; - actorId: string; + workerId: string; } export interface GetWithKeyInput { req?: HonoRequest | undefined; name: string; - key: ActorKey; + key: WorkerKey; } export interface GetOrCreateWithKeyInput { req?: HonoRequest | undefined; name: string; - key: ActorKey; + key: WorkerKey; input?: unknown; region?: string; } @@ -32,14 +32,14 @@ export interface GetOrCreateWithKeyInput { export interface CreateInput { req?: HonoRequest | undefined; name: string; - key: ActorKey; + key: WorkerKey; input?: unknown; region?: string; } -export interface ActorOutput { - actorId: string; +export interface WorkerOutput { + workerId: string; name: string; - key: ActorKey; + key: WorkerKey; meta?: unknown; } diff --git a/packages/actor/src/manager/log.ts b/packages/core/src/manager/log.ts similarity index 70% rename from packages/actor/src/manager/log.ts rename to packages/core/src/manager/log.ts index 41beaa5b8..94947c9e8 100644 --- a/packages/actor/src/manager/log.ts +++ b/packages/core/src/manager/log.ts @@ -1,6 +1,6 @@ import { getLogger } from "@/common//log"; -export const LOGGER_NAME = "actor-manager"; +export const LOGGER_NAME = "worker-manager"; export function logger() { return getLogger(LOGGER_NAME); diff --git a/packages/actor/src/manager/mod.ts b/packages/core/src/manager/mod.ts similarity index 100% rename from packages/actor/src/manager/mod.ts rename to packages/core/src/manager/mod.ts diff --git a/packages/core/src/manager/protocol/mod.ts b/packages/core/src/manager/protocol/mod.ts new file mode 100644 index 000000000..e6dc7c9d8 --- /dev/null +++ b/packages/core/src/manager/protocol/mod.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import { WorkerQuerySchema } from "./query"; +import { TransportSchema } from "@/worker/protocol/message/mod"; +export * from "./query"; + +export const WorkersRequestSchema = z.object({ + query: WorkerQuerySchema, +}); + +export const WorkersResponseSchema = z.object({ + workerId: z.string(), + supportedTransports: z.array(TransportSchema), +}); + +//export const RivetConfigResponseSchema = z.object({ +// endpoint: z.string(), +// project: z.string().optional(), +// environment: z.string().optional(), +//}); + +export type WorkersRequest = z.infer; +export type WorkersResponse = z.infer; +//export type RivetConfigResponse = z.infer; + diff --git a/packages/actor/src/manager/protocol/query.ts b/packages/core/src/manager/protocol/query.ts similarity index 69% rename from packages/actor/src/manager/protocol/query.ts rename to packages/core/src/manager/protocol/query.ts index a4fc87ff1..675b20dd1 100644 --- a/packages/actor/src/manager/protocol/query.ts +++ b/packages/core/src/manager/protocol/query.ts @@ -1,38 +1,38 @@ -import { ActorKeySchema } from "@/common//utils"; +import { WorkerKeySchema } from "@/common//utils"; import { z } from "zod"; -import { EncodingSchema } from "@/actor/protocol/serde"; +import { EncodingSchema } from "@/worker/protocol/serde"; import { - HEADER_ACTOR_ID, + HEADER_WORKER_ID, HEADER_CONN_ID, HEADER_CONN_PARAMS, HEADER_CONN_TOKEN, HEADER_ENCODING, - HEADER_ACTOR_QUERY, -} from "@/actor/router-endpoints"; + HEADER_WORKER_QUERY, +} from "@/worker/router-endpoints"; export const CreateRequestSchema = z.object({ name: z.string(), - key: ActorKeySchema, + key: WorkerKeySchema, input: z.unknown().optional(), region: z.string().optional(), }); export const GetForKeyRequestSchema = z.object({ name: z.string(), - key: ActorKeySchema, + key: WorkerKeySchema, }); export const GetOrCreateRequestSchema = z.object({ name: z.string(), - key: ActorKeySchema, + key: WorkerKeySchema, input: z.unknown().optional(), region: z.string().optional(), }); -export const ActorQuerySchema = z.union([ +export const WorkerQuerySchema = z.union([ z.object({ getForId: z.object({ - actorId: z.string(), + workerId: z.string(), }), }), z.object({ @@ -47,32 +47,32 @@ export const ActorQuerySchema = z.union([ ]); export const ConnectRequestSchema = z.object({ - query: ActorQuerySchema.describe(HEADER_ACTOR_QUERY), + query: WorkerQuerySchema.describe(HEADER_WORKER_QUERY), encoding: EncodingSchema.describe(HEADER_ENCODING), connParams: z.string().optional().describe(HEADER_CONN_PARAMS), }); export const ConnectWebSocketRequestSchema = z.object({ - query: ActorQuerySchema.describe("query"), + query: WorkerQuerySchema.describe("query"), encoding: EncodingSchema.describe("encoding"), }); export const ConnMessageRequestSchema = z.object({ - actorId: z.string().describe(HEADER_ACTOR_ID), + workerId: z.string().describe(HEADER_WORKER_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: ActorQuerySchema.describe(HEADER_ACTOR_QUERY), + query: WorkerQuerySchema.describe(HEADER_WORKER_QUERY), }); -export type ActorQuery = z.infer; +export type WorkerQuery = z.infer; export type GetForKeyRequest = z.infer; export type GetOrCreateRequest = z.infer; export type ConnectQuery = z.infer; /** - * Interface representing a request to create an actor. + * Interface representing a request to create a worker. */ export type CreateRequest = z.infer; diff --git a/packages/actor/src/manager/router.ts b/packages/core/src/manager/router.ts similarity index 80% rename from packages/actor/src/manager/router.ts rename to packages/core/src/manager/router.ts index 4c1ef2105..d9c9c78c2 100644 --- a/packages/actor/src/manager/router.ts +++ b/packages/core/src/manager/router.ts @@ -1,7 +1,7 @@ -import * as errors from "@/actor/errors"; -import type * as protoHttpResolve from "@/actor/protocol/http/resolve"; -import type { ToClient } from "@/actor/protocol/message/to-client"; -import { type Encoding, serialize } from "@/actor/protocol/serde"; +import * as errors from "@/worker/errors"; +import type * as protoHttpResolve from "@/worker/protocol/http/resolve"; +import type { ToClient } from "@/worker/protocol/message/to-client"; +import { type Encoding, serialize } from "@/worker/protocol/serde"; import { type ConnectionHandlers, getRequestEncoding, @@ -9,16 +9,16 @@ import { handleAction, handleSseConnect, handleWebSocketConnect, - HEADER_ACTOR_ID, + HEADER_WORKER_ID, HEADER_CONN_ID, HEADER_CONN_PARAMS, HEADER_CONN_TOKEN, HEADER_ENCODING, - HEADER_ACTOR_QUERY, + HEADER_WORKER_QUERY, ALL_HEADERS, getRequestQuery, -} from "@/actor/router-endpoints"; -import { assertUnreachable } from "@/actor/utils"; +} from "@/worker/router-endpoints"; +import { assertUnreachable } from "@/worker/utils"; import type { AppConfig } from "@/app/config"; import { handleRouteError, @@ -47,9 +47,9 @@ import { ConnMessageRequestSchema, ResolveRequestSchema, } from "./protocol/query"; -import type { ActorQuery } from "./protocol/query"; +import type { WorkerQuery } from "./protocol/query"; import { VERSION } from "@/utils"; -import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; +import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; type ManagerRouterHandler = { onConnectInspector?: ManagerInspectorConnHandler; @@ -61,17 +61,17 @@ const OPENAPI_ENCODING = z.string().openapi({ example: "json", }); -const OPENAPI_ACTOR_QUERY = z.string().openapi({ - description: "Actor query information", +const OPENAPI_WORKER_QUERY = z.string().openapi({ + description: "Worker query information", }); const OPENAPI_CONN_PARAMS = z.string().openapi({ description: "Connection parameters", }); -const OPENAPI_ACTOR_ID = z.string().openapi({ - description: "Actor ID (used in some endpoints)", - example: "actor-123456", +const OPENAPI_WORKER_ID = z.string().openapi({ + description: "Worker ID (used in some endpoints)", + example: "worker-123456", }); const OPENAPI_CONN_ID = z.string().openapi({ @@ -127,7 +127,7 @@ export function createManagerRouter( const path = c.req.path; // Don't apply to WebSocket routes - if (path === "/actors/connect/websocket" || path === "/inspect") { + if (path === "/workers/connect/websocket" || path === "/inspect") { return next(); } @@ -141,7 +141,7 @@ export function createManagerRouter( // GET / app.get("/", (c) => { return c.text( - "This is an ActorCore server.\n\nLearn more at https://actorcore.org", + "This is an WorkerCore server.\n\nLearn more at https://workercore.org", ); }); @@ -150,12 +150,12 @@ export function createManagerRouter( return c.text("ok"); }); - // POST /actors/resolve + // POST /workers/resolve { const ResolveQuerySchema = z .object({ query: z.any().openapi({ - example: { getForId: { actorId: "actor-123" } }, + example: { getForId: { workerId: "worker-123" } }, }), }) .openapi("ResolveQuery"); @@ -163,14 +163,14 @@ export function createManagerRouter( const ResolveResponseSchema = z .object({ i: z.string().openapi({ - example: "actor-123", + example: "worker-123", }), }) .openapi("ResolveResponse"); const resolveRoute = createRoute({ method: "post", - path: "/actors/resolve", + path: "/workers/resolve", request: { body: { content: { @@ -180,7 +180,7 @@ export function createManagerRouter( }, }, headers: z.object({ - [HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY, + [HEADER_WORKER_QUERY]: OPENAPI_WORKER_QUERY, }), }, responses: buildOpenApiResponses(ResolveResponseSchema), @@ -189,15 +189,15 @@ export function createManagerRouter( app.openapi(resolveRoute, (c) => handleResolveRequest(c, driver)); } - // GET /actors/connect/websocket + // GET /workers/connect/websocket { const wsRoute = createRoute({ method: "get", - path: "/actors/connect/websocket", + path: "/workers/connect/websocket", request: { query: z.object({ encoding: OPENAPI_ENCODING, - query: OPENAPI_ACTOR_QUERY, + query: OPENAPI_WORKER_QUERY, }), }, responses: { @@ -219,15 +219,15 @@ export function createManagerRouter( ); } - // GET /actors/connect/sse + // GET /workers/connect/sse { const sseRoute = createRoute({ method: "get", - path: "/actors/connect/sse", + path: "/workers/connect/sse", request: { headers: z.object({ [HEADER_ENCODING]: OPENAPI_ENCODING, - [HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY, + [HEADER_WORKER_QUERY]: OPENAPI_WORKER_QUERY, [HEADER_CONN_PARAMS]: OPENAPI_CONN_PARAMS.optional(), }), }, @@ -248,7 +248,7 @@ export function createManagerRouter( ); } - // POST /actors/action/:action + // POST /workers/action/:action { const ActionParamsSchema = z .object({ @@ -265,7 +265,7 @@ export function createManagerRouter( const ActionRequestSchema = z .object({ query: z.any().openapi({ - example: { getForId: { actorId: "actor-123" } }, + example: { getForId: { workerId: "worker-123" } }, }), body: z .any() @@ -280,7 +280,7 @@ export function createManagerRouter( const actionRoute = createRoute({ method: "post", - path: "/actors/actions/{action}", + path: "/workers/actions/{action}", request: { params: ActionParamsSchema, body: { @@ -303,12 +303,12 @@ export function createManagerRouter( ); } - // POST /actors/message + // POST /workers/message { const ConnectionMessageRequestSchema = z .object({ message: z.any().openapi({ - example: { type: "message", content: "Hello, actor!" }, + example: { type: "message", content: "Hello, worker!" }, }), }) .openapi("ConnectionMessageRequest"); @@ -319,7 +319,7 @@ export function createManagerRouter( const messageRoute = createRoute({ method: "post", - path: "/actors/message", + path: "/workers/message", request: { body: { content: { @@ -329,7 +329,7 @@ export function createManagerRouter( }, }, headers: z.object({ - [HEADER_ACTOR_ID]: OPENAPI_ACTOR_ID, + [HEADER_WORKER_ID]: OPENAPI_WORKER_ID, [HEADER_CONN_ID]: OPENAPI_CONN_ID, [HEADER_ENCODING]: OPENAPI_ENCODING, [HEADER_CONN_TOKEN]: OPENAPI_CONN_TOKEN, @@ -358,7 +358,7 @@ export function createManagerRouter( openapi: "3.0.0", info: { version: VERSION, - title: "ActorCore API", + title: "WorkerCore API", }, }); @@ -369,34 +369,34 @@ export function createManagerRouter( } /** - * Query the manager driver to get or create an actor based on the provided query + * Query the manager driver to get or create a worker based on the provided query */ -export async function queryActor( +export async function queryWorker( c: HonoContext, - query: ActorQuery, + query: WorkerQuery, driver: ManagerDriver, -): Promise<{ actorId: string; meta?: unknown }> { - logger().debug("querying actor", { query }); - let actorOutput: { actorId: string; meta?: unknown }; +): Promise<{ workerId: string; meta?: unknown }> { + logger().debug("querying worker", { query }); + let workerOutput: { workerId: string; meta?: unknown }; if ("getForId" in query) { const output = await driver.getForId({ req: c.req, - actorId: query.getForId.actorId, + workerId: query.getForId.workerId, }); - if (!output) throw new errors.ActorNotFound(query.getForId.actorId); - actorOutput = output; + if (!output) throw new errors.WorkerNotFound(query.getForId.workerId); + workerOutput = output; } else if ("getForKey" in query) { - const existingActor = await driver.getWithKey({ + const existingWorker = await driver.getWithKey({ req: c.req, name: query.getForKey.name, key: query.getForKey.key, }); - if (!existingActor) { - throw new errors.ActorNotFound( + if (!existingWorker) { + throw new errors.WorkerNotFound( `${query.getForKey.name}:${JSON.stringify(query.getForKey.key)}`, ); } - actorOutput = existingActor; + workerOutput = existingWorker; } else if ("getOrCreateForKey" in query) { const getOrCreateOutput = await driver.getOrCreateWithKey({ req: c.req, @@ -405,31 +405,31 @@ export async function queryActor( input: query.getOrCreateForKey.input, region: query.getOrCreateForKey.region, }); - actorOutput = { - actorId: getOrCreateOutput.actorId, + workerOutput = { + workerId: getOrCreateOutput.workerId, meta: getOrCreateOutput.meta, }; } else if ("create" in query) { - const createOutput = await driver.createActor({ + const createOutput = await driver.createWorker({ req: c.req, name: query.create.name, key: query.create.key, input: query.create.input, region: query.create.region, }); - actorOutput = { - actorId: createOutput.actorId, + workerOutput = { + workerId: createOutput.workerId, meta: createOutput.meta, }; } else { throw new errors.InvalidRequest("Invalid query format"); } - logger().debug("actor query result", { - actorId: actorOutput.actorId, - meta: actorOutput.meta, + logger().debug("worker query result", { + workerId: workerOutput.workerId, + meta: workerOutput.meta, }); - return { actorId: actorOutput.actorId, meta: actorOutput.meta }; + return { workerId: workerOutput.workerId, meta: workerOutput.meta }; } /** @@ -462,10 +462,10 @@ async function handleSseConnectRequest( const query = params.data.query; - // Get the actor ID and meta - const { actorId, meta } = await queryActor(c, query, driver); - invariant(actorId, "Missing actor ID"); - logger().debug("sse connection to actor", { actorId, meta }); + // Get the worker ID and meta + const { workerId, meta } = await queryWorker(c, query, driver); + invariant(workerId, "Missing worker ID"); + logger().debug("sse connection to worker", { workerId, meta }); // Handle based on mode if ("inline" in handler.routingHandler) { @@ -476,11 +476,11 @@ async function handleSseConnectRequest( appConfig, driverConfig, handler.routingHandler.inline.handlers.onConnectSse, - actorId, + workerId, ); } else if ("custom" in handler.routingHandler) { logger().debug("using custom proxy mode for sse connection"); - const url = new URL("http://actor/connect/sse"); + const url = new URL("http://worker/connect/sse"); const proxyRequest = new Request(url, c.req.raw); proxyRequest.headers.set(HEADER_ENCODING, params.data.encoding); if (params.data.connParams) { @@ -489,7 +489,7 @@ async function handleSseConnectRequest( return await handler.routingHandler.custom.proxyRequest( c, proxyRequest, - actorId, + workerId, meta, ); } else { @@ -583,10 +583,10 @@ async function handleWebSocketConnectRequest( throw new errors.InvalidRequest(params.error); } - // Get the actor ID and meta - const { actorId, meta } = await queryActor(c, params.data.query, driver); - logger().debug("found actor for websocket connection", { actorId, meta }); - invariant(actorId, "missing actor id"); + // Get the worker ID and meta + const { workerId, meta } = await queryWorker(c, params.data.query, driver); + logger().debug("found worker for websocket connection", { workerId, meta }); + invariant(workerId, "missing worker id"); if ("inline" in handler.routingHandler) { logger().debug("using inline proxy mode for websocket connection"); @@ -603,7 +603,7 @@ async function handleWebSocketConnectRequest( appConfig, driverConfig, onConnectWebSocket, - actorId, + workerId, )(); })(c, noopNext()); } else if ("custom" in handler.routingHandler) { @@ -611,7 +611,7 @@ async function handleWebSocketConnectRequest( return await handler.routingHandler.custom.proxyWebSocket( c, `/connect/websocket?encoding=${params.data.encoding}`, - actorId, + workerId, meta, ); } else { @@ -663,7 +663,7 @@ async function handleWebSocketConnectRequest( } /** - * Handle a connection message request to an actor + * Handle a connection message request to a worker */ async function handleMessageRequest( c: HonoContext, @@ -673,7 +673,7 @@ async function handleMessageRequest( logger().debug("connection message request received"); try { const params = ConnMessageRequestSchema.safeParse({ - actorId: c.req.header(HEADER_ACTOR_ID), + workerId: c.req.header(HEADER_WORKER_ID), connId: c.req.header(HEADER_CONN_ID), encoding: c.req.header(HEADER_ENCODING), connToken: c.req.header(HEADER_CONN_TOKEN), @@ -684,7 +684,7 @@ async function handleMessageRequest( }); throw new errors.InvalidRequest(params.error); } - const { actorId, connId, encoding, connToken } = params.data; + const { workerId, connId, encoding, connToken } = params.data; // Handle based on mode if ("inline" in handler.routingHandler) { @@ -696,11 +696,11 @@ async function handleMessageRequest( handler.routingHandler.inline.handlers.onConnMessage, connId, connToken as string, - actorId, + workerId, ); } else if ("custom" in handler.routingHandler) { logger().debug("using custom proxy mode for connection message"); - const url = new URL(`http://actor/connections/message`); + const url = new URL(`http://worker/connections/message`); const proxyRequest = new Request(url, c.req.raw); proxyRequest.headers.set(HEADER_ENCODING, encoding); @@ -710,7 +710,7 @@ async function handleMessageRequest( return await handler.routingHandler.custom.proxyRequest( c, proxyRequest, - actorId, + workerId, ); } else { assertUnreachable(handler.routingHandler); @@ -718,8 +718,8 @@ async function handleMessageRequest( } catch (error) { logger().error("error proxying connection message", { error }); - // Use ProxyError if it's not already an ActorError - if (!errors.ActorError.isActorError(error)) { + // Use ProxyError if it's not already an WorkerError + if (!errors.WorkerError.isWorkerError(error)) { throw new errors.ProxyError("connection message", error); } else { throw error; @@ -728,7 +728,7 @@ async function handleMessageRequest( } /** - * Handle an action request to an actor + * Handle an action request to a worker */ async function handleActionRequest( c: HonoContext, @@ -754,10 +754,10 @@ async function handleActionRequest( throw new errors.InvalidRequest(params.error); } - // Get the actor ID and meta - const { actorId, meta } = await queryActor(c, params.data.query, driver); - logger().debug("found actor for action", { actorId, meta }); - invariant(actorId, "Missing actor ID"); + // Get the worker ID and meta + const { workerId, meta } = await queryWorker(c, params.data.query, driver); + logger().debug("found worker for action", { workerId, meta }); + invariant(workerId, "Missing worker ID"); // Handle based on mode if ("inline" in handler.routingHandler) { @@ -769,7 +769,7 @@ async function handleActionRequest( driverConfig, handler.routingHandler.inline.handlers.onAction, actionName, - actorId, + workerId, ); } else if ("custom" in handler.routingHandler) { logger().debug("using custom proxy mode for action call"); @@ -777,13 +777,13 @@ async function handleActionRequest( // TODO: Encoding // TODO: Parameters const url = new URL( - `http://actor/action/${encodeURIComponent(actionName)}`, + `http://worker/action/${encodeURIComponent(actionName)}`, ); const proxyRequest = new Request(url, c.req.raw); return await handler.routingHandler.custom.proxyRequest( c, proxyRequest, - actorId, + workerId, meta, ); } else { @@ -792,8 +792,8 @@ async function handleActionRequest( } catch (error) { logger().error("error in action handler", { error }); - // Use ProxyError if it's not already an ActorError - if (!errors.ActorError.isActorError(error)) { + // Use ProxyError if it's not already an WorkerError + if (!errors.WorkerError.isWorkerError(error)) { throw new errors.ProxyError("Action call", error); } else { throw error; @@ -802,7 +802,7 @@ async function handleActionRequest( } /** - * Handle the resolve request to get an actor ID from a query + * Handle the resolve request to get a worker ID from a query */ async function handleResolveRequest( c: HonoContext, @@ -821,14 +821,14 @@ async function handleResolveRequest( throw new errors.InvalidRequest(params.error); } - // Get the actor ID and meta - const { actorId, meta } = await queryActor(c, params.data.query, driver); - logger().debug("resolved actor", { actorId, meta }); - invariant(actorId, "Missing actor ID"); + // Get the worker ID and meta + const { workerId, meta } = await queryWorker(c, params.data.query, driver); + logger().debug("resolved worker", { workerId, meta }); + invariant(workerId, "Missing worker ID"); // Format response according to protocol const response: protoHttpResolve.ResolveResponse = { - i: actorId, + i: workerId, }; const serialized = serialize(response, encoding); return c.body(serialized); diff --git a/packages/actor/src/mod.ts b/packages/core/src/mod.ts similarity index 67% rename from packages/actor/src/mod.ts rename to packages/core/src/mod.ts index 3b884bc39..d83cfe810 100644 --- a/packages/actor/src/mod.ts +++ b/packages/core/src/mod.ts @@ -1,3 +1,3 @@ export * from "@/app/mod"; -export * from "@/actor/mod"; +export * from "@/worker/mod"; export * from "@/topologies/mod"; diff --git a/packages/actor/src/test/config.ts b/packages/core/src/test/config.ts similarity index 100% rename from packages/actor/src/test/config.ts rename to packages/core/src/test/config.ts diff --git a/packages/core/src/test/driver/global-state.ts b/packages/core/src/test/driver/global-state.ts new file mode 100644 index 000000000..ffe33a381 --- /dev/null +++ b/packages/core/src/test/driver/global-state.ts @@ -0,0 +1,70 @@ +import type { WorkerKey } from "@/mod"; + +export interface WorkerState { + id: string; + name: string; + key: WorkerKey; + persistedData: unknown; + input?: unknown; +} + +export class TestGlobalState { + #workers: 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}`); + } + return worker; + } + + readInput(workerId: string): unknown | undefined { + return this.#getWorker(workerId).input; + } + + readPersistedData(workerId: string): unknown | undefined { + return this.#getWorker(workerId).persistedData; + } + + writePersistedData(workerId: string, data: unknown) { + this.#getWorker(workerId).persistedData = data; + } + + createWorker( + workerId: string, + name: string, + key: WorkerKey, + input?: unknown, + ): void { + // Create worker state if it doesn't exist + if (!this.#workers.has(workerId)) { + this.#workers.set(workerId, { + id: workerId, + name, + key, + persistedData: undefined, + input, + }); + } else { + throw new Error(`Worker already exists for ID: ${workerId}`); + } + } + + findWorker(filter: (worker: WorkerState) => boolean): WorkerState | undefined { + for (const worker of this.#workers.values()) { + if (filter(worker)) { + return worker; + } + } + return undefined; + } + + getWorker(workerId: string): WorkerState | undefined { + return this.#workers.get(workerId); + } + + getAllWorkers(): WorkerState[] { + return Array.from(this.#workers.values()); + } +} diff --git a/packages/actor/src/test/driver/log.ts b/packages/core/src/test/driver/log.ts similarity index 100% rename from packages/actor/src/test/driver/log.ts rename to packages/core/src/test/driver/log.ts diff --git a/packages/core/src/test/driver/manager.ts b/packages/core/src/test/driver/manager.ts new file mode 100644 index 000000000..a9d80e5c3 --- /dev/null +++ b/packages/core/src/test/driver/manager.ts @@ -0,0 +1,145 @@ +import type { + GetForIdInput, + GetWithKeyInput, + GetOrCreateWithKeyInput, + ManagerDriver, + CreateInput, +} from "@/driver-helpers/mod"; +import { WorkerAlreadyExists } from "@/worker/errors"; +import type { TestGlobalState } from "./global-state"; +import * as crypto from "node:crypto"; +import { ManagerInspector } from "@/inspector/manager"; +import type { WorkerCoreApp } from "@/app/mod"; +import { WorkerOutput } from "@/manager/driver"; + +export class TestManagerDriver implements ManagerDriver { + #state: TestGlobalState; + + /** + * @internal + */ + inspector: ManagerInspector = new ManagerInspector(this, { + getAllWorkers: () => this.#state.getAllWorkers(), + getAllTypesOfWorkers: () => Object.keys(this.app.config.workers), + }); + + constructor( + private readonly app: WorkerCoreApp, + state: TestGlobalState, + ) { + this.#state = state; + } + + async getForId({ workerId }: GetForIdInput): Promise { + // Validate the worker exists + const worker = this.#state.getWorker(workerId); + if (!worker) { + return undefined; + } + + return { + workerId, + name: worker.name, + key: worker.key, + }; + } + + async getWithKey({ + name, + key, + }: GetWithKeyInput): Promise { + // NOTE: This is a slow implementation that checks each worker individually. + // This can be optimized with an index in the future. + + const worker = this.#state.findWorker((worker) => { + if (worker.name !== name) { + return false; + } + + // handle empty key + if (key === null || key === undefined) { + return worker.key === null || worker.key === undefined; + } + + // handle array + if (Array.isArray(key)) { + if (!Array.isArray(worker.key)) { + return false; + } + if (key.length !== worker.key.length) { + return false; + } + // Check if all elements in key are in worker.key + for (let i = 0; i < key.length; i++) { + if (key[i] !== worker.key[i]) { + return false; + } + } + return true; + } + + // Handle object + if (typeof key === "object" && !Array.isArray(key)) { + if (typeof worker.key !== "object" || Array.isArray(worker.key)) { + return false; + } + if (worker.key === null) { + return false; + } + + // Check if all keys in key are in worker.key + const keyObj = key as Record; + const workerKeyObj = worker.key as unknown as Record; + for (const k in keyObj) { + if (!(k in workerKeyObj) || keyObj[k] !== workerKeyObj[k]) { + return false; + } + } + return true; + } + + // handle scalar + return key === worker.key; + }); + + if (worker) { + return { + workerId: worker.id, + name, + key: worker.key, + }; + } + + return undefined; + } + + async getOrCreateWithKey( + input: GetOrCreateWithKeyInput, + ): Promise { + const getOutput = await this.getWithKey(input); + if (getOutput) { + return getOutput; + } else { + return await this.createWorker(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); + } + + const workerId = crypto.randomUUID(); + this.#state.createWorker(workerId, name, key, input); + + this.inspector.onWorkersChange(this.#state.getAllWorkers()); + + return { + workerId, + name, + key, + }; + } +} diff --git a/packages/actor/src/test/driver/mod.ts b/packages/core/src/test/driver/mod.ts similarity index 68% rename from packages/actor/src/test/driver/mod.ts rename to packages/core/src/test/driver/mod.ts index 51438dd00..e0e4b10d0 100644 --- a/packages/actor/src/test/driver/mod.ts +++ b/packages/core/src/test/driver/mod.ts @@ -1,3 +1,3 @@ export { TestGlobalState } from "./global-state"; -export { TestActorDriver } from "./actor"; +export { TestWorkerDriver } from "./worker"; export { TestManagerDriver } from "./manager"; diff --git a/packages/core/src/test/driver/worker.ts b/packages/core/src/test/driver/worker.ts new file mode 100644 index 000000000..c6820a2be --- /dev/null +++ b/packages/core/src/test/driver/worker.ts @@ -0,0 +1,40 @@ +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); + } +} diff --git a/packages/actor/src/test/log.ts b/packages/core/src/test/log.ts similarity index 100% rename from packages/actor/src/test/log.ts rename to packages/core/src/test/log.ts diff --git a/packages/actor/src/test/mod.ts b/packages/core/src/test/mod.ts similarity index 89% rename from packages/actor/src/test/mod.ts rename to packages/core/src/test/mod.ts index 8e9a1d0e7..6f8b82321 100644 --- a/packages/actor/src/test/mod.ts +++ b/packages/core/src/test/mod.ts @@ -4,11 +4,11 @@ import { assertUnreachable } from "@/utils"; import { CoordinateTopology } from "@/topologies/coordinate/mod"; import { logger } from "./log"; import type { Hono } from "hono"; -import { StandaloneTopology, type ActorCoreApp } from "@/mod"; +import { StandaloneTopology, type WorkerCoreApp } from "@/mod"; import { TestGlobalState, TestManagerDriver, - TestActorDriver, + TestWorkerDriver, } from "./driver/mod"; import { type InputConfig, ConfigSchema } from "./config"; import { type TestContext, vi } from "vitest"; @@ -16,7 +16,7 @@ import { type Client, createClient } from "@/client/mod"; import { createServer } from "node:net"; function createRouter( - app: ActorCoreApp, + app: WorkerCoreApp, inputConfig?: InputConfig, ): { router: Hono; @@ -26,13 +26,13 @@ function createRouter( // Configure default configuration if (!config.topology) config.topology = "standalone"; - if (!config.drivers.manager || !config.drivers.actor) { + if (!config.drivers.manager || !config.drivers.worker) { const memoryState = new TestGlobalState(); if (!config.drivers.manager) { config.drivers.manager = new TestManagerDriver(app, memoryState); } - if (!config.drivers.actor) { - config.drivers.actor = new TestActorDriver(memoryState); + if (!config.drivers.worker) { + config.drivers.worker = new TestWorkerDriver(memoryState); } } @@ -64,7 +64,7 @@ function createRouter( } } -function serve(app: ActorCoreApp, inputConfig?: InputConfig): ServerType { +function serve(app: WorkerCoreApp, inputConfig?: InputConfig): ServerType { const config = ConfigSchema.parse(inputConfig); const { router, injectWebSocket } = createRouter(app, config); @@ -76,7 +76,7 @@ function serve(app: ActorCoreApp, inputConfig?: InputConfig): ServerType { }); injectWebSocket(server); - logger().info("actorcore started", { + logger().info("workercore started", { hostname: config.hostname, port: config.port, }); @@ -84,17 +84,17 @@ function serve(app: ActorCoreApp, inputConfig?: InputConfig): ServerType { return server; } -export interface SetupTestResult> { +export interface SetupTestResult> { client: Client; mockDriver: { - actorDriver: { + workerDriver: { setCreateVarsContext: (ctx: any) => void; }; }; } // Must use `TestContext` since global hooks do not work when running concurrently -export async function setupTest>( +export async function setupTest>( c: TestContext, app: A, ): Promise> { @@ -122,7 +122,7 @@ export async function setupTest>( return { client, mockDriver: { - actorDriver: { + workerDriver: { setCreateVarsContext: setDriverContextFn, }, }, diff --git a/packages/actor/src/topologies/common/generic-conn-driver.ts b/packages/core/src/topologies/common/generic-conn-driver.ts similarity index 85% rename from packages/actor/src/topologies/common/generic-conn-driver.ts rename to packages/core/src/topologies/common/generic-conn-driver.ts index 3b38c0b71..6b9df94c5 100644 --- a/packages/actor/src/topologies/common/generic-conn-driver.ts +++ b/packages/core/src/topologies/common/generic-conn-driver.ts @@ -1,18 +1,18 @@ -import type { AnyActorInstance } from "@/actor/instance"; -import { AnyConn, Conn } from "@/actor/connection"; +import type { AnyWorkerInstance } from "@/worker/instance"; +import { AnyConn, Conn } from "@/worker/connection"; import { logger } from "./log"; -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 { 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 { WSContext } from "hono/ws"; import { SSEStreamingApi } from "hono/streaming"; -// 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 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 holds the actual connections, which are not serializable. // -// This is scoped to each actor. Do not share between multiple actors. +// This is scoped to each worker. Do not share between multiple workers. export class GenericConnGlobalState { websockets = new Map(); sseStreams = new Map(); @@ -43,7 +43,7 @@ export function createGenericWebSocketDriver( ): ConnDriver { return { sendMessage: ( - _actor: AnyActorInstance, + _worker: AnyWorkerInstance, conn: AnyConn, state: GenericWebSocketDriverState, message: CachedSerializer, @@ -57,7 +57,7 @@ export function createGenericWebSocketDriver( }, disconnect: async ( - _actor: AnyActorInstance, + _worker: AnyWorkerInstance, conn: AnyConn, _state: GenericWebSocketDriverState, reason?: string, @@ -96,7 +96,7 @@ export interface GenericSseDriverState { export function createGenericSseDriver(globalState: GenericConnGlobalState) { return { sendMessage: ( - _actor: AnyActorInstance, + _worker: AnyWorkerInstance, conn: AnyConn, state: GenericSseDriverState, message: CachedSerializer, @@ -114,7 +114,7 @@ export function createGenericSseDriver(globalState: GenericConnGlobalState) { }, disconnect: async ( - _actor: AnyActorInstance, + _worker: AnyWorkerInstance, conn: AnyConn, _state: GenericSseDriverState, _reason?: string, diff --git a/packages/actor/src/topologies/common/log.ts b/packages/core/src/topologies/common/log.ts similarity index 100% rename from packages/actor/src/topologies/common/log.ts rename to packages/core/src/topologies/common/log.ts diff --git a/packages/actor/src/topologies/coordinate/conn/driver.ts b/packages/core/src/topologies/coordinate/conn/driver.ts similarity index 62% rename from packages/actor/src/topologies/coordinate/conn/driver.ts rename to packages/core/src/topologies/coordinate/conn/driver.ts index a06dbe06e..88d169028 100644 --- a/packages/actor/src/topologies/coordinate/conn/driver.ts +++ b/packages/core/src/topologies/coordinate/conn/driver.ts @@ -1,9 +1,9 @@ -import type { ConnDriver } from "@/actor/driver"; +import type { ConnDriver } from "@/worker/driver"; import type { GlobalState } from "../topology"; -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 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 { logger } from "../log"; import type { NodeMessage } from "../node/protocol"; import type { CoordinateDriver } from "../driver"; @@ -20,14 +20,14 @@ export function createCoordinateRelayDriver( ): ConnDriver { return { sendMessage: ( - actor: AnyActorInstance, + worker: AnyWorkerInstance, conn: AnyConn, state: CoordinateRelayState, message: CachedSerializer, ) => { - const actorPeer = globalState.actorPeers.get(actor.id); - if (!actorPeer) { - logger().warn("missing actor for message", { actorId: actor.id }); + const workerPeer = globalState.workerPeers.get(worker.id); + if (!workerPeer) { + logger().warn("missing worker for message", { workerId: worker.id }); return; } @@ -43,16 +43,16 @@ export function createCoordinateRelayDriver( CoordinateDriver.publishToNode(state.nodeId, JSON.stringify(messageRaw)); }, disconnect: async ( - actor: AnyActorInstance, + worker: AnyWorkerInstance, conn: AnyConn, state: CoordinateRelayState, reason?: string, ) => { - if (actor.isStopping) return; + if (worker.isStopping) return; - const actorPeer = globalState.actorPeers.get(actor.id); - if (!actorPeer) { - logger().warn("missing actor for disconnect", { actorId: actor.id }); + const workerPeer = globalState.workerPeers.get(worker.id); + if (!workerPeer) { + logger().warn("missing worker for disconnect", { workerId: worker.id }); return; } diff --git a/packages/actor/src/topologies/coordinate/conn/mod.ts b/packages/core/src/topologies/coordinate/conn/mod.ts similarity index 76% rename from packages/actor/src/topologies/coordinate/conn/mod.ts rename to packages/core/src/topologies/coordinate/conn/mod.ts index 9a7be84a9..e489bf9d5 100644 --- a/packages/actor/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 "@/actor/protocol/message/to-client"; -import * as errors from "@/actor/errors"; +import type * as messageToClient from "@/worker/protocol/message/to-client"; +import * as errors from "@/worker/errors"; import type { CoordinateDriver } from "../driver"; import { logger } from "../log"; -import { ActorPeer } from "../actor-peer"; +import { WorkerPeer } from "../worker-peer"; import { publishMessageToLeader } from "../node/message"; -import { generateConnId, generateConnToken } from "@/actor/connection"; -import type { ActorDriver } from "@/actor/driver"; +import { generateConnId, generateConnToken } from "@/worker/connection"; +import type { WorkerDriver } from "@/worker/driver"; import { DriverConfig } from "@/driver-helpers/config"; import { AppConfig } from "@/app/config"; @@ -16,19 +16,19 @@ export interface RelayConnDriver { } /** - * This is different than `Connection`. `Connection` represents the data of the connection state on the actor itself, `RelayConnection` supports managing a connection for an actor running on another machine over pubsub. + * 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. */ export class RelayConn { #appConfig: AppConfig; #driverConfig: DriverConfig; #coordinateDriver: CoordinateDriver; - #actorDriver: ActorDriver; + #workerDriver: WorkerDriver; #globalState: GlobalState; #driver: RelayConnDriver; - #actorId: string; + #workerId: string; #parameters: unknown; - #actorPeer?: ActorPeer; + #workerPeer?: WorkerPeer; #connId?: string; #connToken?: string; @@ -50,20 +50,20 @@ export class RelayConn { constructor( appConfig: AppConfig, driverConfig: DriverConfig, - actorDriver: ActorDriver, + workerDriver: WorkerDriver, CoordinateDriver: CoordinateDriver, globalState: GlobalState, driver: RelayConnDriver, - actorId: string, + workerId: string, parameters: unknown, ) { this.#appConfig = appConfig; this.#driverConfig = driverConfig; this.#coordinateDriver = CoordinateDriver; - this.#actorDriver = actorDriver; + this.#workerDriver = workerDriver; this.#driver = driver; this.#globalState = globalState; - this.#actorId = actorId; + this.#workerId = workerId; this.#parameters = parameters; } @@ -77,18 +77,18 @@ export class RelayConn { this.#connToken = connToken; logger().info("starting relay connection", { - actorId: this.#actorId, + workerId: this.#workerId, connId: this.#connId, }); - // Create actor peer - this.#actorPeer = await ActorPeer.acquire( + // Create worker peer + this.#workerPeer = await WorkerPeer.acquire( this.#appConfig, this.#driverConfig, - this.#actorDriver, + this.#workerDriver, this.#coordinateDriver, this.#globalState, - this.#actorId, + this.#workerId, connId, ); @@ -100,11 +100,11 @@ export class RelayConn { this.#driverConfig, this.#coordinateDriver, this.#globalState, - this.#actorId, + this.#workerId, { b: { lco: { - ai: this.#actorId, + ai: this.#workerId, ci: connId, ct: connToken, p: this.#parameters, @@ -142,18 +142,18 @@ export class RelayConn { this.#globalState.relayConns.delete(this.#connId); // Publish connection close - if (!fromLeader && this.#actorPeer?.leaderNodeId) { + if (!fromLeader && this.#workerPeer?.leaderNodeId) { // Publish connection close await publishMessageToLeader( this.#appConfig, this.#driverConfig, this.#coordinateDriver, this.#globalState, - this.#actorId, + this.#workerId, { b: { lcc: { - ai: this.#actorId, + ai: this.#workerId, ci: this.#connId, }, }, @@ -162,10 +162,10 @@ export class RelayConn { ); } - // Remove reference to actor (will shut down if no more references) + // Remove reference to worker (will shut down if no more references) // // IMPORTANT: Do this last since we need to send the connection close event - await this.#actorPeer?.removeConnectionReference(this.#connId); + await this.#workerPeer?.removeConnectionReference(this.#connId); } else { logger().warn("disposing connection without connection id"); } diff --git a/packages/actor/src/topologies/coordinate/driver.ts b/packages/core/src/topologies/coordinate/driver.ts similarity index 63% rename from packages/actor/src/topologies/coordinate/driver.ts rename to packages/core/src/topologies/coordinate/driver.ts index b569c5c39..8b1324055 100644 --- a/packages/actor/src/topologies/coordinate/driver.ts +++ b/packages/core/src/topologies/coordinate/driver.ts @@ -1,19 +1,19 @@ -import type { ActorKey } from "@/common/utils"; +import type { WorkerKey } from "@/common/utils"; export type NodeMessageCallback = (message: string) => void; -export interface GetActorLeaderOutput { +export interface GetWorkerLeaderOutput { /** Undefined if not initialized. */ - actor?: { + worker?: { leaderNodeId?: string; }; } -export interface StartActorAndAcquireLeaseOutput { +export interface StartWorkerAndAcquireLeaseOutput { /** Undefined if not initialized. */ - actor?: { + worker?: { name?: string; - key?: ActorKey; + key?: WorkerKey; leaderNodeId?: string; }; } @@ -34,22 +34,22 @@ export interface CoordinateDriver { ): Promise; publishToNode(targetNodeId: string, message: string): Promise; - // MARK: Actor lifecycle - getActorLeader(actorId: string): Promise; - startActorAndAcquireLease( - actorId: string, + // MARK: Worker lifecycle + getWorkerLeader(workerId: string): Promise; + startWorkerAndAcquireLease( + workerId: string, selfNodeId: string, leaseDuration: number, - ): Promise; + ): Promise; extendLease( - actorId: string, + workerId: string, selfNodeId: string, leaseDuration: number, ): Promise; attemptAcquireLease( - actorId: string, + workerId: string, selfNodeId: string, leaseDuration: number, ): Promise; - releaseLease(actorId: string, nodeId: string): Promise; + releaseLease(workerId: string, nodeId: string): Promise; } \ No newline at end of file diff --git a/packages/actor/src/topologies/coordinate/log.ts b/packages/core/src/topologies/coordinate/log.ts similarity index 68% rename from packages/actor/src/topologies/coordinate/log.ts rename to packages/core/src/topologies/coordinate/log.ts index c032a7727..769291a65 100644 --- a/packages/actor/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 = "actor-coordinate"; +export const LOGGER_NAME = "worker-coordinate"; export function logger() { return getLogger(LOGGER_NAME); diff --git a/packages/actor/src/topologies/coordinate/mod.ts b/packages/core/src/topologies/coordinate/mod.ts similarity index 100% rename from packages/actor/src/topologies/coordinate/mod.ts rename to packages/core/src/topologies/coordinate/mod.ts diff --git a/packages/actor/src/topologies/coordinate/node/message.ts b/packages/core/src/topologies/coordinate/node/message.ts similarity index 85% rename from packages/actor/src/topologies/coordinate/node/message.ts rename to packages/core/src/topologies/coordinate/node/message.ts index 9315bd974..090db4d63 100644 --- a/packages/actor/src/topologies/coordinate/node/message.ts +++ b/packages/core/src/topologies/coordinate/node/message.ts @@ -16,7 +16,7 @@ export async function publishMessageToLeader( driverConfig: DriverConfig, CoordinateDriver: CoordinateDriver, globalState: GlobalState, - actorId: string, + workerId: string, message: NodeMessage, signal?: AbortSignal, ) { @@ -35,7 +35,7 @@ export async function publishMessageToLeader( driverConfig, CoordinateDriver, globalState, - actorId, + workerId, messageId, message, signal, @@ -59,23 +59,23 @@ async function publishMessageToLeaderInner( driverConfig: DriverConfig, CoordinateDriver: CoordinateDriver, globalState: GlobalState, - actorId: string, + workerId: string, messageId: string, message: NodeMessage, signal?: AbortSignal, ) { // Find the leader node - const { actor } = await CoordinateDriver.getActorLeader(actorId); + const { worker } = await CoordinateDriver.getWorkerLeader(workerId); // Validate initialized - if (!actor) throw new AbortError("Actor not initialized"); + if (!worker) throw new AbortError("Worker not initialized"); // Validate node - if (!actor.leaderNodeId) { - throw new Error("actor not leased, may be transferring leadership"); + if (!worker.leaderNodeId) { + throw new Error("worker not leased, may be transferring leadership"); } - logger().debug("found actor leader node", { nodeId: actor.leaderNodeId }); + logger().debug("found worker leader node", { nodeId: worker.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")), - appConfig.actorPeer.messageAckTimeout, + appConfig.workerPeer.messageAckTimeout, ); try { // Forward outgoing message await CoordinateDriver.publishToNode( - actor.leaderNodeId, + worker.leaderNodeId, JSON.stringify(message), ); diff --git a/packages/actor/src/topologies/coordinate/node/mod.ts b/packages/core/src/topologies/coordinate/node/mod.ts similarity index 75% rename from packages/actor/src/topologies/coordinate/node/mod.ts rename to packages/core/src/topologies/coordinate/node/mod.ts index ec0cf1994..ed30e4077 100644 --- a/packages/actor/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 { ActorPeer } from "../actor-peer"; +import { WorkerPeer } from "../worker-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 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. + // 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. // - // Additionally, in most serverless environments, 1 node usually owns 1 actor, so this would double the RTT to create the required subscribers. + // Additionally, in most serverless environments, 1 node usually owns 1 worker, 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 actor + // TODO: Support multiple protocols for the worker // Parse message const { data, success, error } = NodeMessageSchema.safeParse( @@ -101,7 +101,7 @@ export class Node { async #onLeaderConnectionOpen( nodeId: string | undefined, { - ai: actorId, + ai: workerId, ci: connId, ct: connToken, p: connParams, @@ -112,21 +112,21 @@ export class Node { return; } - logger().debug("received connection open", { actorId, connId }); + logger().debug("received connection open", { workerId, connId }); // Connection open try { - const actor = ActorPeer.getLeaderActor(this.#globalState, actorId); - if (!actor) { - logger().warn("received message for nonexistent actor leader", { - actorId, + const worker = WorkerPeer.getLeaderWorker(this.#globalState, workerId); + if (!worker) { + logger().warn("received message for nonexistent worker leader", { + workerId, }); return; } - const connState = await actor.prepareConn(connParams); - await actor.createConn( + const connState = await worker.prepareConn(connParams); + await worker.createConn( connId, connToken, connParams, @@ -135,7 +135,7 @@ export class Node { { nodeId } satisfies CoordinateRelayState, ); - // Connection init will be sent by `Actor` + // Connection init will be sent by `Worker` } catch (error) { logger().warn("failed to open connection", { error: `${error}` }); @@ -150,29 +150,29 @@ export class Node { // }, //}; //redis.publish( - // PUBSUB.ACTOR.follower(actorId, followerId), + // PUBSUB.WORKER.follower(workerId, followerId), // JSON.stringify(message), //); } } async #onLeaderConnectionClose({ - ai: actorId, + ai: workerId, ci: connId, }: ToLeaderConnectionClose) { - logger().debug("received connection close", { actorId }); + logger().debug("received connection close", { workerId }); - const actor = ActorPeer.getLeaderActor(this.#globalState, actorId); - if (!actor) { - logger().warn("received message for nonexistent actor leader", { - actorId, + const worker = WorkerPeer.getLeaderWorker(this.#globalState, workerId); + if (!worker) { + logger().warn("received message for nonexistent worker leader", { + workerId, }); return; } - const conn = actor.__getConnForId(connId); + const conn = worker.__getConnForId(connId); if (conn) { - actor.__removeConn(conn); + worker.__removeConn(conn); } else { logger().warn("received connection close for nonexisting connection", { connId, @@ -181,24 +181,24 @@ export class Node { } async #onLeaderMessage({ - ai: actorId, + ai: workerId, ci: connId, ct: connToken, m: message, }: ToLeaderMessage) { - logger().debug("received connection message", { actorId, connId }); + logger().debug("received connection message", { workerId, connId }); // Get leader - const actor = ActorPeer.getLeaderActor(this.#globalState, actorId); - if (!actor) { - logger().warn("received message for nonexistent actor leader", { - actorId, + const worker = WorkerPeer.getLeaderWorker(this.#globalState, workerId); + if (!worker) { + logger().warn("received message for nonexistent worker leader", { + workerId, }); return; } // Get connection - const conn = actor.__getConnForId(connId); + const conn = worker.__getConnForId(connId); if (conn) { // Validate token if (conn._token !== connToken) { @@ -206,7 +206,7 @@ export class Node { } // Process message - await actor.processMessage(message, conn); + await worker.processMessage(message, conn); } else { logger().warn("received message for nonexisting connection", { connId, diff --git a/packages/actor/src/topologies/coordinate/node/protocol.ts b/packages/core/src/topologies/coordinate/node/protocol.ts similarity index 91% rename from packages/actor/src/topologies/coordinate/node/protocol.ts rename to packages/core/src/topologies/coordinate/node/protocol.ts index 1d54a0bf7..cc9f22320 100644 --- a/packages/actor/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 "@/actor/protocol/message/to-server" -import * as messageToClient from "@/actor/protocol/message/to-client" +import * as messageToServer from "@/worker/protocol/message/to-server" +import * as messageToClient from "@/worker/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({ - // Actor ID + // Worker ID ai: z.string(), // Connection ID ci: z.string(), @@ -23,7 +23,7 @@ export const ToLeaderConnectionOpenSchema = z.object({ export type ToLeaderConnectionOpen = z.infer; export const ToLeaderConnectionCloseSchema = z.object({ - // Actor ID + // Worker ID ai: z.string(), // Connection ID ci: z.string(), @@ -32,7 +32,7 @@ export const ToLeaderConnectionCloseSchema = z.object({ export type ToLeaderConnectionClose = z.infer; export const ToLeaderMessageSchema = z.object({ - // Actor ID + // Worker ID ai: z.string(), // Connection ID ci: z.string(), diff --git a/packages/actor/src/topologies/coordinate/router/sse.ts b/packages/core/src/topologies/coordinate/router/sse.ts similarity index 79% rename from packages/actor/src/topologies/coordinate/router/sse.ts rename to packages/core/src/topologies/coordinate/router/sse.ts index 712a410a7..4f136ebaa 100644 --- a/packages/actor/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 "@/actor/protocol/serde"; +import { encodeDataToString, serialize } from "@/worker/protocol/serde"; import type { CoordinateDriver } from "../driver"; import { RelayConn } from "../conn/mod"; -import type { ActorDriver } from "@/actor/driver"; +import type { WorkerDriver } from "@/worker/driver"; import { DriverConfig } from "@/driver-helpers/config"; import { AppConfig } from "@/app/config"; -import { ConnectSseOpts, ConnectSseOutput } from "@/actor/router-endpoints"; +import { ConnectSseOpts, ConnectSseOutput } from "@/worker/router-endpoints"; export async function serveSse( appConfig: AppConfig, driverConfig: DriverConfig, - actorDriver: ActorDriver, + workerDriver: WorkerDriver, CoordinateDriver: CoordinateDriver, globalState: GlobalState, - actorId: string, + workerId: string, { encoding, params }: ConnectSseOpts, ): Promise { let conn: RelayConn | undefined; @@ -23,7 +23,7 @@ export async function serveSse( conn = new RelayConn( appConfig, driverConfig, - actorDriver, + workerDriver, CoordinateDriver, globalState, { @@ -37,7 +37,7 @@ export async function serveSse( stream.close(); }, }, - actorId, + workerId, params, ); await conn.start(); diff --git a/packages/actor/src/topologies/coordinate/router/websocket.ts b/packages/core/src/topologies/coordinate/router/websocket.ts similarity index 82% rename from packages/actor/src/topologies/coordinate/router/websocket.ts rename to packages/core/src/topologies/coordinate/router/websocket.ts index 618f02b79..abbbeb481 100644 --- a/packages/actor/src/topologies/coordinate/router/websocket.ts +++ b/packages/core/src/topologies/coordinate/router/websocket.ts @@ -1,24 +1,24 @@ import type { GlobalState } from "@/topologies/coordinate/topology"; import type { WSContext } from "hono/ws"; import { logger } from "../log"; -import { serialize } from "@/actor/protocol/serde"; -import type * as messageToServer from "@/actor/protocol/message/to-server"; -import * as errors from "@/actor/errors"; +import { serialize } from "@/worker/protocol/serde"; +import type * as messageToServer from "@/worker/protocol/message/to-server"; +import * as errors from "@/worker/errors"; import type { CoordinateDriver } from "../driver"; import { RelayConn } from "../conn/mod"; import { publishMessageToLeader } from "../node/message"; -import type { ActorDriver } from "@/actor/driver"; +import type { WorkerDriver } from "@/worker/driver"; import type { DriverConfig } from "@/driver-helpers/config"; import type { AppConfig } from "@/app/config"; -import { ConnectWebSocketOpts, ConnectWebSocketOutput } from "@/actor/router-endpoints"; +import { ConnectWebSocketOpts, ConnectWebSocketOutput } from "@/worker/router-endpoints"; export async function serveWebSocket( appConfig: AppConfig, driverConfig: DriverConfig, - actorDriver: ActorDriver, + workerDriver: WorkerDriver, CoordinateDriver: CoordinateDriver, globalState: GlobalState, - actorId: string, + workerId: string, { req, encoding, params }: ConnectWebSocketOpts, ): Promise { let conn: RelayConn | undefined; @@ -27,7 +27,7 @@ export async function serveWebSocket( conn = new RelayConn( appConfig, driverConfig, - actorDriver, + workerDriver, CoordinateDriver, globalState, { @@ -39,7 +39,7 @@ export async function serveWebSocket( ws.close(); }, }, - actorId, + workerId, params, ); await conn.start(); @@ -54,11 +54,11 @@ export async function serveWebSocket( driverConfig, CoordinateDriver, globalState, - actorId, + workerId, { b: { lm: { - ai: actorId, + ai: workerId, ci: conn.connId, ct: conn.connToken, m: message, diff --git a/packages/actor/src/topologies/coordinate/topology.ts b/packages/core/src/topologies/coordinate/topology.ts similarity index 86% rename from packages/actor/src/topologies/coordinate/topology.ts rename to packages/core/src/topologies/coordinate/topology.ts index 13b3410fc..42f0e57bc 100644 --- a/packages/actor/src/topologies/coordinate/topology.ts +++ b/packages/core/src/topologies/coordinate/topology.ts @@ -1,6 +1,6 @@ import { Node } from "./node/mod"; -import type { ActorPeer } from "./actor-peer"; -import * as errors from "@/actor/errors"; +import type { WorkerPeer } from "./worker-peer"; +import * as errors from "@/worker/errors"; import * as events from "node:events"; import { publishMessageToLeader } from "./node/message"; import type { RelayConn } from "./conn/mod"; @@ -18,18 +18,18 @@ import type { ConnectSseOutput, ActionOutput, ConnectionHandlers, -} from "@/actor/router-endpoints"; +} from "@/worker/router-endpoints"; import invariant from "invariant"; import { createInlineClientDriver } from "@/app/inline-client-driver"; import { serveWebSocket } from "./router/websocket"; import { serveSse } from "./router/sse"; import { ClientDriver } from "@/client/client"; -import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; +import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; export interface GlobalState { nodeId: string; - /** Actors currently running on this instance. */ - actorPeers: Map; + /** Workers currently running on this instance. */ + workerPeers: Map; /** Connections that are connected to this node. */ relayConns: Map; /** Resolvers for when a message is acknowledged by the peer. */ @@ -42,9 +42,9 @@ export class CoordinateTopology { constructor(appConfig: AppConfig, driverConfig: DriverConfig) { if (!driverConfig.drivers) throw new Error("config.drivers not defined."); - const { actor: actorDriver, coordinate: CoordinateDriver } = + const { worker: workerDriver, coordinate: CoordinateDriver } = driverConfig.drivers; - if (!actorDriver) throw new Error("config.drivers.actor not defined."); + if (!workerDriver) throw new Error("config.drivers.worker not defined."); if (!CoordinateDriver) throw new Error("config.drivers.coordinate not defined."); @@ -54,7 +54,7 @@ export class CoordinateTopology { const globalState: GlobalState = { nodeId: crypto.randomUUID(), - actorPeers: new Map(), + workerPeers: new Map(), relayConns: new Map(), messageAckResolvers: new Map(), }; @@ -75,10 +75,10 @@ export class CoordinateTopology { return await serveWebSocket( appConfig, driverConfig, - actorDriver, + workerDriver, CoordinateDriver, globalState, - opts.actorId, + opts.workerId, opts, ); }, @@ -86,10 +86,10 @@ export class CoordinateTopology { return await serveSse( appConfig, driverConfig, - actorDriver, + workerDriver, CoordinateDriver, globalState, - opts.actorId, + opts.workerId, opts, ); }, @@ -103,11 +103,11 @@ export class CoordinateTopology { driverConfig, CoordinateDriver, globalState, - opts.actorId, + opts.workerId, { b: { lm: { - ai: opts.actorId, + ai: opts.workerId, ci: opts.connId, ct: opts.connToken, m: opts.message, diff --git a/packages/actor/src/topologies/coordinate/actor-peer.ts b/packages/core/src/topologies/coordinate/worker-peer.ts similarity index 57% rename from packages/actor/src/topologies/coordinate/actor-peer.ts rename to packages/core/src/topologies/coordinate/worker-peer.ts index 84773457f..21065cede 100644 --- a/packages/actor/src/topologies/coordinate/actor-peer.ts +++ b/packages/core/src/topologies/coordinate/worker-peer.ts @@ -1,9 +1,9 @@ import type { GlobalState } from "@/topologies/coordinate/topology"; import { logger } from "./log"; import type { CoordinateDriver } from "./driver"; -import type { ActorInstance, AnyActorInstance } from "@/actor/instance"; -import type { ActorKey } from "@/common/utils"; -import { ActorDriver } from "@/actor/driver"; +import type { WorkerInstance, AnyWorkerInstance } from "@/worker/instance"; +import type { WorkerKey } from "@/common/utils"; +import { WorkerDriver } from "@/worker/driver"; import { CONN_DRIVER_COORDINATE_RELAY, createCoordinateRelayDriver, @@ -11,25 +11,25 @@ import { import { DriverConfig } from "@/driver-helpers/config"; import { AppConfig, AppConfigSchema } from "@/app/config"; -export class ActorPeer { +export class WorkerPeer { #appConfig: AppConfig; #driverConfig: DriverConfig; #coordinateDriver: CoordinateDriver; - #actorDriver: ActorDriver; + #workerDriver: WorkerDriver; #globalState: GlobalState; - #actorId: string; - #actorName?: string; - #actorKey?: ActorKey; + #workerId: string; + #workerName?: string; + #workerKey?: WorkerKey; #isDisposed = false; - /** Connections that hold a reference to this actor. If this set is empty, the actor should be shut down. */ + /** Connections that hold a reference to this worker. If this set is empty, the worker should be shut down. */ #referenceConnections = new Set(); - /** Node ID that's the leader for this actor. */ + /** Node ID that's the leader for this worker. */ #leaderNodeId?: string; - /** Holds the insantiated actor class if is leader. */ - #loadedActor?: AnyActorInstance; + /** Holds the insantiated worker class if is leader. */ + #loadedWorker?: AnyWorkerInstance; #heartbeatTimeout?: NodeJS.Timeout; @@ -46,48 +46,48 @@ export class ActorPeer { appConfig: AppConfig, driverConfig: DriverConfig, CoordinateDriver: CoordinateDriver, - actorDriver: ActorDriver, + workerDriver: WorkerDriver, globalState: GlobalState, - actorId: string, + workerId: string, ) { this.#appConfig = appConfig; this.#driverConfig = driverConfig; this.#coordinateDriver = CoordinateDriver; - this.#actorDriver = actorDriver; + this.#workerDriver = workerDriver; this.#globalState = globalState; - this.#actorId = actorId; + this.#workerId = workerId; } - /** Acquires a `ActorPeer` for a connection and includes the connection ID in the references. */ + /** Acquires a `WorkerPeer` for a connection and includes the connection ID in the references. */ static async acquire( appConfig: AppConfig, driverConfig: DriverConfig, - actorDriver: ActorDriver, + workerDriver: WorkerDriver, CoordinateDriver: CoordinateDriver, globalState: GlobalState, - actorId: string, + workerId: string, connId: string, - ): Promise { - let peer = globalState.actorPeers.get(actorId); + ): Promise { + let peer = globalState.workerPeers.get(workerId); // Create peer if needed if (!peer) { - peer = new ActorPeer( + peer = new WorkerPeer( appConfig, driverConfig, CoordinateDriver, - actorDriver, + workerDriver, globalState, - actorId, + workerId, ); - globalState.actorPeers.set(actorId, peer); + globalState.workerPeers.set(workerId, peer); await peer.#start(); } peer.#referenceConnections.add(connId); - logger().debug("added actor reference", { - actorId, + logger().debug("added worker reference", { + workerId, connId, newReferenceCount: peer.#referenceConnections.size, }); @@ -95,17 +95,17 @@ export class ActorPeer { return peer; } - static getLeaderActor( + static getLeaderWorker( globalState: GlobalState, - actorId: string, - ): AnyActorInstance | undefined { - const peer = globalState.actorPeers.get(actorId); + workerId: string, + ): AnyWorkerInstance | undefined { + const peer = globalState.workerPeers.get(workerId); if (!peer) return undefined; if (peer.#isLeader) { - const actor = peer.#loadedActor; - if (!actor) - throw new Error("Actor is leader, but loadedActor is undefined"); - return actor; + const worker = peer.#loadedWorker; + if (!worker) + throw new Error("Worker is leader, but loadedWorker is undefined"); + return worker; } else { return undefined; } @@ -122,7 +122,7 @@ export class ActorPeer { // 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 actor from globalState + // TODO: Remove worker from globalState // TODO: Add back NX // Acquire lease @@ -130,41 +130,41 @@ export class ActorPeer { // TODO: Do this in 1 round trip with a Lua script // Acquire initial information - const { actor } = await this.#coordinateDriver.startActorAndAcquireLease( - this.#actorId, + const { worker } = await this.#coordinateDriver.startWorkerAndAcquireLease( + this.#workerId, this.#globalState.nodeId, - this.#appConfig.actorPeer.leaseDuration, + this.#appConfig.workerPeer.leaseDuration, ); // Log - logger().debug("starting actor peer", { - actor, + logger().debug("starting worker peer", { + worker, }); - // Validate actor exists - if (!actor) { - throw new Error("Actor does not exist"); + // Validate worker exists + if (!worker) { + throw new Error("Worker does not exist"); } // Parse tags - this.#actorName = actor.name; - this.#actorKey = actor.key; + this.#workerName = worker.name; + this.#workerKey = worker.key; // Handle leadership - this.#leaderNodeId = actor.leaderNodeId; - if (actor.leaderNodeId === this.#globalState.nodeId) { - logger().debug("actor peer is leader", { - actorId: this.#actorId, - leaderNodeId: actor.leaderNodeId, + this.#leaderNodeId = worker.leaderNodeId; + if (worker.leaderNodeId === this.#globalState.nodeId) { + logger().debug("worker peer is leader", { + workerId: this.#workerId, + leaderNodeId: worker.leaderNodeId, }); await this.#convertToLeader(); } else { - logger().debug("actor peer is follower", { - actorId: this.#actorId, - leaderNodeId: actor.leaderNodeId, + logger().debug("worker peer is follower", { + workerId: this.#workerId, + leaderNodeId: worker.leaderNodeId, }); - this.#leaderNodeId = actor.leaderNodeId; + this.#leaderNodeId = worker.leaderNodeId; } // Schedule first heartbeat @@ -189,44 +189,44 @@ export class ActorPeer { let hbTimeout: number; if (this.#isLeader) { hbTimeout = - this.#appConfig.actorPeer.leaseDuration - - this.#appConfig.actorPeer.renewLeaseGrace; + this.#appConfig.workerPeer.leaseDuration - + this.#appConfig.workerPeer.renewLeaseGrace; } else { // TODO: Add jitter hbTimeout = - this.#appConfig.actorPeer.checkLeaseInterval + - Math.random() * this.#appConfig.actorPeer.checkLeaseJitter; + this.#appConfig.workerPeer.checkLeaseInterval + + Math.random() * this.#appConfig.workerPeer.checkLeaseJitter; } if (hbTimeout < 0) - throw new Error("Actor peer heartbeat timeout is negative, check config"); + throw new Error("Worker peer heartbeat timeout is negative, check config"); this.#heartbeatTimeout = setTimeout(this.#heartbeat.bind(this), hbTimeout); } async #convertToLeader() { - if (!this.#actorName || !this.#actorKey) throw new Error("missing name or key"); + if (!this.#workerName || !this.#workerKey) throw new Error("missing name or key"); - logger().debug("peer acquired leadership", { actorId: this.#actorId }); + logger().debug("peer acquired leadership", { workerId: this.#workerId }); - // Build actor - const actorName = this.#actorName; - const definition = this.#appConfig.actors[actorName]; - if (!definition) throw new Error(`no actor definition for name ${definition}`); + // Build worker + const workerName = this.#workerName; + const definition = this.#appConfig.workers[workerName]; + if (!definition) throw new Error(`no worker definition for name ${definition}`); - // Create leader actor - const actor = definition.instantiate(); - this.#loadedActor = actor; + // Create leader worker + const worker = definition.instantiate(); + this.#loadedWorker = worker; - await actor.start( + await worker.start( { [CONN_DRIVER_COORDINATE_RELAY]: createCoordinateRelayDriver( this.#globalState, this.#coordinateDriver, ), }, - this.#actorDriver, - this.#actorId, - this.#actorName, - this.#actorKey, + this.#workerDriver, + this.#workerId, + this.#workerName, + this.#workerKey, "unknown", ); } @@ -234,18 +234,18 @@ export class ActorPeer { /** * 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 actor. + * If the lease has expired for any reason (e.g. connection latency or database purged), this will automatically shut down the worker. */ async #extendLease() { const { leaseValid } = await this.#coordinateDriver.extendLease( - this.#actorId, + this.#workerId, this.#globalState.nodeId, - this.#appConfig.actorPeer.leaseDuration, + this.#appConfig.workerPeer.leaseDuration, ); if (leaseValid) { - logger().debug("lease is valid", { actorId: this.#actorId }); + logger().debug("lease is valid", { workerId: this.#workerId }); } else { - logger().debug("lease is invalid", { actorId: this.#actorId }); + logger().debug("lease is invalid", { workerId: this.#workerId }); // Shut down. SInce the lease is already lost, no need to clear it. await this.#dispose(false); @@ -258,9 +258,9 @@ export class ActorPeer { async #attemptAcquireLease() { const { newLeaderNodeId } = await this.#coordinateDriver.attemptAcquireLease( - this.#actorId, + this.#workerId, this.#globalState.nodeId, - this.#appConfig.actorPeer.leaseDuration, + this.#appConfig.workerPeer.leaseDuration, ); // Check if the lease was successfully acquired and promoted to leader @@ -282,14 +282,14 @@ export class ActorPeer { const removed = this.#referenceConnections.delete(connId); if (removed) { - logger().debug("removed actor reference", { - actorId: this.#actorId, + logger().debug("removed worker reference", { + workerId: this.#workerId, connId, newReferenceCount: this.#referenceConnections.size, }); } else { - logger().warn("removed reference to actor that didn't exist", { - actorId: this.#actorId, + logger().warn("removed reference to worker that didn't exist", { + workerId: this.#workerId, connId, }); } @@ -303,27 +303,27 @@ export class ActorPeer { if (this.#isDisposed) return; this.#isDisposed = true; - logger().info("actor shutting down", { actorId: this.#actorId }); + logger().info("worker shutting down", { workerId: this.#workerId }); // IMPORTANT: Do this before anything async clearTimeout(this.#heartbeatTimeout); - this.#globalState.actorPeers.delete(this.#actorId); + this.#globalState.workerPeers.delete(this.#workerId); - // Stop actor + // Stop worker // // We wait for this to finish to ensure that state is persisted safely to storage - if (this.#isLeader && this.#loadedActor) { - await this.#loadedActor.stop(); + if (this.#isLeader && this.#loadedWorker) { + await this.#loadedWorker.stop(); } // Clear the lease if needed if (this.#isLeader && releaseLease) { await this.#coordinateDriver.releaseLease( - this.#actorId, + this.#workerId, this.#globalState.nodeId, ); } - logger().info("actor shutdown success", { actorId: this.#actorId }); + logger().info("worker shutdown success", { workerId: this.#workerId }); } } diff --git a/packages/actor/src/topologies/mod.ts b/packages/core/src/topologies/mod.ts similarity index 79% rename from packages/actor/src/topologies/mod.ts rename to packages/core/src/topologies/mod.ts index 4b9c83f44..ca53d8933 100644 --- a/packages/actor/src/topologies/mod.ts +++ b/packages/core/src/topologies/mod.ts @@ -1,5 +1,5 @@ /** - * ActorCore topologies for different scaling patterns + * WorkerCore topologies for different scaling patterns */ // Export the coordinate topology diff --git a/packages/actor/src/topologies/partition/log.ts b/packages/core/src/topologies/partition/log.ts similarity index 68% rename from packages/actor/src/topologies/partition/log.ts rename to packages/core/src/topologies/partition/log.ts index aaa3fd432..46c3d4dfe 100644 --- a/packages/actor/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 = "actor-standalone"; +export const LOGGER_NAME = "worker-standalone"; 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 new file mode 100644 index 000000000..402980a57 --- /dev/null +++ b/packages/core/src/topologies/partition/mod.ts @@ -0,0 +1 @@ +export { PartitionTopologyManager, PartitionTopologyWorker } from "./topology"; diff --git a/packages/actor/src/topologies/partition/topology.ts b/packages/core/src/topologies/partition/topology.ts similarity index 60% rename from packages/actor/src/topologies/partition/topology.ts rename to packages/core/src/topologies/partition/topology.ts index 9c5427827..2365ca423 100644 --- a/packages/actor/src/topologies/partition/topology.ts +++ b/packages/core/src/topologies/partition/topology.ts @@ -1,14 +1,14 @@ import { Hono, HonoRequest } from "hono"; -import { createActorRouter } from "@/topologies/partition/actor-router"; -import type { AnyActorInstance } from "@/actor/instance"; -import * as errors from "@/actor/errors"; +import { createWorkerRouter } from "@/topologies/partition/worker-router"; +import type { AnyWorkerInstance } from "@/worker/instance"; +import * as errors from "@/worker/errors"; import { type AnyConn, generateConnId, generateConnToken, -} from "@/actor/connection"; +} from "@/worker/connection"; import { logger } from "./log"; -import { ActionContext } from "@/actor/action"; +import { ActionContext } from "@/worker/action"; import { CONN_DRIVER_GENERIC_HTTP, CONN_DRIVER_GENERIC_SSE, @@ -19,11 +19,11 @@ import { type GenericSseDriverState, type GenericWebSocketDriverState, } from "../common/generic-conn-driver"; -import type { ConnDriver } from "@/actor/driver"; -import type { ActorKey } from "@/common/utils"; +import type { ConnDriver } from "@/worker/driver"; +import type { WorkerKey } from "@/common/utils"; import type { DriverConfig } from "@/driver-helpers/config"; import type { AppConfig } from "@/app/config"; -import type { ActorInspectorConnection } from "@/inspector/actor"; +import type { WorkerInspectorConnection } from "@/inspector/worker"; import { createManagerRouter } from "@/manager/router"; import type { ManagerInspectorConnection } from "@/inspector/manager"; import type { @@ -34,28 +34,28 @@ import type { ConnectWebSocketOutput, ConnectSseOutput, ActionOutput, -} from "@/actor/router-endpoints"; +} from "@/worker/router-endpoints"; import { ClientDriver } from "@/client/client"; -import { ToServer } from "@/actor/protocol/message/to-server"; -import { ActorQuery } from "@/manager/protocol/query"; +import { ToServer } from "@/worker/protocol/message/to-server"; +import { WorkerQuery } from "@/manager/protocol/query"; import { Encoding } from "@/mod"; import { EventSource } from "eventsource"; import { createInlineClientDriver } from "@/app/inline-client-driver"; import { ConnRoutingHandler, ConnRoutingHandlerCustom, -} from "@/actor/conn-routing-handler"; +} from "@/worker/conn-routing-handler"; import invariant from "invariant"; export type SendRequestHandler = ( - actorRequest: Request, - actorId: string, + workerRequest: Request, + workerId: string, meta?: unknown, ) => Promise; export type OpenWebSocketHandler = ( path: string, - actorId: string, + workerId: string, meta?: unknown, ) => Promise; @@ -106,24 +106,24 @@ export class PartitionTopologyManager { } } -/** Manages the actor in the topology. */ -export class PartitionTopologyActor { +/** Manages the worker in the topology. */ +export class PartitionTopologyWorker { router: Hono; #appConfig: AppConfig; #driverConfig: DriverConfig; #connDrivers: Record; - #actor?: AnyActorInstance; + #worker?: AnyWorkerInstance; - get actor(): AnyActorInstance { - if (!this.#actor) throw new Error("Actor not loaded"); - return this.#actor; + get worker(): AnyWorkerInstance { + if (!this.#worker) throw new Error("Worker not loaded"); + return this.#worker; } /** - * 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. + * 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. **/ - #actorStartedPromise?: PromiseWithResolvers = Promise.withResolvers(); + #workerStartedPromise?: PromiseWithResolvers = Promise.withResolvers(); constructor(appConfig: AppConfig, driverConfig: DriverConfig) { this.#appConfig = appConfig; @@ -132,25 +132,25 @@ export class PartitionTopologyActor { const genericConnGlobalState = new GenericConnGlobalState(); this.#connDrivers = createGenericConnDrivers(genericConnGlobalState); - // TODO: Store this actor router globally so we're not re-initializing it for every DO - this.router = createActorRouter(appConfig, driverConfig, { - getActorId: async () => { - if (this.#actorStartedPromise) await this.#actorStartedPromise.promise; - return this.actor.id; + // TODO: Store this worker router globally so we're not re-initializing it for every DO + this.router = createWorkerRouter(appConfig, driverConfig, { + getWorkerId: async () => { + if (this.#workerStartedPromise) await this.#workerStartedPromise.promise; + return this.worker.id; }, connectionHandlers: { onConnectWebSocket: async ( opts: ConnectWebSocketOpts, ): Promise => { - if (this.#actorStartedPromise) - await this.#actorStartedPromise.promise; + if (this.#workerStartedPromise) + await this.#workerStartedPromise.promise; - const actor = this.#actor; - if (!actor) throw new Error("Actor should be defined"); + const worker = this.#worker; + if (!worker) throw new Error("Worker should be defined"); const connId = generateConnId(); const connToken = generateConnToken(); - const connState = await actor.prepareConn(opts.params, opts.req?.raw); + const connState = await worker.prepareConn(opts.params, opts.req?.raw); let conn: AnyConn | undefined; return { @@ -159,7 +159,7 @@ export class PartitionTopologyActor { genericConnGlobalState.websockets.set(connId, ws); // Create connection - conn = await actor.createConn( + conn = await worker.createConn( connId, connToken, opts.params, @@ -178,13 +178,13 @@ export class PartitionTopologyActor { return; } - await actor.processMessage(message, conn); + await worker.processMessage(message, conn); }, onClose: async () => { genericConnGlobalState.websockets.delete(connId); if (conn) { - actor.__removeConn(conn); + worker.__removeConn(conn); } }, }; @@ -192,15 +192,15 @@ export class PartitionTopologyActor { onConnectSse: async ( opts: ConnectSseOpts, ): Promise => { - if (this.#actorStartedPromise) - await this.#actorStartedPromise.promise; + if (this.#workerStartedPromise) + await this.#workerStartedPromise.promise; - const actor = this.#actor; - if (!actor) throw new Error("Actor should be defined"); + const worker = this.#worker; + if (!worker) throw new Error("Worker should be defined"); const connId = generateConnId(); const connToken = generateConnToken(); - const connState = await actor.prepareConn(opts.params, opts.req?.raw); + const connState = await worker.prepareConn(opts.params, opts.req?.raw); let conn: AnyConn | undefined; return { @@ -209,7 +209,7 @@ export class PartitionTopologyActor { genericConnGlobalState.sseStreams.set(connId, stream); // Create connection - conn = await actor.createConn( + conn = await worker.createConn( connId, connToken, opts.params, @@ -222,7 +222,7 @@ export class PartitionTopologyActor { genericConnGlobalState.sseStreams.delete(connId); if (conn) { - actor.__removeConn(conn); + worker.__removeConn(conn); } }, }; @@ -231,18 +231,18 @@ export class PartitionTopologyActor { let conn: AnyConn | undefined; try { // Wait for init to finish - if (this.#actorStartedPromise) - await this.#actorStartedPromise.promise; + if (this.#workerStartedPromise) + await this.#workerStartedPromise.promise; - const actor = this.#actor; - if (!actor) throw new Error("Actor should be defined"); + const worker = this.#worker; + if (!worker) throw new Error("Worker should be defined"); // Create conn - const connState = await actor.prepareConn( + const connState = await worker.prepareConn( opts.params, opts.req?.raw, ); - conn = await actor.createConn( + conn = await worker.createConn( generateConnId(), generateConnToken(), opts.params, @@ -252,8 +252,8 @@ export class PartitionTopologyActor { ); // Call action - const ctx = new ActionContext(actor.actorContext!, conn!); - const output = await actor.executeAction( + const ctx = new ActionContext(worker.workerContext!, conn!); + const output = await worker.executeAction( ctx, opts.actionName, opts.actionArgs, @@ -262,20 +262,20 @@ export class PartitionTopologyActor { return { output }; } finally { if (conn) { - this.#actor?.__removeConn(conn); + this.#worker?.__removeConn(conn); } } }, onConnMessage: async (opts: ConnsMessageOpts): Promise => { // Wait for init to finish - if (this.#actorStartedPromise) - await this.#actorStartedPromise.promise; + if (this.#workerStartedPromise) + await this.#workerStartedPromise.promise; - const actor = this.#actor; - if (!actor) throw new Error("Actor should be defined"); + const worker = this.#worker; + if (!worker) throw new Error("Worker should be defined"); // Find connection - const conn = actor.conns.get(opts.connId); + const conn = worker.conns.get(opts.connId); if (!conn) { throw new errors.ConnNotFound(opts.connId); } @@ -286,19 +286,19 @@ export class PartitionTopologyActor { } // Process message - await actor.processMessage(opts.message, conn); + await worker.processMessage(opts.message, conn); }, }, onConnectInspector: async () => { - if (this.#actorStartedPromise) await this.#actorStartedPromise.promise; + if (this.#workerStartedPromise) await this.#workerStartedPromise.promise; - const actor = this.#actor; - if (!actor) throw new Error("Actor should be defined"); + const worker = this.#worker; + if (!worker) throw new Error("Worker should be defined"); - let conn: ActorInspectorConnection | undefined; + let conn: WorkerInspectorConnection | undefined; return { onOpen: async (ws) => { - conn = actor.inspector.createConnection(ws); + conn = worker.inspector.createConnection(ws); }, onMessage: async (message) => { if (!conn) { @@ -306,11 +306,11 @@ export class PartitionTopologyActor { return; } - actor.inspector.processMessage(conn, message); + worker.inspector.processMessage(conn, message); }, onClose: async () => { if (conn) { - actor.inspector.removeConnection(conn); + worker.inspector.removeConnection(conn); } }, }; @@ -318,30 +318,30 @@ export class PartitionTopologyActor { }); } - async start(id: string, name: string, key: ActorKey, region: string) { - const actorDriver = this.#driverConfig.drivers?.actor; - if (!actorDriver) throw new Error("config.drivers.actor not defined."); + async start(id: string, name: string, key: WorkerKey, region: string) { + const workerDriver = this.#driverConfig.drivers?.worker; + if (!workerDriver) throw new Error("config.drivers.worker not defined."); - // Find actor prototype - const definition = this.#appConfig.actors[name]; + // Find worker prototype + const definition = this.#appConfig.workers[name]; // TODO: Handle error here gracefully somehow if (!definition) - throw new Error(`no actor in registry for name ${definition}`); + throw new Error(`no worker in registry for name ${definition}`); - // Create actor - this.#actor = definition.instantiate(); + // Create worker + this.#worker = definition.instantiate(); - // Start actor - await this.#actor.start( + // Start worker + await this.#worker.start( this.#connDrivers, - actorDriver, + workerDriver, id, name, key, region, ); - this.#actorStartedPromise?.resolve(); - this.#actorStartedPromise = undefined; + this.#workerStartedPromise?.resolve(); + this.#workerStartedPromise = undefined; } } diff --git a/packages/actor/src/topologies/partition/actor-router.ts b/packages/core/src/topologies/partition/worker-router.ts similarity index 82% rename from packages/actor/src/topologies/partition/actor-router.ts rename to packages/core/src/topologies/partition/worker-router.ts index 3502b9170..501f1b98e 100644 --- a/packages/actor/src/topologies/partition/actor-router.ts +++ b/packages/core/src/topologies/partition/worker-router.ts @@ -9,9 +9,9 @@ import { import type { DriverConfig } from "@/driver-helpers/config"; import type { AppConfig } from "@/app/config"; import { - type ActorInspectorConnHandler, - createActorInspectorRouter, -} from "@/inspector/actor"; + type WorkerInspectorConnHandler, + createWorkerInspectorRouter, +} from "@/inspector/worker"; import { type ConnectWebSocketOpts, type ConnectWebSocketOutput, @@ -28,7 +28,7 @@ import { HEADER_CONN_TOKEN, HEADER_CONN_ID, ALL_HEADERS, -} from "@/actor/router-endpoints"; +} from "@/worker/router-endpoints"; export type { ConnectWebSocketOpts, @@ -40,22 +40,22 @@ export type { ConnsMessageOpts, }; -export interface ActorRouterHandler { - getActorId: () => Promise; +export interface WorkerRouterHandler { + getWorkerId: () => Promise; // Connection handlers as a required subobject connectionHandlers: ConnectionHandlers; - onConnectInspector?: ActorInspectorConnHandler; + onConnectInspector?: WorkerInspectorConnHandler; } /** * Creates a router that runs on the partitioned instance. */ -export function createActorRouter( +export function createWorkerRouter( appConfig: AppConfig, driverConfig: DriverConfig, - handler: ActorRouterHandler, + handler: WorkerRouterHandler, ): Hono { const app = new Hono(); @@ -65,7 +65,7 @@ export function createActorRouter( // Apply CORS middleware if configured // - //This is only relevant if the actor is exposed directly publicly + //This is only relevant if the worker is exposed directly publicly if (appConfig.cors) { const corsConfig = appConfig.cors; @@ -86,7 +86,7 @@ export function createActorRouter( app.get("/", (c) => { return c.text( - "This is an ActorCore server.\n\nLearn more at https://actorcore.org", + "This is an WorkerCore server.\n\nLearn more at https://workercore.org", ); }); @@ -101,13 +101,13 @@ export function createActorRouter( app.get( "/connect/websocket", upgradeWebSocket(async (c) => { - const actorId = await handler.getActorId(); + const workerId = await handler.getWorkerId(); return handleWebSocketConnect( c as HonoContext, appConfig, driverConfig, handlers.onConnectWebSocket!, - actorId, + workerId, )(); }), ); @@ -124,13 +124,13 @@ export function createActorRouter( if (!handlers.onConnectSse) { throw new Error("onConnectSse handler is required"); } - const actorId = await handler.getActorId(); + const workerId = await handler.getWorkerId(); return handleSseConnect( c, appConfig, driverConfig, handlers.onConnectSse, - actorId, + workerId, ); }); @@ -139,14 +139,14 @@ export function createActorRouter( throw new Error("onAction handler is required"); } const actionName = c.req.param("action"); - const actorId = await handler.getActorId(); + const workerId = await handler.getWorkerId(); return handleAction( c, appConfig, driverConfig, handlers.onAction, actionName, - actorId, + workerId, ); }); @@ -156,7 +156,7 @@ export function createActorRouter( } const connId = c.req.header(HEADER_CONN_ID); const connToken = c.req.header(HEADER_CONN_TOKEN); - const actorId = await handler.getActorId(); + const workerId = await handler.getWorkerId(); if (!connId || !connToken) { throw new Error("Missing required parameters"); } @@ -166,14 +166,14 @@ export function createActorRouter( handlers.onConnMessage, connId, connToken, - actorId, + workerId, ); }); if (appConfig.inspector.enabled) { app.route( "/inspect", - createActorInspectorRouter( + createWorkerInspectorRouter( upgradeWebSocket, handler.onConnectInspector, appConfig.inspector, diff --git a/packages/actor/src/topologies/standalone/log.ts b/packages/core/src/topologies/standalone/log.ts similarity index 68% rename from packages/actor/src/topologies/standalone/log.ts rename to packages/core/src/topologies/standalone/log.ts index cc1b03491..e150bb4f6 100644 --- a/packages/actor/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 = "actor-standalone"; +export const LOGGER_NAME = "worker-standalone"; export function logger() { return getLogger(LOGGER_NAME); diff --git a/packages/actor/src/topologies/standalone/mod.ts b/packages/core/src/topologies/standalone/mod.ts similarity index 100% rename from packages/actor/src/topologies/standalone/mod.ts rename to packages/core/src/topologies/standalone/mod.ts diff --git a/packages/actor/src/topologies/standalone/topology.ts b/packages/core/src/topologies/standalone/topology.ts similarity index 63% rename from packages/actor/src/topologies/standalone/topology.ts rename to packages/core/src/topologies/standalone/topology.ts index 6b7308176..f0b856f26 100644 --- a/packages/actor/src/topologies/standalone/topology.ts +++ b/packages/core/src/topologies/standalone/topology.ts @@ -1,12 +1,12 @@ -import type { AnyActorInstance } from "@/actor/instance"; +import type { AnyWorkerInstance } from "@/worker/instance"; import { Hono } from "hono"; import { type AnyConn, generateConnId, generateConnToken, -} from "@/actor/connection"; +} from "@/worker/connection"; import { logger } from "./log"; -import * as errors from "@/actor/errors"; +import * as errors from "@/worker/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 "@/actor/action"; +import { ActionContext } from "@/worker/action"; import type { DriverConfig } from "@/driver-helpers/config"; import type { AppConfig } from "@/app/config"; import { createManagerRouter } from "@/manager/router"; @@ -31,25 +31,25 @@ import type { ActionOpts, ActionOutput, ConnectionHandlers, -} from "@/actor/router-endpoints"; +} from "@/worker/router-endpoints"; import { createInlineClientDriver } from "@/app/inline-client-driver"; import invariant from "invariant"; import { ClientDriver } from "@/client/client"; -import { ConnRoutingHandler } from "@/actor/conn-routing-handler"; +import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; -class ActorHandler { +class WorkerHandler { /** Will be undefined if not yet loaded. */ - actor?: AnyActorInstance; + worker?: AnyWorkerInstance; - /** Promise that will resolve when the actor is loaded. This should always be awaited before accessing the actor. */ - actorPromise?: PromiseWithResolvers = Promise.withResolvers(); + /** Promise that will resolve when the worker is loaded. This should always be awaited before accessing the worker. */ + workerPromise?: PromiseWithResolvers = Promise.withResolvers(); genericConnGlobalState = new GenericConnGlobalState(); } /** * Standalone topology implementation. - * Manages actors in a single instance without distributed coordination. + * Manages workers in a single instance without distributed coordination. */ export class StandaloneTopology { clientDriver: ClientDriver; @@ -57,91 +57,91 @@ export class StandaloneTopology { #appConfig: AppConfig; #driverConfig: DriverConfig; - #actors = new Map(); + #workers = new Map(); - async #getActor( - actorId: string, - ): Promise<{ handler: ActorHandler; actor: AnyActorInstance }> { - // Load existing actor - let handler = this.#actors.get(actorId); + async #getWorker( + workerId: string, + ): Promise<{ handler: WorkerHandler; worker: AnyWorkerInstance }> { + // Load existing worker + let handler = this.#workers.get(workerId); if (handler) { - if (handler.actorPromise) await handler.actorPromise.promise; - if (!handler.actor) throw new Error("Actor should be loaded"); - return { handler, actor: handler.actor }; + if (handler.workerPromise) await handler.workerPromise.promise; + if (!handler.worker) throw new Error("Worker should be loaded"); + return { handler, worker: handler.worker }; } - // Create new actor - logger().debug("creating new actor", { actorId }); + // Create new worker + logger().debug("creating new worker", { workerId }); - // Insert unloaded placeholder in order to prevent race conditions with multiple insertions of the actor - handler = new ActorHandler(); - this.#actors.set(actorId, handler); + // Insert unloaded placeholder in order to prevent race conditions with multiple insertions of the worker + handler = new WorkerHandler(); + this.#workers.set(workerId, handler); // Validate config - if (!this.#driverConfig.drivers?.actor) - throw new Error("config.drivers.actor is not defined."); + if (!this.#driverConfig.drivers?.worker) + throw new Error("config.drivers.worker is not defined."); if (!this.#driverConfig.drivers?.manager) throw new Error("config.drivers.manager is not defined."); - // Load actor meta - const actorMetadata = await this.#driverConfig.drivers.manager.getForId({ - actorId, + // Load worker meta + const workerMetadata = await this.#driverConfig.drivers.manager.getForId({ + workerId, }); - if (!actorMetadata) throw new Error(`No actor found for ID ${actorId}`); + if (!workerMetadata) throw new Error(`No worker found for ID ${workerId}`); - // Build actor - const definition = this.#appConfig.actors[actorMetadata.name]; + // Build worker + const definition = this.#appConfig.workers[workerMetadata.name]; if (!definition) - throw new Error(`no actor in registry for name ${definition}`); + throw new Error(`no worker in registry for name ${definition}`); - // Create leader actor - const actor = definition.instantiate(); - handler.actor = actor; + // Create leader worker + const worker = definition.instantiate(); + handler.worker = worker; // Create connection drivers const connDrivers = createGenericConnDrivers( handler.genericConnGlobalState, ); - // Start actor - await handler.actor.start( + // Start worker + await handler.worker.start( connDrivers, - this.#driverConfig.drivers.actor, - actorId, - actorMetadata.name, - actorMetadata.key, + this.#driverConfig.drivers.worker, + workerId, + workerMetadata.name, + workerMetadata.key, "unknown", ); // Finish - handler.actorPromise?.resolve(); - handler.actorPromise = undefined; + handler.workerPromise?.resolve(); + handler.workerPromise = undefined; - return { handler, actor }; + return { handler, worker }; } constructor(appConfig: AppConfig, driverConfig: DriverConfig) { this.#appConfig = appConfig; this.#driverConfig = driverConfig; - if (!driverConfig.drivers?.actor) - throw new Error("config.drivers.actor not defined."); + if (!driverConfig.drivers?.worker) + throw new Error("config.drivers.worker not defined."); // Build router const app = new Hono(); const upgradeWebSocket = driverConfig.getUpgradeWebSocket?.(app); - // Create shared connection handlers that will be used by both manager and actor routers + // Create shared connection handlers that will be used by both manager and worker routers const sharedConnectionHandlers: ConnectionHandlers = { onConnectWebSocket: async ( opts: ConnectWebSocketOpts, ): Promise => { - const { handler, actor } = await this.#getActor(opts.actorId); + const { handler, worker } = await this.#getWorker(opts.workerId); const connId = generateConnId(); const connToken = generateConnToken(); - const connState = await actor.prepareConn(opts.params, opts.req?.raw); + const connState = await worker.prepareConn(opts.params, opts.req?.raw); let conn: AnyConn | undefined; return { @@ -150,7 +150,7 @@ export class StandaloneTopology { handler.genericConnGlobalState.websockets.set(connId, ws); // Create connection - conn = await actor.createConn( + conn = await worker.createConn( connId, connToken, opts.params, @@ -167,23 +167,23 @@ export class StandaloneTopology { return; } - await actor.processMessage(message, conn); + await worker.processMessage(message, conn); }, onClose: async () => { handler.genericConnGlobalState.websockets.delete(connId); if (conn) { - actor.__removeConn(conn); + worker.__removeConn(conn); } }, }; }, onConnectSse: async (opts: ConnectSseOpts): Promise => { - const { handler, actor } = await this.#getActor(opts.actorId); + const { handler, worker } = await this.#getWorker(opts.workerId); const connId = generateConnId(); const connToken = generateConnToken(); - const connState = await actor.prepareConn(opts.params, opts.req?.raw); + const connState = await worker.prepareConn(opts.params, opts.req?.raw); let conn: AnyConn | undefined; return { @@ -192,7 +192,7 @@ export class StandaloneTopology { handler.genericConnGlobalState.sseStreams.set(connId, stream); // Create connection - conn = await actor.createConn( + conn = await worker.createConn( connId, connToken, opts.params, @@ -205,7 +205,7 @@ export class StandaloneTopology { handler.genericConnGlobalState.sseStreams.delete(connId); if (conn) { - actor.__removeConn(conn); + worker.__removeConn(conn); } }, }; @@ -213,11 +213,11 @@ export class StandaloneTopology { onAction: async (opts: ActionOpts): Promise => { let conn: AnyConn | undefined; try { - const { actor } = await this.#getActor(opts.actorId); + const { worker } = await this.#getWorker(opts.workerId); // Create conn - const connState = await actor.prepareConn(opts.params, opts.req?.raw); - conn = await actor.createConn( + const connState = await worker.prepareConn(opts.params, opts.req?.raw); + conn = await worker.createConn( generateConnId(), generateConnToken(), opts.params, @@ -227,8 +227,8 @@ export class StandaloneTopology { ); // Call action - const ctx = new ActionContext(actor.actorContext!, conn); - const output = await actor.executeAction( + const ctx = new ActionContext(worker.workerContext!, conn); + const output = await worker.executeAction( ctx, opts.actionName, opts.actionArgs, @@ -237,16 +237,16 @@ export class StandaloneTopology { return { output }; } finally { if (conn) { - const { actor } = await this.#getActor(opts.actorId); - actor.__removeConn(conn); + const { worker } = await this.#getWorker(opts.workerId); + worker.__removeConn(conn); } } }, onConnMessage: async (opts: ConnsMessageOpts): Promise => { - const { actor } = await this.#getActor(opts.actorId); + const { worker } = await this.#getWorker(opts.workerId); // Find connection - const conn = actor.conns.get(opts.connId); + const conn = worker.conns.get(opts.connId); if (!conn) { throw new errors.ConnNotFound(opts.connId); } @@ -257,7 +257,7 @@ export class StandaloneTopology { } // Process message - await actor.processMessage(opts.message, conn); + await worker.processMessage(opts.message, conn); }, }; diff --git a/packages/actor/src/utils.ts b/packages/core/src/utils.ts similarity index 93% rename from packages/actor/src/utils.ts rename to packages/core/src/utils.ts index 023b4d2cb..4fd61cbce 100644 --- a/packages/actor/src/utils.ts +++ b/packages/core/src/utils.ts @@ -14,7 +14,7 @@ export function httpUserAgent(): string { } // Library - let userAgent = `ActorCore/${VERSION}`; + let userAgent = `WorkerCore/${VERSION}`; // Navigator const navigatorObj = typeof navigator !== "undefined" ? navigator : undefined; diff --git a/packages/actor/src/actor/action.ts b/packages/core/src/worker/action.ts similarity index 55% rename from packages/actor/src/actor/action.ts rename to packages/core/src/worker/action.ts index ff530b741..b302421f8 100644 --- a/packages/actor/src/actor/action.ts +++ b/packages/core/src/worker/action.ts @@ -1,115 +1,115 @@ -import type { AnyActorInstance } from "./instance"; +import type { AnyWorkerInstance } from "./instance"; import type { Conn } from "./connection"; import type { Logger } from "@/common/log"; -import type { ActorKey } from "@/common/utils"; +import type { WorkerKey } from "@/common/utils"; import type { Schedule } from "./schedule"; import type { ConnId } from "./connection"; import type { SaveStateOptions } from "./instance"; import { Actions } from "./config"; -import { ActorContext } from "./context"; +import { WorkerContext } from "./context"; /** * Context for a remote procedure call. * - * @typeParam A Actor this action belongs to + * @typeParam A Worker this action belongs to */ export class ActionContext { - #actorContext: ActorContext; + #workerContext: WorkerContext; /** * Should not be called directly. * - * @param actorContext - The actor context + * @param workerContext - The worker context * @param conn - The connection associated with the action */ constructor( - actorContext: ActorContext, + workerContext: WorkerContext, public readonly conn: Conn, ) { - this.#actorContext = actorContext; + this.#workerContext = workerContext; } /** - * Get the actor state + * Get the worker state */ get state(): S { - return this.#actorContext.state; + return this.#workerContext.state; } /** - * Get the actor variables + * Get the worker variables */ get vars(): V { - return this.#actorContext.vars; + return this.#workerContext.vars; } /** * Broadcasts an event to all connected clients. */ broadcast(name: string, ...args: any[]): void { - this.#actorContext.broadcast(name, ...args); + this.#workerContext.broadcast(name, ...args); } /** * Gets the logger instance. */ get log(): Logger { - return this.#actorContext.log; + return this.#workerContext.log; } /** - * Gets actor ID. + * Gets worker ID. */ - get actorId(): string { - return this.#actorContext.actorId; + get workerId(): string { + return this.#workerContext.workerId; } /** - * Gets the actor name. + * Gets the worker name. */ get name(): string { - return this.#actorContext.name; + return this.#workerContext.name; } /** - * Gets the actor key. + * Gets the worker key. */ - get key(): ActorKey { - return this.#actorContext.key; + get key(): WorkerKey { + return this.#workerContext.key; } /** * Gets the region. */ get region(): string { - return this.#actorContext.region; + return this.#workerContext.region; } /** * Gets the scheduler. */ get schedule(): Schedule { - return this.#actorContext.schedule; + return this.#workerContext.schedule; } /** * Gets the map of connections. */ get conns(): Map> { - return this.#actorContext.conns; + return this.#workerContext.conns; } /** * Forces the state to get saved. */ async saveState(opts: SaveStateOptions): Promise { - return this.#actorContext.saveState(opts); + return this.#workerContext.saveState(opts); } /** * Runs a promise in the background. */ runInBackground(promise: Promise): void { - return this.#actorContext.runInBackground(promise); + return this.#workerContext.runInBackground(promise); } } diff --git a/packages/actor/src/actor/config.ts b/packages/core/src/worker/config.ts similarity index 78% rename from packages/actor/src/actor/config.ts rename to packages/core/src/worker/config.ts index 2b3be3579..6fbbc9455 100644 --- a/packages/actor/src/actor/config.ts +++ b/packages/core/src/worker/config.ts @@ -1,14 +1,14 @@ import type { Conn } from "./connection"; import type { ActionContext } from "./action"; -import type { ActorContext } from "./context"; +import type { WorkerContext } from "./context"; import { z } from "zod"; -// This schema is used to validate the input at runtime. The generic types are defined below in `ActorConfig`. +// This schema is used to validate the input at runtime. The generic types are defined below in `WorkerConfig`. // // 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 ActorConfigSchema = z +export const WorkerConfigSchema = z .object({ onCreate: z.function().optional(), onStart: z.function().optional(), @@ -99,7 +99,7 @@ type CreateState = | { state: S } | { createState: ( - c: ActorContext, + c: WorkerContext, opts: CreateStateOptions, ) => S | Promise; } @@ -112,7 +112,7 @@ type CreateConnState = | { connState: CS } | { createConnState: ( - c: ActorContext, + c: WorkerContext, opts: OnConnectOptions, ) => CS | Promise; } @@ -136,7 +136,7 @@ type CreateVars = * @experimental */ createVars: ( - c: ActorContext, + c: WorkerContext, driverCtx: unknown, ) => V | Promise; } @@ -146,45 +146,45 @@ export interface Actions { [Action: string]: (c: ActionContext, ...args: any[]) => any; } -//export type ActorConfig = BaseActorConfig & -// ActorConfigLifecycle & +//export type WorkerConfig = BaseWorkerConfig & +// WorkerConfigLifecycle & // CreateState & // CreateConnState; -interface BaseActorConfig> { +interface BaseWorkerConfig> { /** - * Called when the actor is first initialized. + * Called when the worker is first initialized. * - * Use this hook to initialize your actor's state. + * Use this hook to initialize your worker's state. * This is called before any other lifecycle hooks. */ onCreate?: ( - c: ActorContext, + c: WorkerContext, opts: OnCreateOptions, ) => void | Promise; /** - * Called when the actor is started and ready to receive connections and action. + * Called when the worker is started and ready to receive connections and action. * - * Use this hook to initialize resources needed for the actor's operation + * Use this hook to initialize resources needed for the worker's operation * (timers, external connections, etc.) * * @returns Void or a Promise that resolves when startup is complete */ - onStart?: (c: ActorContext) => void | Promise; + onStart?: (c: WorkerContext) => void | Promise; /** - * Called when the actor's state changes. + * Called when the worker's state changes. * * Use this hook to react to state changes, such as updating * external systems or triggering events. * * @param newState The updated state */ - onStateChange?: (c: ActorContext, newState: S) => void; + onStateChange?: (c: WorkerContext, newState: S) => void; /** - * Called before a client connects to the actor. + * Called before a client connects to the worker. * * Use this hook to determine if a connection should be accepted * and to initialize connection-specific state. @@ -194,35 +194,35 @@ interface BaseActorConfig> { * @throws Throw an error to reject the connection */ onBeforeConnect?: ( - c: ActorContext, + c: WorkerContext, opts: OnConnectOptions, ) => void | Promise; /** - * Called when a client successfully connects to the actor. + * Called when a client successfully connects to the worker. * * Use this hook to perform actions when a connection is established, - * such as sending initial data or updating the actor's state. + * such as sending initial data or updating the worker's state. * * @param conn The connection object * @returns Void or a Promise that resolves when connection handling is complete */ onConnect?: ( - c: ActorContext, + c: WorkerContext, conn: Conn, ) => void | Promise; /** - * Called when a client disconnects from the actor. + * Called when a client disconnects from the worker. * * Use this hook to clean up resources associated with the connection - * or update the actor's state. + * or update the worker's state. * * @param conn The connection that is being closed * @returns Void or a Promise that resolves when disconnect handling is complete */ onDisconnect?: ( - c: ActorContext, + c: WorkerContext, conn: Conn, ) => void | Promise; @@ -239,7 +239,7 @@ interface BaseActorConfig> { * @returns The modified output to send to the client */ onBeforeActionResponse?: ( - c: ActorContext, + c: WorkerContext, name: string, args: unknown[], output: Out, @@ -251,8 +251,8 @@ interface BaseActorConfig> { // 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 ActorConfig = Omit< - z.infer, +export type WorkerConfig = Omit< + z.infer, | "actions" | "onCreate" | "onStart" @@ -268,20 +268,20 @@ export type ActorConfig = Omit< | "vars" | "createVars" > & - BaseActorConfig> & + BaseWorkerConfig> & CreateState & CreateConnState & CreateVars; -// See description on `ActorConfig` -export type ActorConfigInput< +// See description on `WorkerConfig` +export type WorkerConfigInput< S, CP, CS, V, R extends Actions, > = Omit< - z.input, + z.input, | "actions" | "onCreate" | "onStart" @@ -297,20 +297,20 @@ export type ActorConfigInput< | "vars" | "createVars" > & - BaseActorConfig & + BaseWorkerConfig & CreateState & CreateConnState & CreateVars; // For testing type definitions: export function test>( - input: ActorConfigInput, -): ActorConfig { - const config = ActorConfigSchema.parse(input) as ActorConfig; + input: WorkerConfigInput, +): WorkerConfig { + const config = WorkerConfigSchema.parse(input) as WorkerConfig; return config; } -export const testActor = test({ +export const testWorker = test({ state: { count: 0 }, // createState: () => ({ count: 0 }), actions: { diff --git a/packages/actor/src/actor/conn-routing-handler.ts b/packages/core/src/worker/conn-routing-handler.ts similarity index 80% rename from packages/actor/src/actor/conn-routing-handler.ts rename to packages/core/src/worker/conn-routing-handler.ts index 7b5eb3b7d..041d1f455 100644 --- a/packages/actor/src/actor/conn-routing-handler.ts +++ b/packages/core/src/worker/conn-routing-handler.ts @@ -2,7 +2,7 @@ import type { ConnectionHandlers as ConnHandlers } from "./router-endpoints"; import type { Context as HonoContext, HonoRequest } from "hono"; /** - * Deterines how requests to actors should be routed. + * Deterines how requests to workers should be routed. * * Inline handlers calls the connection handlers directly. * @@ -25,29 +25,29 @@ export interface ConnRoutingHandlerCustom { proxyWebSocket: ProxyWebSocketHandler; } -export type BuildProxyEndpoint = (c: HonoContext, actorId: string) => string; +export type BuildProxyEndpoint = (c: HonoContext, workerId: string) => string; export type SendRequestHandler = ( - actorId: string, + workerId: string, meta: unknown | undefined, - actorRequest: Request, + workerRequest: Request, ) => Promise; export type OpenWebSocketHandler = ( - actorId: string, + workerId: string, meta?: unknown, ) => Promise; export type ProxyRequestHandler = ( c: HonoContext, - actorRequest: Request, - actorId: string, + workerRequest: Request, + workerId: string, meta?: unknown, ) => Promise; export type ProxyWebSocketHandler = ( c: HonoContext, path: string, - actorId: string, + workerId: string, meta?: unknown, ) => Promise; diff --git a/packages/actor/src/actor/connection.ts b/packages/core/src/worker/connection.ts similarity index 84% rename from packages/actor/src/actor/connection.ts rename to packages/core/src/worker/connection.ts index 4b442af7c..b4f2418e3 100644 --- a/packages/actor/src/actor/connection.ts +++ b/packages/core/src/worker/connection.ts @@ -1,11 +1,11 @@ -import type { ActorInstance } from "./instance"; +import type { WorkerInstance } from "./instance"; import * as errors from "./errors"; import { generateSecureToken } from "./utils"; import { CachedSerializer } from "./protocol/serde"; import type { ConnDriver } from "./driver"; -import * as messageToClient from "@/actor/protocol/message/to-client"; +import * as messageToClient from "@/worker/protocol/message/to-client"; import type { PersistedConn } from "./persisted"; -import * as wsToClient from "@/actor/protocol/message/to-client"; +import * as wsToClient from "@/worker/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 an actor. + * Represents a client connection to a worker. * * Manages connection-specific data and controls the connection lifecycle. * @@ -32,7 +32,7 @@ export class Conn { #stateEnabled: boolean; // TODO: Remove this cyclical reference - #actor: ActorInstance; + #worker: WorkerInstance; /** * The proxied state that notifies of changes automatically. @@ -94,17 +94,17 @@ export class Conn { /** * Initializes a new instance of the Connection class. * - * This should only be constructed by {@link Actor}. + * This should only be constructed by {@link Worker}. * * @protected */ public constructor( - actor: ActorInstance, + worker: WorkerInstance, persist: PersistedConn, driver: ConnDriver, stateEnabled: boolean, ) { - this.#actor = actor; + this.#worker = worker; this.__persist = persist; this.#driver = driver; this.#stateEnabled = stateEnabled; @@ -124,7 +124,7 @@ export class Conn { * @protected */ public _sendMessage(message: CachedSerializer) { - this.#driver.sendMessage?.(this.#actor, this, this.__persist.ds, message); + this.#driver.sendMessage?.(this.#worker, this, this.__persist.ds, message); } /** @@ -153,6 +153,6 @@ export class Conn { * @param reason - The reason for disconnection. */ public async disconnect(reason?: string) { - await this.#driver.disconnect(this.#actor, this, this.__persist.ds, reason); + await this.#driver.disconnect(this.#worker, this, this.__persist.ds, reason); } } diff --git a/packages/actor/src/actor/context.ts b/packages/core/src/worker/context.ts similarity index 56% rename from packages/actor/src/actor/context.ts rename to packages/core/src/worker/context.ts index 7b92fea6f..3cda79159 100644 --- a/packages/actor/src/actor/context.ts +++ b/packages/core/src/worker/context.ts @@ -1,32 +1,32 @@ import { Logger } from "@/common/log"; import { Actions } from "./config"; -import { ActorInstance, SaveStateOptions } from "./instance"; +import { WorkerInstance, SaveStateOptions } from "./instance"; import { Conn, ConnId } from "./connection"; -import { ActorKey } from "@/common/utils"; +import { WorkerKey } from "@/common/utils"; import { Schedule } from "./schedule"; /** - * ActorContext class that provides access to actor methods and state + * WorkerContext class that provides access to worker methods and state */ -export class ActorContext { - #actor: ActorInstance; +export class WorkerContext { + #worker: WorkerInstance; - constructor(actor: ActorInstance) { - this.#actor = actor; + constructor(worker: WorkerInstance) { + this.#worker = worker; } /** - * Get the actor state + * Get the worker state */ get state(): S { - return this.#actor.state; + return this.#worker.state; } /** - * Get the actor variables + * Get the worker variables */ get vars(): V { - return this.#actor.vars; + return this.#worker.vars; } /** @@ -35,7 +35,7 @@ export class ActorContext { * @param args - The arguments to send with the event. */ broadcast>(name: string, ...args: Args): void { - this.#actor._broadcast(name, ...args); + this.#worker._broadcast(name, ...args); return; } @@ -43,49 +43,49 @@ export class ActorContext { * Gets the logger instance. */ get log(): Logger { - return this.#actor.log; + return this.#worker.log; } /** - * Gets actor ID. + * Gets worker ID. */ - get actorId(): string { - return this.#actor.id; + get workerId(): string { + return this.#worker.id; } /** - * Gets the actor name. + * Gets the worker name. */ get name(): string { - return this.#actor.name; + return this.#worker.name; } /** - * Gets the actor key. + * Gets the worker key. */ - get key(): ActorKey { - return this.#actor.key; + get key(): WorkerKey { + return this.#worker.key; } /** * Gets the region. */ get region(): string { - return this.#actor.region; + return this.#worker.region; } /** * Gets the scheduler. */ get schedule(): Schedule { - return this.#actor.schedule; + return this.#worker.schedule; } /** * Gets the map of connections. */ get conns(): Map> { - return this.#actor.conns; + return this.#worker.conns; } /** @@ -94,7 +94,7 @@ export class ActorContext { * @param opts - Options for saving the state. */ async saveState(opts: SaveStateOptions): Promise { - return this.#actor.saveState(opts); + return this.#worker.saveState(opts); } /** @@ -103,7 +103,7 @@ export class ActorContext { * @param promise - The promise to run in the background. */ runInBackground(promise: Promise): void { - this.#actor._runInBackground(promise); + this.#worker._runInBackground(promise); return; } } diff --git a/packages/core/src/worker/definition.ts b/packages/core/src/worker/definition.ts new file mode 100644 index 000000000..7c45c28a4 --- /dev/null +++ b/packages/core/src/worker/definition.ts @@ -0,0 +1,37 @@ +import { + type WorkerConfig, + type Actions, +} from "./config"; +import { WorkerInstance } from "./instance"; +import { WorkerContext } from "./context"; +import type { ActionContext } from "./action"; + +export type AnyWorkerDefinition = WorkerDefinition; + +/** + * Extracts the context type from an WorkerDefinition + */ +export type WorkerContextOf = + AD extends WorkerDefinition + ? WorkerContext + : never; + +/** + * Extracts the context type from an WorkerDefinition + */ +export type ActionContextOf = + AD extends WorkerDefinition + ? ActionContext + : never; + +export class WorkerDefinition> { + #config: WorkerConfig; + + constructor(config: WorkerConfig) { + this.#config = config; + } + + instantiate(): WorkerInstance { + return new WorkerInstance(this.#config); + } +} diff --git a/packages/actor/src/actor/driver.ts b/packages/core/src/worker/driver.ts similarity index 51% rename from packages/actor/src/actor/driver.ts rename to packages/core/src/worker/driver.ts index 9b444e60a..7eb2a184a 100644 --- a/packages/actor/src/actor/driver.ts +++ b/packages/core/src/worker/driver.ts @@ -1,21 +1,21 @@ -import type * as messageToClient from "@/actor/protocol/message/to-client"; -import type { CachedSerializer } from "@/actor/protocol/serde"; -import type { AnyActorInstance } from "./instance"; +import type * as messageToClient from "@/worker/protocol/message/to-client"; +import type { CachedSerializer } from "@/worker/protocol/serde"; +import type { AnyWorkerInstance } from "./instance"; import { AnyConn } from "./connection"; export type ConnDrivers = Record; -export interface ActorDriver { +export interface WorkerDriver { //load(): Promise; - getContext(actorId: string): unknown; + getContext(workerId: string): unknown; - readInput(actorId: string): Promise; + readInput(workerId: string): Promise; - readPersistedData(actorId: string): Promise; - writePersistedData(actorId: string, unknown: unknown): Promise; + readPersistedData(workerId: string): Promise; + writePersistedData(workerId: string, unknown: unknown): Promise; // Schedule - setAlarm(actor: AnyActorInstance, timestamp: number): Promise; + setAlarm(worker: AnyWorkerInstance, timestamp: number): Promise; // TODO: //destroy(): Promise; @@ -24,7 +24,7 @@ export interface ActorDriver { export interface ConnDriver { sendMessage?( - actor: AnyActorInstance, + worker: AnyWorkerInstance, conn: AnyConn, state: ConnDriverState, message: CachedSerializer, @@ -34,7 +34,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( - actor: AnyActorInstance, + worker: AnyWorkerInstance, conn: AnyConn, state: ConnDriverState, reason?: string, diff --git a/packages/actor/src/actor/errors.ts b/packages/core/src/worker/errors.ts similarity index 73% rename from packages/actor/src/actor/errors.ts rename to packages/core/src/worker/errors.ts index 4069cd448..176ae7e91 100644 --- a/packages/actor/src/actor/errors.ts +++ b/packages/core/src/worker/errors.ts @@ -1,34 +1,34 @@ export const INTERNAL_ERROR_CODE = "internal_error"; export const INTERNAL_ERROR_DESCRIPTION = - "Internal error. Read the actor logs for more details."; + "Internal error. Read the worker logs for more details."; export type InternalErrorMetadata = {}; export const USER_ERROR_CODE = "user_error"; -interface ActorErrorOptions extends ErrorOptions { +interface WorkerErrorOptions 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 ActorError extends Error { - __type = "ActorError"; +export class WorkerError extends Error { + __type = "WorkerError"; public public: boolean; public metadata?: unknown; public statusCode: number = 500; - public static isActorError(error: unknown): error is ActorError { + public static isWorkerError(error: unknown): error is WorkerError { return ( - typeof error === "object" && (error as ActorError).__type === "ActorError" + typeof error === "object" && (error as WorkerError).__type === "WorkerError" ); } constructor( public readonly code: string, message: string, - opts?: ActorErrorOptions, + opts?: WorkerErrorOptions, ) { super(message, { cause: opts?.cause }); this.public = opts?.public ?? false; @@ -57,7 +57,7 @@ export class ActorError extends Error { } } -export class InternalError extends ActorError { +export class InternalError extends WorkerError { constructor(message: string) { super(INTERNAL_ERROR_CODE, message); } @@ -69,7 +69,7 @@ export class Unreachable extends InternalError { } } -export class StateNotEnabled extends ActorError { +export class StateNotEnabled extends WorkerError { constructor() { super( "state_not_enabled", @@ -78,7 +78,7 @@ export class StateNotEnabled extends ActorError { } } -export class ConnStateNotEnabled extends ActorError { +export class ConnStateNotEnabled extends WorkerError { constructor() { super( "conn_state_not_enabled", @@ -87,7 +87,7 @@ export class ConnStateNotEnabled extends ActorError { } } -export class VarsNotEnabled extends ActorError { +export class VarsNotEnabled extends WorkerError { constructor() { super( "vars_not_enabled", @@ -96,19 +96,19 @@ export class VarsNotEnabled extends ActorError { } } -export class ActionTimedOut extends ActorError { +export class ActionTimedOut extends WorkerError { constructor() { super("action_timed_out", "Action timed out.", { public: true }); } } -export class ActionNotFound extends ActorError { +export class ActionNotFound extends WorkerError { constructor() { super("action_not_found", "Action not found.", { public: true }); } } -export class InvalidEncoding extends ActorError { +export class InvalidEncoding extends WorkerError { constructor(format?: string) { super("invalid_encoding", `Invalid encoding \`${format}\`.`, { public: true, @@ -116,7 +116,7 @@ export class InvalidEncoding extends ActorError { } } -export class ConnNotFound extends ActorError { +export class ConnNotFound extends WorkerError { constructor(id?: string) { super("conn_not_found", `Connection not found for ID \`${id}\`.`, { public: true, @@ -124,7 +124,7 @@ export class ConnNotFound extends ActorError { } } -export class IncorrectConnToken extends ActorError { +export class IncorrectConnToken extends WorkerError { constructor() { super("incorrect_conn_token", "Incorrect connection token.", { public: true, @@ -132,7 +132,7 @@ export class IncorrectConnToken extends ActorError { } } -export class ConnParamsTooLong extends ActorError { +export class ConnParamsTooLong extends WorkerError { constructor() { super("conn_params_too_long", "Connection parameters too long.", { public: true, @@ -140,7 +140,7 @@ export class ConnParamsTooLong extends ActorError { } } -export class MalformedConnParams extends ActorError { +export class MalformedConnParams extends WorkerError { constructor(cause: unknown) { super( "malformed_conn_params", @@ -150,13 +150,13 @@ export class MalformedConnParams extends ActorError { } } -export class MessageTooLong extends ActorError { +export class MessageTooLong extends WorkerError { constructor() { super("message_too_long", "Message too long.", { public: true }); } } -export class MalformedMessage extends ActorError { +export class MalformedMessage extends WorkerError { constructor(cause?: unknown) { super("malformed_message", `Malformed message: ${cause}`, { public: true, @@ -169,7 +169,7 @@ export interface InvalidStateTypeOptions { path?: unknown; } -export class InvalidStateType extends ActorError { +export class InvalidStateType extends WorkerError { constructor(opts?: InvalidStateTypeOptions) { let msg = ""; if (opts?.path) { @@ -182,13 +182,13 @@ export class InvalidStateType extends ActorError { } } -export class StateTooLarge extends ActorError { +export class StateTooLarge extends WorkerError { constructor() { super("state_too_large", "State too large."); } } -export class Unsupported extends ActorError { +export class Unsupported extends WorkerError { constructor(feature: string) { super("unsupported", `Unsupported feature: ${feature}`); } @@ -210,7 +210,7 @@ export interface UserErrorOptions extends ErrorOptions { } /** Error that can be safely returned to the user. */ -export class UserError extends ActorError { +export class UserError extends WorkerError { /** * Constructs a new UserError instance. * @@ -225,7 +225,7 @@ export class UserError extends ActorError { } } -export class InvalidQueryJSON extends ActorError { +export class InvalidQueryJSON extends WorkerError { constructor(error?: unknown) { super("invalid_query_json", `Invalid query JSON: ${error}`, { public: true, @@ -234,7 +234,7 @@ export class InvalidQueryJSON extends ActorError { } } -export class InvalidRequest extends ActorError { +export class InvalidRequest extends WorkerError { constructor(error?: unknown) { super("invalid_request", `Invalid request: ${error}`, { public: true, @@ -243,27 +243,27 @@ export class InvalidRequest extends ActorError { } } -export class ActorNotFound extends ActorError { +export class WorkerNotFound extends WorkerError { constructor(identifier?: string) { super( - "actor_not_found", - identifier ? `Actor not found: ${identifier}` : "Actor not found", + "worker_not_found", + identifier ? `Worker not found: ${identifier}` : "Worker not found", { public: true }, ); } } -export class ActorAlreadyExists extends ActorError { +export class WorkerAlreadyExists extends WorkerError { constructor(name: string, key: string[]) { super( - "actor_already_exists", - `Actor already exists with name "${name}" and key ${JSON.stringify(key)}`, + "worker_already_exists", + `Worker already exists with name "${name}" and key ${JSON.stringify(key)}`, { public: true }, ); } } -export class ProxyError extends ActorError { +export class ProxyError extends WorkerError { constructor(operation: string, error?: unknown) { super("proxy_error", `Error proxying ${operation}: ${error}`, { public: true, @@ -272,13 +272,13 @@ export class ProxyError extends ActorError { } } -export class InvalidActionRequest extends ActorError { +export class InvalidActionRequest extends WorkerError { constructor(message: string) { super("invalid_action_request", message, { public: true }); } } -export class InvalidParams extends ActorError { +export class InvalidParams extends WorkerError { constructor(message: string) { super("invalid_params", message, { public: true }); } diff --git a/packages/actor/src/actor/instance.ts b/packages/core/src/worker/instance.ts similarity index 86% rename from packages/actor/src/actor/instance.ts rename to packages/core/src/worker/instance.ts index a8e9d5dd0..8f4294644 100644 --- a/packages/actor/src/actor/instance.ts +++ b/packages/core/src/worker/instance.ts @@ -1,13 +1,13 @@ import type { Logger } from "@/common//log"; import { - type ActorKey, + type WorkerKey, isJsonSerializable, stringifyError, } from "@/common//utils"; import onChange from "on-change"; -import type { ActorConfig } from "./config"; +import type { WorkerConfig } from "./config"; import { Conn, type ConnId } from "./connection"; -import type { ActorDriver, ConnDrivers } from "./driver"; +import type { WorkerDriver, ConnDrivers } from "./driver"; import type { ConnDriver } from "./driver"; import * as errors from "./errors"; import { processMessage } from "./protocol/message/mod"; @@ -15,14 +15,14 @@ import { instanceLogger, logger } from "./log"; import type { ActionContext } from "./action"; import { DeadlineError, Lock, deadline } from "./utils"; import { Schedule } from "./schedule"; -import * as wsToClient from "@/actor/protocol/message/to-client"; -import type * as wsToServer from "@/actor/protocol/message/to-server"; +import * as wsToClient from "@/worker/protocol/message/to-client"; +import type * as wsToServer from "@/worker/protocol/message/to-server"; import { CachedSerializer } from "./protocol/serde"; -import { ActorInspector } from "@/inspector/actor"; -import { ActorContext } from "./context"; +import { WorkerInspector } from "@/inspector/worker"; +import { WorkerContext } from "./context"; import invariant from "invariant"; import type { - PersistedActor, + PersistedWorker, PersistedConn, PersistedScheduleEvents, } from "./persisted"; @@ -37,12 +37,12 @@ export interface SaveStateOptions { immediate?: boolean; } -/** Actor type alias with all `any` types. Used for `extends` in classes referencing this actor. */ +/** Worker type alias with all `any` types. Used for `extends` in classes referencing this worker. */ // biome-ignore lint/suspicious/noExplicitAny: Needs to be used in `extends` -export type AnyActorInstance = ActorInstance; +export type AnyWorkerInstance = WorkerInstance; -export type ExtractActorState = - A extends ActorInstance< +export type ExtractWorkerState = + A extends WorkerInstance< infer State, // biome-ignore lint/suspicious/noExplicitAny: Must be used for `extends` any, @@ -54,8 +54,8 @@ export type ExtractActorState = ? State : never; -export type ExtractActorConnParams = - A extends ActorInstance< +export type ExtractWorkerConnParams = + A extends WorkerInstance< // biome-ignore lint/suspicious/noExplicitAny: Must be used for `extends` any, infer ConnParams, @@ -67,8 +67,8 @@ export type ExtractActorConnParams = ? ConnParams : never; -export type ExtractActorConnState = - A extends ActorInstance< +export type ExtractWorkerConnState = + A extends WorkerInstance< // biome-ignore lint/suspicious/noExplicitAny: Must be used for `extends` any, // biome-ignore lint/suspicious/noExplicitAny: Must be used for `extends` @@ -80,9 +80,9 @@ export type ExtractActorConnState = ? ConnState : never; -export class ActorInstance { - // Shared actor context for this instance - actorContext: ActorContext; +export class WorkerInstance { + // Shared worker context for this instance + workerContext: WorkerContext; isStopping = false; #persistChanged = false; @@ -92,10 +92,10 @@ export class ActorInstance { * * Any data that should be stored indefinitely should be held within this object. */ - #persist!: PersistedActor; + #persist!: PersistedWorker; /** Raw state without the proxy wrapper */ - #persistRaw!: PersistedActor; + #persistRaw!: PersistedWorker; #writePersistLock = new Lock(void 0); @@ -105,12 +105,12 @@ export class ActorInstance { #vars?: V; #backgroundPromises: Promise[] = []; - #config: ActorConfig; + #config: WorkerConfig; #connectionDrivers!: ConnDrivers; - #actorDriver!: ActorDriver; - #actorId!: string; + #workerDriver!: WorkerDriver; + #workerId!: string; #name!: string; - #key!: ActorKey; + #key!: WorkerKey; #region!: string; #ready = false; @@ -120,43 +120,43 @@ export class ActorInstance { #schedule!: Schedule; /** - * Inspector for the actor. + * Inspector for the worker. * @internal */ - inspector!: ActorInspector; + inspector!: WorkerInspector; get id() { - return this.#actorId; + return this.#workerId; } /** * This constructor should never be used directly. * - * Constructed in {@link ActorInstance.start}. + * Constructed in {@link WorkerInstance.start}. * * @private */ - constructor(config: ActorConfig) { + constructor(config: WorkerConfig) { this.#config = config; - this.actorContext = new ActorContext(this); + this.workerContext = new WorkerContext(this); } async start( connectionDrivers: ConnDrivers, - actorDriver: ActorDriver, - actorId: string, + workerDriver: WorkerDriver, + workerId: string, name: string, - key: ActorKey, + key: WorkerKey, region: string, ) { this.#connectionDrivers = connectionDrivers; - this.#actorDriver = actorDriver; - this.#actorId = actorId; + this.#workerDriver = workerDriver; + this.#workerId = workerId; this.#name = name; this.#key = key; this.#region = region; this.#schedule = new Schedule(this); - this.inspector = new ActorInspector(this); + this.inspector = new WorkerInspector(this); // Initialize server // @@ -168,13 +168,13 @@ export class ActorInstance { let vars: V | undefined = undefined; if ("createVars" in this.#config) { const dataOrPromise = this.#config.createVars( - this.actorContext as unknown as ActorContext< + this.workerContext as unknown as WorkerContext< undefined, undefined, undefined, undefined >, - this.#actorDriver.getContext(this.#actorId), + this.#workerDriver.getContext(this.#workerId), ); if (dataOrPromise instanceof Promise) { vars = await deadline( @@ -193,9 +193,9 @@ export class ActorInstance { } // TODO: Exit process if this errors - logger().info("actor starting"); + logger().info("worker starting"); if (this.#config.onStart) { - const result = this.#config.onStart(this.actorContext); + const result = this.#config.onStart(this.workerContext); if (result instanceof Promise) { await result; } @@ -203,10 +203,10 @@ export class ActorInstance { // Set alarm for next scheduled event if any exist after finishing initiation sequence if (this.#persist.e.length > 0) { - await this.#actorDriver.setAlarm(this, this.#persist.e[0].t); + await this.#workerDriver.setAlarm(this, this.#persist.e[0].t); } - logger().info("actor ready"); + logger().info("worker ready"); this.#ready = true; } @@ -224,7 +224,7 @@ export class ActorInstance { ar: args, }; - this.actorContext.log.info("scheduling event", { + this.workerContext.log.info("scheduling event", { event: eventId, timestamp, action: fn, @@ -242,14 +242,14 @@ export class ActorInstance { // - 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.actorContext.log.info("setting alarm", { timestamp }); - await this.#actorDriver.setAlarm(this, newEvent.t); + this.workerContext.log.info("setting alarm", { timestamp }); + await this.#workerDriver.setAlarm(this, newEvent.t); } } async onAlarm() { const now = Date.now(); - this.actorContext.log.debug("alarm triggered", { + this.workerContext.log.debug("alarm triggered", { now, events: this.#persist.e.length, }); @@ -257,23 +257,23 @@ export class ActorInstance { // Remove events from schedule that we're about to run const runIndex = this.#persist.e.findIndex((x) => x.t <= now); if (runIndex === -1) { - this.actorContext.log.debug("no events to run", { now }); + this.workerContext.log.debug("no events to run", { now }); return; } const scheduleEvents = this.#persist.e.splice(0, runIndex + 1); - this.actorContext.log.debug("running events", { + this.workerContext.log.debug("running events", { count: scheduleEvents.length, }); // Set alarm for next event if (this.#persist.e.length > 0) { - await this.#actorDriver.setAlarm(this, this.#persist.e[0].t); + await this.#workerDriver.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.actorContext.log.info("running action for event", { + this.workerContext.log.info("running action for event", { event: event.e, timestamp: event.t, action: event.a, @@ -290,9 +290,9 @@ export class ActorInstance { // Call function try { - await fn.call(undefined, this.actorContext, ...event.ar); + await fn.call(undefined, this.workerContext, ...event.ar); } catch (error) { - this.actorContext.log.error("error while running event", { + this.workerContext.log.error("error while running event", { error: stringifyError(error), event: event.e, timestamp: event.t, @@ -301,7 +301,7 @@ export class ActorInstance { }); } } catch (error) { - this.actorContext.log.error("internal error while running event", { + this.workerContext.log.error("internal error while running event", { error: stringifyError(error), event: event.e, timestamp: event.t, @@ -376,8 +376,8 @@ export class ActorInstance { this.#persistChanged = false; // Write to KV - await this.#actorDriver.writePersistedData( - this.#actorId, + await this.#workerDriver.writePersistedData( + this.#workerId, this.#persistRaw, ); @@ -395,7 +395,7 @@ export class ActorInstance { /** * Creates proxy for `#persist` that handles automatically flagging when state needs to be updated. */ - #setPersist(target: PersistedActor) { + #setPersist(target: PersistedWorker) { // Set raw persist object this.#persistRaw = target; @@ -450,7 +450,7 @@ export class ActorInstance { // Call onStateChange if it exists if (this.#config.onStateChange && this.#ready) { try { - this.#config.onStateChange(this.actorContext, this.#persistRaw.s); + this.#config.onStateChange(this.workerContext, this.#persistRaw.s); } catch (error) { logger().error("error in `_onStateChange`", { error: stringifyError(error), @@ -466,12 +466,12 @@ export class ActorInstance { async #initialize() { // Read initial state - const persistData = (await this.#actorDriver.readPersistedData( - this.#actorId, - )) as PersistedActor; + const persistData = (await this.#workerDriver.readPersistedData( + this.#workerId, + )) as PersistedWorker; if (persistData !== undefined) { - logger().info("actor restoring", { + logger().info("worker restoring", { connections: persistData.c.length, }); @@ -496,21 +496,21 @@ export class ActorInstance { } } } else { - logger().info("actor creating"); + logger().info("worker creating"); - const input = await this.#actorDriver.readInput(this.#actorId); + const input = await this.#workerDriver.readInput(this.#workerId); - // Initialize actor state + // Initialize worker state let stateData: unknown = undefined; if (this.stateEnabled) { - logger().info("actor state initializing"); + logger().info("worker 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.actorContext as unknown as ActorContext< + this.workerContext as unknown as WorkerContext< undefined, undefined, undefined, @@ -527,7 +527,7 @@ export class ActorInstance { logger().debug("state not enabled"); } - const persist: PersistedActor = { + const persist: PersistedWorker = { s: stateData as S, c: [], e: [], @@ -535,13 +535,13 @@ export class ActorInstance { // Update state logger().debug("writing state"); - await this.#actorDriver.writePersistedData(this.#actorId, persist); + await this.#workerDriver.writePersistedData(this.#workerId, persist); this.#setPersist(persist); // Notify creation if (this.#config.onCreate) { - await this.#config.onCreate(this.actorContext, { input }); + await this.#config.onCreate(this.workerContext, { input }); } } } @@ -581,7 +581,7 @@ export class ActorInstance { this.inspector.onConnChange(this.#connections); if (this.#config.onDisconnect) { try { - const result = this.#config.onDisconnect(this.actorContext, conn); + const result = this.#config.onDisconnect(this.workerContext, conn); if (result instanceof Promise) { // Handle promise but don't await it to prevent blocking result.catch((error) => { @@ -599,7 +599,7 @@ export class ActorInstance { } async prepareConn( - // biome-ignore lint/suspicious/noExplicitAny: TypeScript bug with ExtractActorConnParams, + // biome-ignore lint/suspicious/noExplicitAny: TypeScript bug with ExtractWorkerConnParams, params: any, request?: Request, ): Promise { @@ -613,7 +613,7 @@ export class ActorInstance { if (this.#config.onBeforeConnect) { await this.#config.onBeforeConnect( - this.actorContext, + this.workerContext, onBeforeConnectOpts, ); } @@ -621,7 +621,7 @@ export class ActorInstance { if (this.#connStateEnabled) { if ("createConnState" in this.#config) { const dataOrPromise = this.#config.createConnState( - this.actorContext as unknown as ActorContext< + this.workerContext as unknown as WorkerContext< undefined, undefined, undefined, @@ -699,7 +699,7 @@ export class ActorInstance { // Handle connection if (this.#config.onConnect) { try { - const result = this.#config.onConnect(this.actorContext, conn); + const result = this.#config.onConnect(this.workerContext, conn); if (result instanceof Promise) { deadline( result, @@ -820,7 +820,7 @@ export class ActorInstance { } #assertReady() { - if (!this.#ready) throw new errors.InternalError("Actor not ready"); + if (!this.#ready) throw new errors.InternalError("Worker not ready"); } /** @@ -890,7 +890,7 @@ export class ActorInstance { if (this.#config.onBeforeActionResponse) { try { const processedOutput = this.#config.onBeforeActionResponse( - this.actorContext, + this.workerContext, actionName, args, output, @@ -936,7 +936,7 @@ export class ActorInstance { } /** - * Returns a list of action methods available on this actor. + * Returns a list of action methods available on this worker. */ get actions(): string[] { return Object.keys(this.#config.actions); @@ -962,7 +962,7 @@ export class ActorInstance { /** * Gets the key. */ - get key(): ActorKey { + get key(): WorkerKey { return this.#key; } @@ -1043,7 +1043,7 @@ export class ActorInstance { /** * Runs a promise in the background. * - * This allows the actor runtime to ensure that a promise completes while + * This allows the worker runtime to ensure that a promise completes while * returning from an action request early. * * @param promise - The promise to run in the background. @@ -1097,7 +1097,7 @@ export class ActorInstance { async stop() { if (this.isStopping) { - logger().warn("already stopping actor"); + logger().warn("already stopping worker"); return; } this.isStopping = true; diff --git a/packages/core/src/worker/log.ts b/packages/core/src/worker/log.ts new file mode 100644 index 000000000..d093e846a --- /dev/null +++ b/packages/core/src/worker/log.ts @@ -0,0 +1,16 @@ +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 new file mode 100644 index 000000000..ac3b0a2be --- /dev/null +++ b/packages/core/src/worker/mod.ts @@ -0,0 +1,28 @@ +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>( + input: WorkerConfigInput, +): WorkerDefinition { + const config = WorkerConfigSchema.parse(input) as WorkerConfig; + return new WorkerDefinition(config); +} diff --git a/packages/actor/src/actor/persisted.ts b/packages/core/src/worker/persisted.ts similarity index 94% rename from packages/actor/src/actor/persisted.ts rename to packages/core/src/worker/persisted.ts index a02dc713d..2043ada55 100644 --- a/packages/actor/src/actor/persisted.ts +++ b/packages/core/src/worker/persisted.ts @@ -1,5 +1,5 @@ /** State object that gets automatically persisted to storage. */ -export interface PersistedActor { +export interface PersistedWorker { // State s: S; // Connections diff --git a/packages/actor/src/actor/protocol/http/action.ts b/packages/core/src/worker/protocol/http/action.ts similarity index 100% rename from packages/actor/src/actor/protocol/http/action.ts rename to packages/core/src/worker/protocol/http/action.ts diff --git a/packages/actor/src/actor/protocol/http/error.ts b/packages/core/src/worker/protocol/http/error.ts similarity index 100% rename from packages/actor/src/actor/protocol/http/error.ts rename to packages/core/src/worker/protocol/http/error.ts diff --git a/packages/actor/src/actor/protocol/http/resolve.ts b/packages/core/src/worker/protocol/http/resolve.ts similarity index 92% rename from packages/actor/src/actor/protocol/http/resolve.ts rename to packages/core/src/worker/protocol/http/resolve.ts index 54c124539..3f044ac49 100644 --- a/packages/actor/src/actor/protocol/http/resolve.ts +++ b/packages/core/src/worker/protocol/http/resolve.ts @@ -1,7 +1,7 @@ import { z } from "zod"; export const ResolveResponseSchema = z.object({ - // Actor ID + // Worker ID i: z.string(), }); diff --git a/packages/actor/src/actor/protocol/message/mod.ts b/packages/core/src/worker/protocol/message/mod.ts similarity index 90% rename from packages/actor/src/actor/protocol/message/mod.ts rename to packages/core/src/worker/protocol/message/mod.ts index 23b7de19d..fc014f8aa 100644 --- a/packages/actor/src/actor/protocol/message/mod.ts +++ b/packages/core/src/worker/protocol/message/mod.ts @@ -1,6 +1,6 @@ -import * as wsToClient from "@/actor/protocol/message/to-client"; -import * as wsToServer from "@/actor/protocol/message/to-server"; -import type { ActorInstance, AnyActorInstance } from "../../instance"; +import * as wsToClient from "@/worker/protocol/message/to-client"; +import * as wsToServer from "@/worker/protocol/message/to-server"; +import type { WorkerInstance, AnyWorkerInstance } from "../../instance"; import type { Conn } from "../../connection"; import * as errors from "../../errors"; import { logger } from "../../log"; @@ -12,15 +12,15 @@ import { Encoding, InputData, CachedSerializer, -} from "@/actor/protocol/serde"; +} from "@/worker/protocol/serde"; import { deconstructError } from "@/common/utils"; -import { Actions } from "@/actor/config"; +import { Actions } from "@/worker/config"; import invariant from "invariant"; export const TransportSchema = z.enum(["websocket", "sse"]); /** - * Transport mechanism used to communicate between client & actor. + * Transport mechanism used to communicate between client & worker. */ export type Transport = z.infer; @@ -84,7 +84,7 @@ export interface ProcessMessageHandler { export async function processMessage( message: wsToServer.ToServer, - actor: ActorInstance, + worker: WorkerInstance, conn: Conn, handler: ProcessMessageHandler, ) { @@ -112,7 +112,7 @@ export async function processMessage( argsCount: args.length, }); - const ctx = new ActionContext(actor.actorContext, conn); + const ctx = new ActionContext(worker.workerContext, conn); // Process the action request and wait for the result // This will wait for async actions to complete diff --git a/packages/actor/src/actor/protocol/message/to-client.ts b/packages/core/src/worker/protocol/message/to-client.ts similarity index 98% rename from packages/actor/src/actor/protocol/message/to-client.ts rename to packages/core/src/worker/protocol/message/to-client.ts index a0a5330a9..b24c517cc 100644 --- a/packages/actor/src/actor/protocol/message/to-client.ts +++ b/packages/core/src/worker/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({ - // Actor ID + // Worker ID ai: z.string(), // Connection ID ci: z.string(), diff --git a/packages/actor/src/actor/protocol/message/to-server.ts b/packages/core/src/worker/protocol/message/to-server.ts similarity index 100% rename from packages/actor/src/actor/protocol/message/to-server.ts rename to packages/core/src/worker/protocol/message/to-server.ts diff --git a/packages/actor/src/actor/protocol/serde.ts b/packages/core/src/worker/protocol/serde.ts similarity index 96% rename from packages/actor/src/actor/protocol/serde.ts rename to packages/core/src/worker/protocol/serde.ts index 245513e61..41a3532fe 100644 --- a/packages/actor/src/actor/protocol/serde.ts +++ b/packages/core/src/worker/protocol/serde.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import * as errors from "@/actor/errors"; +import * as errors from "@/worker/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 & actor. + * Encoding used to communicate between the client & worker. */ export type Encoding = z.infer; diff --git a/packages/actor/src/actor/router-endpoints.ts b/packages/core/src/worker/router-endpoints.ts similarity index 92% rename from packages/actor/src/actor/router-endpoints.ts rename to packages/core/src/worker/router-endpoints.ts index 99e831551..7e3b9c294 100644 --- a/packages/actor/src/actor/router-endpoints.ts +++ b/packages/core/src/worker/router-endpoints.ts @@ -9,11 +9,11 @@ import { serialize, deserialize, CachedSerializer, -} 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, OutputData } from "@/actor/protocol/serde"; +} 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, OutputData } from "@/worker/protocol/serde"; import { assertUnreachable } from "./utils"; import { deconstructError, stringifyError } from "@/common/utils"; import type { AppConfig } from "@/app/config"; @@ -24,7 +24,7 @@ export interface ConnectWebSocketOpts { req?: HonoRequest; encoding: Encoding; params: unknown; - actorId: string; + workerId: string; } export interface ConnectWebSocketOutput { @@ -37,7 +37,7 @@ export interface ConnectSseOpts { req?: HonoRequest; encoding: Encoding; params: unknown; - actorId: string; + workerId: string; } export interface ConnectSseOutput { @@ -50,7 +50,7 @@ export interface ActionOpts { params: unknown; actionName: string; actionArgs: unknown[]; - actorId: string; + workerId: string; } export interface ActionOutput { @@ -62,11 +62,11 @@ export interface ConnsMessageOpts { connId: string; connToken: string; message: messageToServer.ToServer; - actorId: string; + workerId: string; } /** - * Shared interface for connection handlers used by both ActorRouterHandler and ManagerRouterHandler + * Shared interface for connection handlers used by both WorkerRouterHandler and ManagerRouterHandler */ export interface ConnectionHandlers { onConnectWebSocket?( @@ -85,7 +85,7 @@ export function handleWebSocketConnect( appConfig: AppConfig, driverConfig: DriverConfig, handler: (opts: ConnectWebSocketOpts) => Promise, - actorId: string, + workerId: string, ) { return async () => { const encoding = getRequestEncoding(context.req, true); @@ -148,7 +148,7 @@ export function handleWebSocketConnect( req: context.req, encoding, params: message.b.i.p, - actorId, + workerId, }); // Notify socket open @@ -212,7 +212,7 @@ export function handleWebSocketConnect( }, onError: async (_error: unknown) => { try { - // Actors don't need to know about this, since it's abstracted away + // Workers don't need to know about this, since it's abstracted away logger().warn("websocket error"); } catch (error) { deconstructError(error, logger(), { wsEvent: "error" }); @@ -230,7 +230,7 @@ export async function handleSseConnect( appConfig: AppConfig, driverConfig: DriverConfig, handler: (opts: ConnectSseOpts) => Promise, - actorId: string, + workerId: string, ) { const encoding = getRequestEncoding(c.req, false); const parameters = getRequestConnParams(c.req, appConfig, driverConfig); @@ -239,7 +239,7 @@ export async function handleSseConnect( req: c.req, encoding, params: parameters, - actorId, + workerId, }); return streamSSE(c, async (stream) => { @@ -275,7 +275,7 @@ export async function handleAction( driverConfig: DriverConfig, handler: (opts: ActionOpts) => Promise, actionName: string, - actorId: string, + workerId: string, ) { const encoding = getRequestEncoding(c.req, false); const parameters = getRequestConnParams(c.req, appConfig, driverConfig); @@ -325,7 +325,7 @@ export async function handleAction( params: parameters, actionName: actionName, actionArgs: actionArgs, - actorId, + workerId, }); // Encode the response @@ -355,7 +355,7 @@ export async function handleConnectionMessage( handler: (opts: ConnsMessageOpts) => Promise, connId: string, connToken: string, - actorId: string, + workerId: string, ) { const encoding = getRequestEncoding(c.req, false); @@ -389,7 +389,7 @@ export async function handleConnectionMessage( connId, connToken, message, - actorId, + workerId, }); return c.json({}); @@ -416,10 +416,10 @@ export function getRequestEncoding( } export function getRequestQuery(c: HonoContext, useQuery: boolean): unknown { - // Get query parameters for actor lookup + // Get query parameters for worker lookup const queryParam = useQuery ? c.req.query("query") - : c.req.header(HEADER_ACTOR_QUERY); + : c.req.header(HEADER_WORKER_QUERY); if (!queryParam) { logger().error("missing query parameter"); throw new errors.InvalidRequest("missing query"); @@ -435,24 +435,24 @@ export function getRequestQuery(c: HonoContext, useQuery: boolean): unknown { } } -export const HEADER_ACTOR_QUERY = "X-AC-Query"; +export const HEADER_WORKER_QUERY = "X-AC-Query"; export const HEADER_ENCODING = "X-AC-Encoding"; // IMPORTANT: Params must be in headers or in an E2EE part of the request (i.e. NOT the URL or query string) in order to ensure that tokens can be securely passed in params. export const HEADER_CONN_PARAMS = "X-AC-Conn-Params"; -export const HEADER_ACTOR_ID = "X-AC-Actor"; +export const HEADER_WORKER_ID = "X-AC-Worker"; export const HEADER_CONN_ID = "X-AC-Conn"; export const HEADER_CONN_TOKEN = "X-AC-Conn-Token"; export const ALL_HEADERS = [ - HEADER_ACTOR_QUERY, + HEADER_WORKER_QUERY, HEADER_ENCODING, HEADER_CONN_PARAMS, - HEADER_ACTOR_ID, + HEADER_WORKER_ID, HEADER_CONN_ID, HEADER_CONN_TOKEN, ]; diff --git a/packages/actor/src/actor/router.ts b/packages/core/src/worker/router.ts similarity index 84% rename from packages/actor/src/actor/router.ts rename to packages/core/src/worker/router.ts index f599854ea..d3996af5c 100644 --- a/packages/actor/src/actor/router.ts +++ b/packages/core/src/worker/router.ts @@ -10,9 +10,9 @@ import { import type { DriverConfig } from "@/driver-helpers/config"; import type { AppConfig } from "@/app/config"; import { - type ActorInspectorConnHandler, - createActorInspectorRouter, -} from "@/inspector/actor"; + type WorkerInspectorConnHandler, + createWorkerInspectorRouter, +} from "@/inspector/worker"; import invariant from "invariant"; import { type ConnectWebSocketOpts, @@ -42,13 +42,13 @@ export type { ConnsMessageOpts, }; -export interface ActorRouterHandler { - getActorId: () => Promise; +export interface WorkerRouterHandler { + getWorkerId: () => Promise; // Connection handlers as a required subobject connectionHandlers: ConnectionHandlers; - onConnectInspector?: ActorInspectorConnHandler; + onConnectInspector?: WorkerInspectorConnHandler; } /** @@ -56,10 +56,10 @@ export interface ActorRouterHandler { * * This allows for creating a universal protocol across all platforms. */ -export function createActorRouter( +export function createWorkerRouter( appConfig: AppConfig, driverConfig: DriverConfig, - handler: ActorRouterHandler, + handler: WorkerRouterHandler, ): Hono { const app = new Hono(); @@ -69,7 +69,7 @@ export function createActorRouter( // Apply CORS middleware if configured // - //This is only relevant if the actor is exposed directly publicly + //This is only relevant if the worker is exposed directly publicly if (appConfig.cors) { const corsConfig = appConfig.cors; @@ -90,7 +90,7 @@ export function createActorRouter( app.get("/", (c) => { return c.text( - "This is an ActorCore server.\n\nLearn more at https://actorcore.org", + "This is an WorkerCore server.\n\nLearn more at https://workercore.org", ); }); @@ -105,13 +105,13 @@ export function createActorRouter( app.get( "/connect/websocket", upgradeWebSocket(async (c) => { - const actorId = await handler.getActorId(); + const workerId = await handler.getWorkerId(); return handleWebSocketConnect( c as HonoContext, appConfig, driverConfig, handlers.onConnectWebSocket!, - actorId, + workerId, )(); }), ); @@ -128,13 +128,13 @@ export function createActorRouter( if (!handlers.onConnectSse) { throw new Error("onConnectSse handler is required"); } - const actorId = await handler.getActorId(); + const workerId = await handler.getWorkerId(); return handleSseConnect( c, appConfig, driverConfig, handlers.onConnectSse, - actorId, + workerId, ); }); @@ -143,14 +143,14 @@ export function createActorRouter( throw new Error("onAction handler is required"); } const actionName = c.req.param("action"); - const actorId = await handler.getActorId(); + const workerId = await handler.getWorkerId(); return handleAction( c, appConfig, driverConfig, handlers.onAction, actionName, - actorId, + workerId, ); }); @@ -160,7 +160,7 @@ export function createActorRouter( } const connId = c.req.header(HEADER_CONN_ID); const connToken = c.req.header(HEADER_CONN_TOKEN); - const actorId = await handler.getActorId(); + const workerId = await handler.getWorkerId(); if (!connId || !connToken) { throw new Error("Missing required parameters"); } @@ -170,14 +170,14 @@ export function createActorRouter( handlers.onConnMessage, connId, connToken, - actorId, + workerId, ); }); if (appConfig.inspector.enabled) { app.route( "/inspect", - createActorInspectorRouter( + createWorkerInspectorRouter( upgradeWebSocket, handler.onConnectInspector, appConfig.inspector, diff --git a/packages/core/src/worker/schedule.ts b/packages/core/src/worker/schedule.ts new file mode 100644 index 000000000..b94b6abf7 --- /dev/null +++ b/packages/core/src/worker/schedule.ts @@ -0,0 +1,17 @@ +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/actor/src/actor/unstable-react.ts b/packages/core/src/worker/unstable-react.ts similarity index 94% rename from packages/actor/src/actor/unstable-react.ts rename to packages/core/src/worker/unstable-react.ts index ccdac7ca0..d7d8aa8ca 100644 --- a/packages/actor/src/actor/unstable-react.ts +++ b/packages/core/src/worker/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 { Actor } from "./actor"; +//import { Worker } from "./worker"; // ///** -// * A React Server Components (RSC) actor. +// * A React Server Components (RSC) worker. // * // * Supports rendering React elements as action responses. // * // * @see [Documentation](https://rivet.gg/docs/client/react) // * @experimental // */ -//export class RscActor< +//export class RscWorker< // State = undefined, // ConnParams = undefined, // ConnState = undefined, -//> extends Actor { +//> extends Worker { // /** // * Updates the RSCs for all connected clients. // */ diff --git a/packages/actor/src/actor/utils.ts b/packages/core/src/worker/utils.ts similarity index 100% rename from packages/actor/src/actor/utils.ts rename to packages/core/src/worker/utils.ts diff --git a/packages/actor/tests/driver-test-suite.test.ts b/packages/core/tests/driver-test-suite.test.ts similarity index 80% rename from packages/actor/tests/driver-test-suite.test.ts rename to packages/core/tests/driver-test-suite.test.ts index 0eb22fd4a..281711802 100644 --- a/packages/actor/tests/driver-test-suite.test.ts +++ b/packages/core/tests/driver-test-suite.test.ts @@ -3,7 +3,7 @@ import { createTestRuntime, } from "@/driver-test-suite/mod"; import { TestGlobalState } from "@/test/driver/global-state"; -import { TestActorDriver } from "@/test/driver/actor"; +import { TestWorkerDriver } from "@/test/driver/worker"; import { TestManagerDriver } from "@/test/driver/manager"; runDriverTests({ @@ -11,7 +11,7 @@ runDriverTests({ return await createTestRuntime(appPath, async (app) => { const memoryState = new TestGlobalState(); return { - actorDriver: new TestActorDriver(memoryState), + workerDriver: new TestWorkerDriver(memoryState), managerDriver: new TestManagerDriver(app, memoryState), }; }); diff --git a/packages/core/tests/worker-types.test.ts b/packages/core/tests/worker-types.test.ts new file mode 100644 index 000000000..e90a9851c --- /dev/null +++ b/packages/core/tests/worker-types.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect, expectTypeOf } from "vitest"; +import { WorkerDefinition, type WorkerContextOf } from "@/worker/definition"; +import type { WorkerContext } from "@/worker/context"; + +describe("WorkerDefinition", () => { + describe("WorkerContextOf type utility", () => { + it("should correctly extract the context type from an WorkerDefinition", () => { + // Define some simple types for testing + interface TestState { + counter: number; + } + + interface TestConnParams { + clientId: string; + } + + interface TestConnState { + lastSeen: number; + } + + interface TestVars { + foo: string; + } + + // For testing type utilities, we don't need a real worker instance + // We just need a properly typed WorkerDefinition to check against + type TestActions = Record; + const dummyDefinition = {} as WorkerDefinition< + TestState, + TestConnParams, + TestConnState, + TestVars, + TestActions + >; + + // Use expectTypeOf to verify our type utility works correctly + expectTypeOf>().toEqualTypeOf< + WorkerContext + >(); + + // Make sure that different types are not compatible + interface DifferentState { + value: string; + } + + expectTypeOf>().not.toEqualTypeOf< + WorkerContext + >(); + }); + }); +}); diff --git a/packages/actor/tsconfig.json b/packages/core/tsconfig.json similarity index 86% rename from packages/actor/tsconfig.json rename to packages/core/tsconfig.json index fe4a0cb83..dcaca095d 100644 --- a/packages/actor/tsconfig.json +++ b/packages/core/tsconfig.json @@ -5,7 +5,7 @@ "paths": { "@/*": ["./src/*"], // Used for test fixtures - "@rivetkit/actor": ["./src/mod.ts"] + "rivetkit": ["./src/mod.ts"] } }, "include": ["src/**/*", "tests/**/*", "scripts/**/*", "fixtures/driver-test-suite/**/*"] diff --git a/packages/actor/tsup.config.bundled_xvi1jgwbzx.mjs b/packages/core/tsup.config.bundled_xvi1jgwbzx.mjs similarity index 100% rename from packages/actor/tsup.config.bundled_xvi1jgwbzx.mjs rename to packages/core/tsup.config.bundled_xvi1jgwbzx.mjs diff --git a/packages/actor/tsup.config.ts b/packages/core/tsup.config.ts similarity index 100% rename from packages/actor/tsup.config.ts rename to packages/core/tsup.config.ts diff --git a/packages/actor/turbo.json b/packages/core/turbo.json similarity index 100% rename from packages/actor/turbo.json rename to packages/core/turbo.json diff --git a/packages/actor/vitest.config.ts b/packages/core/vitest.config.ts similarity index 100% rename from packages/actor/vitest.config.ts rename to packages/core/vitest.config.ts diff --git a/packages/drivers/file-system/package.json b/packages/drivers/file-system/package.json index dd2e4cc68..c8b192232 100644 --- a/packages/drivers/file-system/package.json +++ b/packages/drivers/file-system/package.json @@ -24,12 +24,12 @@ "test": "vitest run" }, "peerDependencies": { - "@rivetkit/actor": "*" + "rivetkit": "*" }, "devDependencies": { - "@rivetkit/actor": "workspace:*", "@types/invariant": "^2", "@types/node": "^22.14.0", + "rivetkit": "workspace:*", "tsup": "^8.4.0", "typescript": "^5.5.2", "vitest": "^3.1.1" diff --git a/packages/drivers/file-system/src/actor.ts b/packages/drivers/file-system/src/actor.ts deleted file mode 100644 index 19e5be6a9..000000000 --- a/packages/drivers/file-system/src/actor.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { ActorDriver, AnyActorInstance } from "@rivetkit/actor/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); - } -} diff --git a/packages/drivers/file-system/src/global-state.ts b/packages/drivers/file-system/src/global-state.ts index b3bcf1926..150e6ba1d 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 { ActorKey } from "@rivetkit/actor"; +import type { WorkerKey } from "rivetkit"; import { logger } from "./log"; import { getStoragePath, - getActorStoragePath, + getWorkerStoragePath, ensureDirectoryExists, ensureDirectoryExistsSync, } from "./utils"; import invariant from "invariant"; /** - * Interface representing an actor's state + * Interface representing a worker's state */ -export interface ActorState { +export interface WorkerState { id: string; name: string; - key: ActorKey; + key: WorkerKey; persistedData: unknown; input?: unknown; } @@ -27,7 +27,7 @@ export interface ActorState { */ 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}/actors`); + ensureDirectoryExistsSync(`${this.#storagePath}/workers`); - // Load all actors into cache synchronously - this.#loadAllActorsIntoCache(); + // Load all workers into cache synchronously + this.#loadAllWorkersIntoCache(); logger().info("file system loaded", { dir: this.#storagePath, - actorCount: this.#stateCache.size, + workerCount: this.#stateCache.size, }); } /** - * Load all actors into the state cache from the file system + * Load all workers into the state cache from the file system * Only called once during initialization */ - #loadAllActorsIntoCache(): void { - const actorsDir = path.join(this.#storagePath, "actors"); + #loadAllWorkersIntoCache(): void { + const workersDir = path.join(this.#storagePath, "workers"); try { // HACK: Use synchronous filesystem operations for initialization - const actorIds = fsSync.readdirSync(actorsDir); + const workerIds = fsSync.readdirSync(workersDir); - for (const actorId of actorIds) { - const stateFilePath = this.getStateFilePath(actorId); + for (const workerId of workerIds) { + const stateFilePath = this.getStateFilePath(workerId); if (fsSync.existsSync(stateFilePath)) { try { const stateData = fsSync.readFileSync(stateFilePath, "utf8"); - const state = JSON.parse(stateData) as ActorState; + const state = JSON.parse(stateData) as WorkerState; - this.#stateCache.set(actorId, state); + this.#stateCache.set(workerId, state); } catch (error) { logger().error( - "failed to read actor state during cache initialization", - { actorId, error }, + "failed to read worker state during cache initialization", + { workerId, error }, ); } } } } catch (error) { - logger().error("failed to load actors into cache", { error }); + logger().error("failed to load workers into cache", { error }); } } @@ -87,62 +87,62 @@ export class FileSystemGlobalState { } /** - * Get state file path for an actor + * Get state file path for a worker */ - getStateFilePath(actorId: string): string { - const actorDir = getActorStoragePath(this.#storagePath, actorId); - return path.join(actorDir, "state.json"); + getStateFilePath(workerId: string): string { + const workerDir = getWorkerStoragePath(this.#storagePath, workerId); + return path.join(workerDir, "state.json"); } /** - * Load actor state from cache + * Load worker state from cache */ - loadActorState(actorId: string): ActorState { - this.ensureActorExists(actorId); + loadWorkerState(workerId: string): WorkerState { + this.ensureWorkerExists(workerId); - // Get actor state from cache - const cachedActor = this.#stateCache.get(actorId); - invariant(cachedActor, `actor state should exist in cache for ${actorId}`); + // Get worker state from cache + const cachedWorker = this.#stateCache.get(workerId); + invariant(cachedWorker, `worker state should exist in cache for ${workerId}`); - return cachedActor; + return cachedWorker; } - readInput(actorId: string): unknown | undefined { - const state = this.loadActorState(actorId); + readInput(workerId: string): unknown | undefined { + const state = this.loadWorkerState(workerId); return state.input; } /** - * Read persisted data for an actor + * Read persisted data for a worker */ - readPersistedData(actorId: string): unknown | undefined { - const state = this.loadActorState(actorId); + readPersistedData(workerId: string): unknown | undefined { + const state = this.loadWorkerState(workerId); return state.persistedData; } /** - * Write persisted data for an actor + * Write persisted data for a worker */ - writePersistedData(actorId: string, data: unknown): void { - const state = this.loadActorState(actorId); + writePersistedData(workerId: string, data: unknown): void { + const state = this.loadWorkerState(workerId); state.persistedData = data; } /** - * Save actor state to disk + * Save worker state to disk */ - async saveActorState(actorId: string): Promise { - const state = this.#stateCache.get(actorId); + async saveWorkerState(workerId: string): Promise { + const state = this.#stateCache.get(workerId); if (!state) { return; } - const actorDir = getActorStoragePath(this.#storagePath, actorId); - const stateFilePath = this.getStateFilePath(actorId); + const workerDir = getWorkerStoragePath(this.#storagePath, workerId); + const stateFilePath = this.getStateFilePath(workerId); try { - // Create actor directory - await ensureDirectoryExists(actorDir); + // Create worker directory + await ensureDirectoryExists(workerDir); // Create serializable object // State is already in serializable format @@ -154,44 +154,44 @@ export class FileSystemGlobalState { "utf8", ); } catch (error) { - logger().error("failed to save actor state", { actorId, error }); - throw new Error(`Failed to save actor state: ${error}`); + logger().error("failed to save worker state", { workerId, error }); + throw new Error(`Failed to save worker state: ${error}`); } } /** - * Check if an actor exists in the cache + * Check if a worker exists in the cache */ - hasActor(actorId: string): boolean { - return this.#stateCache.has(actorId); + hasWorker(workerId: string): boolean { + return this.#stateCache.has(workerId); } /** - * Ensure an actor exists, throwing if it doesn't + * Ensure a worker exists, throwing if it doesn't */ - ensureActorExists(actorId: string): void { - if (!this.hasActor(actorId)) { - throw new Error(`Actor does not exist for ID: ${actorId}`); + ensureWorkerExists(workerId: string): void { + if (!this.hasWorker(workerId)) { + throw new Error(`Worker does not exist for ID: ${workerId}`); } } /** - * Create an actor + * Create a worker */ - async createActor( - actorId: string, + async createWorker( + workerId: string, name: string, - key: ActorKey, + key: WorkerKey, input?: unknown, ): Promise { - // Check if actor already exists - if (this.hasActor(actorId)) { - throw new Error(`Actor already exists for ID: ${actorId}`); + // Check if worker already exists + if (this.hasWorker(workerId)) { + throw new Error(`Worker already exists for ID: ${workerId}`); } // Create initial state - const newState: ActorState = { - id: actorId, + const newState: WorkerState = { + id: workerId, name, key, persistedData: undefined, @@ -199,30 +199,30 @@ export class FileSystemGlobalState { }; // Cache the state - this.#stateCache.set(actorId, newState); + this.#stateCache.set(workerId, newState); // Save to disk - await this.saveActorState(actorId); + await this.saveWorkerState(workerId); } /** - * Find actor by name and key + * Find worker by name and key */ - findActorByNameAndKey(name: string, key: ActorKey): ActorState | undefined { - // NOTE: This is a slow implementation that checks each actor individually. + findWorkerByNameAndKey(name: string, key: WorkerKey): WorkerState | undefined { + // NOTE: This is a slow implementation that checks each worker individually. // This can be optimized with an index in the future. - return this.findActor((actor) => { - if (actor.name !== name) return false; + return this.findWorker((worker) => { + if (worker.name !== name) return false; - // If actor doesn't have a key, it's not a match - if (!actor.key || actor.key.length !== key.length) { + // If worker doesn't have a key, it's not a match + if (!worker.key || worker.key.length !== key.length) { return false; } - // Check if all elements in key are in actor.key + // Check if all elements in key are in worker.key for (let i = 0; i < key.length; i++) { - if (key[i] !== actor.key[i]) { + if (key[i] !== worker.key[i]) { return false; } } @@ -231,22 +231,22 @@ export class FileSystemGlobalState { } /** - * Find actor by filter function + * Find worker by filter function */ - findActor(filter: (actor: ActorState) => boolean): ActorState | undefined { - for (const actor of this.#stateCache.values()) { - if (filter(actor)) { - return actor; + findWorker(filter: (worker: WorkerState) => boolean): WorkerState | undefined { + for (const worker of this.#stateCache.values()) { + if (filter(worker)) { + return worker; } } return undefined; } /** - * Get all actors from the cache + * Get all workers from the cache */ - getAllActors(): ActorState[] { - // Return all actors from the cache + getAllWorkers(): WorkerState[] { + // Return all workers from the cache return Array.from(this.#stateCache.values()); } } diff --git a/packages/drivers/file-system/src/log.ts b/packages/drivers/file-system/src/log.ts index 24b8c22a4..a782a9076 100644 --- a/packages/drivers/file-system/src/log.ts +++ b/packages/drivers/file-system/src/log.ts @@ -1,4 +1,4 @@ -import { getLogger } from "@rivetkit/actor/log"; +import { getLogger } from "rivetkit/log"; export const LOGGER_NAME = "driver-fs"; diff --git a/packages/drivers/file-system/src/manager.ts b/packages/drivers/file-system/src/manager.ts index b04e2fb98..80497bd2d 100644 --- a/packages/drivers/file-system/src/manager.ts +++ b/packages/drivers/file-system/src/manager.ts @@ -4,15 +4,15 @@ import type { GetForIdInput, GetWithKeyInput, ManagerDriver, - ActorOutput, + WorkerOutput, CreateInput, -} from "@rivetkit/actor/driver-helpers"; -import { ActorAlreadyExists } from "@rivetkit/actor/errors"; +} from "rivetkit/driver-helpers"; +import { WorkerAlreadyExists } from "rivetkit/errors"; import { logger } from "./log"; import type { FileSystemGlobalState } from "./global-state"; -import { ActorState } from "./global-state"; -import type { ActorCoreApp } from "@rivetkit/actor"; -import { ManagerInspector } from "@rivetkit/actor/inspector"; +import { WorkerState } from "./global-state"; +import type { WorkerCoreApp } from "rivetkit"; +import { ManagerInspector } from "rivetkit/inspector"; export class FileSystemManagerDriver implements ManagerDriver { #state: FileSystemGlobalState; @@ -21,35 +21,35 @@ export class FileSystemManagerDriver implements ManagerDriver { * @internal */ inspector: ManagerInspector = new ManagerInspector(this, { - getAllActors: () => this.#state.getAllActors(), - getAllTypesOfActors: () => Object.keys(this.app.config.actors), + getAllWorkers: () => this.#state.getAllWorkers(), + getAllTypesOfWorkers: () => Object.keys(this.app.config.workers), }); constructor( - private readonly app: ActorCoreApp, + private readonly app: WorkerCoreApp, state: FileSystemGlobalState, ) { this.#state = state; } - async getForId({ actorId }: GetForIdInput): Promise { - // Validate the actor exists - if (!this.#state.hasActor(actorId)) { + async getForId({ workerId }: GetForIdInput): Promise { + // Validate the worker exists + if (!this.#state.hasWorker(workerId)) { return undefined; } try { - // Load actor state - const state = this.#state.loadActorState(actorId); + // Load worker state + const state = this.#state.loadWorkerState(workerId); return { - actorId, + workerId, name: state.name, key: state.key, meta: undefined, }; } catch (error) { - logger().error("failed to read actor state", { actorId, error }); + logger().error("failed to read worker state", { workerId, error }); return undefined; } } @@ -57,15 +57,15 @@ export class FileSystemManagerDriver implements ManagerDriver { async getWithKey({ name, key, - }: GetWithKeyInput): Promise { - // Search through all actors to find a match - const actor = this.#state.findActorByNameAndKey(name, key); + }: GetWithKeyInput): Promise { + // Search through all workers to find a match + const worker = this.#state.findWorkerByNameAndKey(name, key); - if (actor) { + if (worker) { return { - actorId: actor.id, + workerId: worker.id, name, - key: actor.key, + key: worker.key, meta: undefined, }; } @@ -75,31 +75,31 @@ export class FileSystemManagerDriver implements ManagerDriver { async getOrCreateWithKey( input: GetOrCreateWithKeyInput, - ): Promise { - // First try to get the actor without locking + ): Promise { + // First try to get the worker without locking const getOutput = await this.getWithKey(input); if (getOutput) { return getOutput; } else { - return await this.createActor(input); + return await this.createWorker(input); } } - 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); + 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); } - const actorId = crypto.randomUUID(); - await this.#state.createActor(actorId, name, key, input); + const workerId = crypto.randomUUID(); + await this.#state.createWorker(workerId, name, key, input); - // Notify inspector about actor changes - this.inspector.onActorsChange(this.#state.getAllActors()); + // Notify inspector about worker changes + this.inspector.onWorkersChange(this.#state.getAllWorkers()); return { - actorId, + workerId, name, key, meta: undefined, diff --git a/packages/drivers/file-system/src/mod.ts b/packages/drivers/file-system/src/mod.ts index e2edfc32d..51587a1c6 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 { FileSystemActorDriver } from "./actor"; +export { FileSystemWorkerDriver } from "./worker"; 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 bc0f481f0..2cbb44d4b 100644 --- a/packages/drivers/file-system/src/utils.ts +++ b/packages/drivers/file-system/src/utils.ts @@ -5,7 +5,7 @@ import * as crypto from "crypto"; import envPaths from "env-paths"; // Get platform-specific data directory -const paths = envPaths("@rivetkit/actor", { suffix: "" }); +const paths = envPaths("rivetkit", { suffix: "" }); /** * Create a hash for a path, normalizing it first @@ -37,10 +37,10 @@ export function getStoragePath(customPath?: string): string { } /** - * Get actor's storage directory + * Get worker's storage directory */ -export function getActorStoragePath(baseDir: string, actorId: string): string { - return path.join(baseDir, "actors", actorId); +export function getWorkerStoragePath(baseDir: string, workerId: string): string { + return path.join(baseDir, "workers", workerId); } /** diff --git a/packages/drivers/file-system/src/worker.ts b/packages/drivers/file-system/src/worker.ts new file mode 100644 index 000000000..93ebf3893 --- /dev/null +++ b/packages/drivers/file-system/src/worker.ts @@ -0,0 +1,48 @@ +import type { WorkerDriver, AnyWorkerInstance } from "rivetkit/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); + } +} diff --git a/packages/drivers/file-system/tests/driver-tests.test.ts b/packages/drivers/file-system/tests/driver-tests.test.ts index 03edce237..3033c4b8c 100644 --- a/packages/drivers/file-system/tests/driver-tests.test.ts +++ b/packages/drivers/file-system/tests/driver-tests.test.ts @@ -1,9 +1,9 @@ import { runDriverTests, createTestRuntime, -} from "@rivetkit/actor/driver-test-suite"; +} from "rivetkit/driver-test-suite"; import { - FileSystemActorDriver, + FileSystemWorkerDriver, FileSystemManagerDriver, FileSystemGlobalState, } from "../src/mod"; @@ -17,13 +17,13 @@ runDriverTests({ // Create a unique temp directory for each test const testDir = path.join( os.tmpdir(), - `actor-core-fs-tests-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, + `worker-core-fs-tests-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, ); await fs.mkdir(testDir, { recursive: true }); const fileSystemState = new FileSystemGlobalState(testDir); return { - actorDriver: new FileSystemActorDriver(fileSystemState), + workerDriver: new FileSystemWorkerDriver(fileSystemState), managerDriver: new FileSystemManagerDriver(app, fileSystemState), async cleanup() { await fs.rmdir(testDir, { recursive: true }); diff --git a/packages/drivers/memory/package.json b/packages/drivers/memory/package.json index 77350ffa0..50460548e 100644 --- a/packages/drivers/memory/package.json +++ b/packages/drivers/memory/package.json @@ -24,10 +24,10 @@ "test": "vitest run" }, "peerDependencies": { - "@rivetkit/actor": "*" + "rivetkit": "*" }, "devDependencies": { - "@rivetkit/actor": "workspace:*", + "rivetkit": "workspace:*", "tsup": "^8.4.0", "typescript": "^5.5.2" }, diff --git a/packages/drivers/memory/src/actor.ts b/packages/drivers/memory/src/actor.ts deleted file mode 100644 index 6157c056b..000000000 --- a/packages/drivers/memory/src/actor.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { ActorDriver, AnyActorInstance } from "@rivetkit/actor/driver-helpers"; -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); - } -} diff --git a/packages/drivers/memory/src/global-state.ts b/packages/drivers/memory/src/global-state.ts index 1cf2ac649..c9964a30c 100644 --- a/packages/drivers/memory/src/global-state.ts +++ b/packages/drivers/memory/src/global-state.ts @@ -1,70 +1,70 @@ -import type { ActorKey } from "@rivetkit/actor"; +import type { WorkerKey } from "rivetkit"; -export interface ActorState { +export interface WorkerState { id: string; name: string; - key: ActorKey; + key: WorkerKey; persistedData: unknown; input?: unknown; } export class MemoryGlobalState { - #actors: Map = new Map(); + #workers: Map = new Map(); - #getActor(actorId: string): ActorState { - const actor = this.#actors.get(actorId); - if (!actor) { - throw new Error(`Actor does not exist for ID: ${actorId}`); + #getWorker(workerId: string): WorkerState { + const worker = this.#workers.get(workerId); + if (!worker) { + throw new Error(`Worker does not exist for ID: ${workerId}`); } - return actor; + return worker; } - readInput(actorId: string): unknown | undefined { - return this.#getActor(actorId).input; + readInput(workerId: string): unknown | undefined { + return this.#getWorker(workerId).input; } - readPersistedData(actorId: string): unknown | undefined { - return this.#getActor(actorId).persistedData; + readPersistedData(workerId: string): unknown | undefined { + return this.#getWorker(workerId).persistedData; } - writePersistedData(actorId: string, data: unknown) { - this.#getActor(actorId).persistedData = data; + writePersistedData(workerId: string, data: unknown) { + this.#getWorker(workerId).persistedData = data; } - createActor( - actorId: string, + createWorker( + workerId: string, name: string, - key: ActorKey, + key: WorkerKey, input?: unknown, ): void { - // Create actor state if it doesn't exist - if (!this.#actors.has(actorId)) { - this.#actors.set(actorId, { - id: actorId, + // Create worker state if it doesn't exist + if (!this.#workers.has(workerId)) { + this.#workers.set(workerId, { + id: workerId, name, key, persistedData: undefined, input, }); } else { - throw new Error(`Actor already exists for ID: ${actorId}`); + throw new Error(`Worker already exists for ID: ${workerId}`); } } - findActor(filter: (actor: ActorState) => boolean): ActorState | undefined { - for (const actor of this.#actors.values()) { - if (filter(actor)) { - return actor; + findWorker(filter: (worker: WorkerState) => boolean): WorkerState | undefined { + for (const worker of this.#workers.values()) { + if (filter(worker)) { + return worker; } } return undefined; } - getActor(actorId: string): ActorState | undefined { - return this.#actors.get(actorId); + getWorker(workerId: string): WorkerState | undefined { + return this.#workers.get(workerId); } - getAllActors(): ActorState[] { - return Array.from(this.#actors.values()); + getAllWorkers(): WorkerState[] { + return Array.from(this.#workers.values()); } } diff --git a/packages/drivers/memory/src/log.ts b/packages/drivers/memory/src/log.ts index 022f002f9..09a995f51 100644 --- a/packages/drivers/memory/src/log.ts +++ b/packages/drivers/memory/src/log.ts @@ -1,4 +1,4 @@ -import { getLogger } from "@rivetkit/actor/log"; +import { getLogger } from "rivetkit/log"; export const LOGGER_NAME = "driver-memory"; diff --git a/packages/drivers/memory/src/manager.ts b/packages/drivers/memory/src/manager.ts index c367df3b3..7251496dd 100644 --- a/packages/drivers/memory/src/manager.ts +++ b/packages/drivers/memory/src/manager.ts @@ -3,14 +3,14 @@ import type { GetForIdInput, GetWithKeyInput, GetOrCreateWithKeyInput, - ActorOutput, + WorkerOutput, ManagerDriver, -} from "@rivetkit/actor/driver-helpers"; -import { ActorAlreadyExists } from "@rivetkit/actor/errors"; +} from "rivetkit/driver-helpers"; +import { WorkerAlreadyExists } from "rivetkit/errors"; import type { MemoryGlobalState } from "./global-state"; import * as crypto from "node:crypto"; -import { ManagerInspector } from "@rivetkit/actor/inspector"; -import type { ActorCoreApp } from "@rivetkit/actor"; +import { ManagerInspector } from "rivetkit/inspector"; +import type { WorkerCoreApp } from "rivetkit"; export class MemoryManagerDriver implements ManagerDriver { #state: MemoryGlobalState; @@ -19,28 +19,28 @@ export class MemoryManagerDriver implements ManagerDriver { * @internal */ inspector: ManagerInspector = new ManagerInspector(this, { - getAllActors: () => this.#state.getAllActors(), - getAllTypesOfActors: () => Object.keys(this.app.config.actors), + getAllWorkers: () => this.#state.getAllWorkers(), + getAllTypesOfWorkers: () => Object.keys(this.app.config.workers), }); constructor( - private readonly app: ActorCoreApp, + private readonly app: WorkerCoreApp, state: MemoryGlobalState, ) { this.#state = state; } - async getForId({ actorId }: GetForIdInput): Promise { - // Validate the actor exists - const actor = this.#state.getActor(actorId); - if (!actor) { + async getForId({ workerId }: GetForIdInput): Promise { + // Validate the worker exists + const worker = this.#state.getWorker(workerId); + if (!worker) { return undefined; } return { - actorId: actor.id, - name: actor.name, - key: actor.key, + workerId: worker.id, + name: worker.name, + key: worker.key, meta: undefined, }; } @@ -48,33 +48,33 @@ export class MemoryManagerDriver implements ManagerDriver { async getWithKey({ name, key, - }: GetWithKeyInput): Promise { - // NOTE: This is a slow implementation that checks each actor individually. + }: GetWithKeyInput): Promise { + // NOTE: This is a slow implementation that checks each worker individually. // This can be optimized with an index in the future. - // Search through all actors to find a match - const actor = this.#state.findActor((actor) => { - if (actor.name !== name) return false; + // Search through all workers to find a match + const worker = this.#state.findWorker((worker) => { + if (worker.name !== name) return false; - // If actor doesn't have a key, it's not a match - if (!actor.key || actor.key.length !== key.length) { + // If worker doesn't have a key, it's not a match + if (!worker.key || worker.key.length !== key.length) { return false; } - // Check if all elements in key are in actor.key + // Check if all elements in key are in worker.key for (let i = 0; i < key.length; i++) { - if (key[i] !== actor.key[i]) { + if (key[i] !== worker.key[i]) { return false; } } return true; }); - if (actor) { + if (worker) { return { - actorId: actor.id, + workerId: worker.id, name, - key: actor.key, + key: worker.key, meta: undefined, }; } @@ -84,27 +84,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.createActor(input); + return await this.createWorker(input); } } - 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); + 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); } - const actorId = crypto.randomUUID(); - this.#state.createActor(actorId, name, key, input); + const workerId = crypto.randomUUID(); + this.#state.createWorker(workerId, name, key, input); - this.inspector.onActorsChange(this.#state.getAllActors()); + this.inspector.onWorkersChange(this.#state.getAllWorkers()); - return { actorId, name, key, meta: undefined }; + return { workerId, name, key, meta: undefined }; } } diff --git a/packages/drivers/memory/src/mod.ts b/packages/drivers/memory/src/mod.ts index 522714b88..0a3bccc44 100644 --- a/packages/drivers/memory/src/mod.ts +++ b/packages/drivers/memory/src/mod.ts @@ -1,3 +1,3 @@ export { MemoryGlobalState } from "./global-state"; -export { MemoryActorDriver } from "./actor"; +export { MemoryWorkerDriver } from "./worker"; export { MemoryManagerDriver } from "./manager"; diff --git a/packages/drivers/memory/src/worker.ts b/packages/drivers/memory/src/worker.ts new file mode 100644 index 000000000..f964c4267 --- /dev/null +++ b/packages/drivers/memory/src/worker.ts @@ -0,0 +1,35 @@ +import type { WorkerDriver, AnyWorkerInstance } from "rivetkit/driver-helpers"; +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); + } +} diff --git a/packages/drivers/memory/tests/driver-tests.test.ts b/packages/drivers/memory/tests/driver-tests.test.ts index f4f2d85ba..41964dd31 100644 --- a/packages/drivers/memory/tests/driver-tests.test.ts +++ b/packages/drivers/memory/tests/driver-tests.test.ts @@ -1,6 +1,6 @@ -import { runDriverTests, createTestRuntime } from "@rivetkit/actor/driver-test-suite"; +import { runDriverTests, createTestRuntime } from "rivetkit/driver-test-suite"; import { - MemoryActorDriver, + MemoryWorkerDriver, MemoryManagerDriver, MemoryGlobalState, } from "../src/mod"; @@ -10,7 +10,7 @@ runDriverTests({ return await createTestRuntime(appPath, async (app) => { const memoryState = new MemoryGlobalState(); return { - actorDriver: new MemoryActorDriver(memoryState), + workerDriver: new MemoryWorkerDriver(memoryState), managerDriver: new MemoryManagerDriver(app, memoryState), }; }); diff --git a/packages/drivers/redis/package.json b/packages/drivers/redis/package.json index a4d95b299..1b11e3f8f 100644 --- a/packages/drivers/redis/package.json +++ b/packages/drivers/redis/package.json @@ -56,11 +56,11 @@ "test": "vitest run" }, "peerDependencies": { - "@rivetkit/actor": "workspace:*" + "rivetkit": "workspace:*" }, "devDependencies": { - "@rivetkit/actor": "workspace:*", "@types/node": "^22.13.1", + "rivetkit": "workspace:*", "tsup": "^8.4.0", "typescript": "^5.5.2", "vitest": "^3.1.1" diff --git a/packages/drivers/redis/src/actor.ts b/packages/drivers/redis/src/actor.ts deleted file mode 100644 index 1244d35d8..000000000 --- a/packages/drivers/redis/src/actor.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { ActorDriver, AnyActorInstance } from "@rivetkit/actor/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); - } -} diff --git a/packages/drivers/redis/src/coordinate.ts b/packages/drivers/redis/src/coordinate.ts index ebe9f3184..d5782042c 100644 --- a/packages/drivers/redis/src/coordinate.ts +++ b/packages/drivers/redis/src/coordinate.ts @@ -1,11 +1,11 @@ import type { AttemptAcquireLease, ExtendLeaseOutput, - GetActorLeaderOutput, + GetWorkerLeaderOutput, NodeMessageCallback, CoordinateDriver, - StartActorAndAcquireLeaseOutput, -} from "@rivetkit/actor/driver-helpers"; + StartWorkerAndAcquireLeaseOutput, +} from "rivetkit/driver-helpers"; import type Redis from "ioredis"; import { KEYS, PUBSUB } from "./keys"; import dedent from "dedent"; @@ -13,21 +13,21 @@ import dedent from "dedent"; // Define custom commands for ioredis declare module "ioredis" { interface RedisCommander { - actorPeerAcquireLease( + workerPeerAcquireLease( nodeKey: string, nodeId: string, leaseDuration: number, ): Promise; - actorPeerExtendLease( + workerPeerExtendLease( nodeKey: string, nodeId: string, leaseDuration: number, ): Promise; - actorPeerReleaseLease(nodeKey: string, nodeId: string): Promise; + workerPeerReleaseLease(nodeKey: string, nodeId: string): Promise; } interface ChainableCommander { - actorPeerAcquireLease( + workerPeerAcquireLease( nodeKey: string, nodeId: string, leaseDuration: number, @@ -66,35 +66,35 @@ export class RedisCoordinateDriver implements CoordinateDriver { await this.#redis.publish(PUBSUB.node(targetNodeId), message); } - async getActorLeader(actorId: string): Promise { + async getWorkerLeader(workerId: string): Promise { // Get current leader from Redis const [initialized, nodeId] = await this.#redis.mget([ - KEYS.ACTOR.initialized(actorId), - KEYS.ACTOR.LEASE.node(actorId), + KEYS.WORKER.initialized(workerId), + KEYS.WORKER.LEASE.node(workerId), ]); if (!initialized) { - return { actor: undefined }; + return { worker: undefined }; } return { - actor: { + worker: { leaderNodeId: nodeId || undefined, }, }; } - async startActorAndAcquireLease( - actorId: string, + async startWorkerAndAcquireLease( + workerId: string, selfNodeId: string, leaseDuration: number, - ): Promise { - // Execute multi to get actor info and attempt to acquire lease in a single operation + ): Promise { + // Execute multi to get worker info and attempt to acquire lease in a single operation const execRes = await this.#redis .multi() - .mget([KEYS.ACTOR.initialized(actorId), KEYS.ACTOR.metadata(actorId)]) - .actorPeerAcquireLease( - KEYS.ACTOR.LEASE.node(actorId), + .mget([KEYS.WORKER.initialized(workerId), KEYS.WORKER.metadata(workerId)]) + .workerPeerAcquireLease( + KEYS.WORKER.LEASE.node(workerId), selfNodeId, leaseDuration, ) @@ -113,15 +113,15 @@ export class RedisCoordinateDriver implements CoordinateDriver { const leaderNodeId = leaseRes as unknown as string; if (!initialized) { - return { actor: undefined }; + return { worker: undefined }; } // Parse metadata if present - if (!metadataRaw) throw new Error("Actor should have metadata if initialized."); + if (!metadataRaw) throw new Error("Worker should have metadata if initialized."); const metadata = JSON.parse(metadataRaw); return { - actor: { + worker: { name: metadata.name, key: metadata.key, leaderNodeId, @@ -130,12 +130,12 @@ export class RedisCoordinateDriver implements CoordinateDriver { } async extendLease( - actorId: string, + workerId: string, selfNodeId: string, leaseDuration: number, ): Promise { - const res = await this.#redis.actorPeerExtendLease( - KEYS.ACTOR.LEASE.node(actorId), + const res = await this.#redis.workerPeerExtendLease( + KEYS.WORKER.LEASE.node(workerId), selfNodeId, leaseDuration, ); @@ -146,12 +146,12 @@ export class RedisCoordinateDriver implements CoordinateDriver { } async attemptAcquireLease( - actorId: string, + workerId: string, selfNodeId: string, leaseDuration: number, ): Promise { - const newLeaderNodeId = await this.#redis.actorPeerAcquireLease( - KEYS.ACTOR.LEASE.node(actorId), + const newLeaderNodeId = await this.#redis.workerPeerAcquireLease( + KEYS.WORKER.LEASE.node(workerId), selfNodeId, leaseDuration, ); @@ -161,16 +161,16 @@ export class RedisCoordinateDriver implements CoordinateDriver { }; } - async releaseLease(actorId: string, nodeId: string): Promise { - await this.#redis.actorPeerReleaseLease( - KEYS.ACTOR.LEASE.node(actorId), + async releaseLease(workerId: string, nodeId: string): Promise { + await this.#redis.workerPeerReleaseLease( + KEYS.WORKER.LEASE.node(workerId), nodeId, ); } #defineRedisScripts() { // Add custom Lua script commands to Redis - this.#redis.defineCommand("actorPeerAcquireLease", { + this.#redis.defineCommand("workerPeerAcquireLease", { numberOfKeys: 1, lua: dedent` -- Get the current value of the key @@ -189,7 +189,7 @@ export class RedisCoordinateDriver implements CoordinateDriver { `, }); - this.#redis.defineCommand("actorPeerExtendLease", { + this.#redis.defineCommand("workerPeerExtendLease", { 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("actorPeerReleaseLease", { + this.#redis.defineCommand("workerPeerReleaseLease", { 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 db16aaa2e..11bb81e62 100644 --- a/packages/drivers/redis/src/keys.ts +++ b/packages/drivers/redis/src/keys.ts @@ -1,17 +1,17 @@ export const KEYS = { - ACTOR: { + WORKER: { // KEY - initialized: (actorId: string) => `actor:${actorId}:initialized`, + initialized: (workerId: string) => `worker:${workerId}:initialized`, LEASE: { // KEY (expire) = node ID - node: (actorId: string) => `actor:${actorId}:lease:node`, + node: (workerId: string) => `worker:${workerId}:lease:node`, }, // KEY - metadata: (actorId: string) => `actor:${actorId}:metadata`, + metadata: (workerId: string) => `worker:${workerId}:metadata`, // KEY - persistedData: (actorId: string) => `actor:${actorId}:persisted_data`, + persistedData: (workerId: string) => `worker:${workerId}:persisted_data`, // KEY - input: (actorId: string) => `actor:${actorId}:input`, + input: (workerId: string) => `worker:${workerId}:input`, }, }; diff --git a/packages/drivers/redis/src/log.ts b/packages/drivers/redis/src/log.ts index 343b780a5..7b031ee1d 100644 --- a/packages/drivers/redis/src/log.ts +++ b/packages/drivers/redis/src/log.ts @@ -1,4 +1,4 @@ -import { getLogger } from "@rivetkit/actor/log"; +import { getLogger } from "rivetkit/log"; export const LOGGER_NAME = "driver-redis"; diff --git a/packages/drivers/redis/src/manager.ts b/packages/drivers/redis/src/manager.ts index 5885e49eb..9a92ad4f4 100644 --- a/packages/drivers/redis/src/manager.ts +++ b/packages/drivers/redis/src/manager.ts @@ -1,19 +1,19 @@ import type { CreateInput, - ActorOutput, + WorkerOutput, GetForIdInput, GetOrCreateWithKeyInput, GetWithKeyInput, ManagerDriver, -} from "@rivetkit/actor/driver-helpers"; -import { ActorAlreadyExists } from "@rivetkit/actor/errors"; +} from "rivetkit/driver-helpers"; +import { WorkerAlreadyExists } from "rivetkit/errors"; import type Redis from "ioredis"; import * as crypto from "node:crypto"; import { KEYS } from "./keys"; -import { ManagerInspector } from "@rivetkit/actor/inspector"; -import type { ActorCoreApp } from "@rivetkit/actor"; +import { ManagerInspector } from "rivetkit/inspector"; +import type { WorkerCoreApp } from "rivetkit"; -interface Actor { +interface Worker { id: string; name: string; key: string[]; @@ -23,42 +23,42 @@ interface Actor { } /** - * Redis Manager Driver for Actor-Core - * Handles actor creation and lookup by ID or key + * Redis Manager Driver for Worker-Core + * Handles worker creation and lookup by ID or key */ export class RedisManagerDriver implements ManagerDriver { #redis: Redis; - #app?: ActorCoreApp; + #app?: WorkerCoreApp; /** * @internal */ inspector: ManagerInspector = new ManagerInspector(this, { - getAllActors: () => { - // Create a function that returns an array of actors directly + getAllWorkers: () => { + // Create a function that returns an array of workers directly // Not returning a Promise since the ManagerInspector expects a synchronous function - const actors: Actor[] = []; + const workers: Worker[] = []; // Return empty array since we can't do async operations here - // The actual data will be fetched when needed by calling getAllActors() manually - return actors; + // The actual data will be fetched when needed by calling getAllWorkers() manually + return workers; }, - getAllTypesOfActors: () => { + getAllTypesOfWorkers: () => { if (!this.#app) return []; - return Object.keys(this.#app.config.actors); + return Object.keys(this.#app.config.workers); }, }); - constructor(redis: Redis, app?: ActorCoreApp) { + constructor(redis: Redis, app?: WorkerCoreApp) { this.#redis = redis; this.#app = app; } - async getForId({ actorId }: GetForIdInput): Promise { + async getForId({ workerId }: GetForIdInput): Promise { // Get metadata from Redis - const metadataStr = await this.#redis.get(KEYS.ACTOR.metadata(actorId)); + const metadataStr = await this.#redis.get(KEYS.WORKER.metadata(workerId)); - // If the actor doesn't exist, return undefined + // If the worker doesn't exist, return undefined if (!metadataStr) { return undefined; } @@ -67,7 +67,7 @@ export class RedisManagerDriver implements ManagerDriver { const { name, key } = metadata; return { - actorId, + workerId, name, key, meta: undefined, @@ -77,99 +77,99 @@ export class RedisManagerDriver implements ManagerDriver { async getWithKey({ name, key, - }: 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); + }: 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); - if (!actorId) { + if (!workerId) { return undefined; } - return this.getForId({ actorId }); + return this.getForId({ workerId }); } 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.createActor(input); + return await this.createWorker(input); } } - 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); + 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); } - const actorId = crypto.randomUUID(); - const actorKeyRedisKey = this.#generateActorKeyRedisKey(name, key); + const workerId = crypto.randomUUID(); + const workerKeyRedisKey = this.#generateWorkerKeyRedisKey(name, key); // Use a transaction to ensure all operations are atomic const pipeline = this.#redis.multi(); - // 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)); + // 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)); - // Create direct lookup by name+key -> actorId - pipeline.set(actorKeyRedisKey, actorId); + // Create direct lookup by name+key -> workerId + pipeline.set(workerKeyRedisKey, workerId); // Execute all commands atomically await pipeline.exec(); - // Notify inspector of actor creation - this.inspector.onActorsChange([ + // Notify inspector of worker creation + this.inspector.onWorkersChange([ { - id: actorId, + id: workerId, name, key, }, ]); return { - actorId, + workerId, name, key, meta: undefined, }; } - // Helper method to get all actors (for inspector) - private async getAllActors(): Promise { + // Helper method to get all workers (for inspector) + private async getAllWorkers(): Promise { const keys = await this.#redis.keys( - KEYS.ACTOR.metadata("*").replace(/:metadata$/, ""), + KEYS.WORKER.metadata("*").replace(/:metadata$/, ""), ); - const actorIds = keys.map((key) => key.split(":")[1]); + const workerIds = keys.map((key) => key.split(":")[1]); - const actors: Actor[] = []; - for (const actorId of actorIds) { - const metadataStr = await this.#redis.get(KEYS.ACTOR.metadata(actorId)); + const workers: Worker[] = []; + for (const workerId of workerIds) { + const metadataStr = await this.#redis.get(KEYS.WORKER.metadata(workerId)); if (metadataStr) { const metadata = JSON.parse(metadataStr); - actors.push({ - id: actorId, + workers.push({ + id: workerId, name: metadata.name, key: metadata.key || [], }); } } - return actors; + return workers; } - // Generate a Redis key for looking up an actor by name+key - #generateActorKeyRedisKey(name: string, key: string[]): string { - // Base prefix for actor key lookups - let redisKey = `actor_by_key:${this.#escapeRedisKey(name)}`; + // 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)}`; // 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 c6fd7f816..47887de25 100644 --- a/packages/drivers/redis/src/mod.ts +++ b/packages/drivers/redis/src/mod.ts @@ -1,3 +1,3 @@ -export { RedisActorDriver } from "./actor"; +export { RedisWorkerDriver } from "./worker"; export { RedisManagerDriver } from "./manager"; export { RedisCoordinateDriver } from "./coordinate"; \ No newline at end of file diff --git a/packages/drivers/redis/src/worker.ts b/packages/drivers/redis/src/worker.ts new file mode 100644 index 000000000..d9df84e14 --- /dev/null +++ b/packages/drivers/redis/src/worker.ts @@ -0,0 +1,46 @@ +import type { WorkerDriver, AnyWorkerInstance } from "rivetkit/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); + } +} diff --git a/packages/drivers/redis/tests/driver-tests.test.ts b/packages/drivers/redis/tests/driver-tests.test.ts index 747f6ff11..590ac9387 100644 --- a/packages/drivers/redis/tests/driver-tests.test.ts +++ b/packages/drivers/redis/tests/driver-tests.test.ts @@ -2,16 +2,16 @@ //import { // runDriverTests, // createTestRuntime, -//} from "@rivetkit/actor/driver-test-suite"; +//} from "rivetkit/driver-test-suite"; //import { -// RedisActorDriver, +// RedisWorkerDriver, // RedisCoordinateDriver, // RedisManagerDriver, //} from "../src/mod"; //import Redis from "ioredis"; //import { $ } from "zx"; //import { expect, test } from "vitest"; -//import { getPort } from "@rivetkit/actor/test"; +//import { getPort } from "rivetkit/test"; // //async function startValkeyContainer(): Promise<{ // port: number; @@ -87,7 +87,7 @@ // }); // // return { -// actorDriver: new RedisActorDriver(redisClient), +// workerDriver: new RedisWorkerDriver(redisClient), // managerDriver: new RedisManagerDriver(redisClient, app), // coordinateDriver: new RedisCoordinateDriver(redisClient), // async cleanup() { diff --git a/packages/frameworks/framework-base/package.json b/packages/frameworks/framework-base/package.json index dedcd75cc..7cd2d60da 100644 --- a/packages/frameworks/framework-base/package.json +++ b/packages/frameworks/framework-base/package.json @@ -25,10 +25,10 @@ "check-types": "tsc --noEmit" }, "peerDependencies": { - "@rivetkit/actor": "*" + "rivetkit": "*" }, "devDependencies": { - "@rivetkit/actor": "workspace:*", + "rivetkit": "workspace:*", "tsup": "^8.3.6", "typescript": "^5.5.2", "vitest": "^3.1.1" diff --git a/packages/frameworks/framework-base/src/mod.ts b/packages/frameworks/framework-base/src/mod.ts index d13167e42..31b901095 100644 --- a/packages/frameworks/framework-base/src/mod.ts +++ b/packages/frameworks/framework-base/src/mod.ts @@ -1,11 +1,11 @@ //import type { -// ActorConn, -// ActorAccessor, +// WorkerConn, +// WorkerAccessor, // ExtractAppFromClient, -// ExtractActorsFromApp, +// ExtractWorkersFromApp, // ClientRaw, -// AnyActorDefinition, -//} from "@rivetkit/actor/client"; +// AnyWorkerDefinition, +//} from "rivetkit/client"; // ///** // * Shallow compare objects. @@ -35,75 +35,75 @@ //} // //namespace State { -// export type Value = -// | { state: "init"; actor: undefined; isLoading: false } -// | { state: "creating"; actor: undefined; isLoading: true } -// | { state: "created"; actor: ActorConn; isLoading: false } -// | { state: "error"; error: unknown; actor: undefined; isLoading: false }; +// export type Value = +// | { state: "init"; worker: undefined; isLoading: false } +// | { state: "creating"; worker: undefined; isLoading: true } +// | { state: "created"; worker: WorkerConn; isLoading: false } +// | { state: "error"; error: unknown; worker: undefined; isLoading: false }; // -// export const INIT = (): Value => ({ +// export const INIT = (): Value => ({ // state: "init", -// actor: undefined, +// worker: undefined, // isLoading: false, // }); -// export const CREATING = (): Value => ({ +// export const CREATING = (): Value => ({ // state: "creating", -// actor: undefined, +// worker: undefined, // isLoading: true, // }); -// export const CREATED = ( -// actor: ActorConn, +// export const CREATED = ( +// worker: WorkerConn, // ): Value => ({ // state: "created", -// actor, +// worker, // isLoading: false, // }); -// export const ERRORED = ( +// export const ERRORED = ( // error: unknown, // ): Value => ({ // state: "error", -// actor: undefined, +// worker: undefined, // error, // isLoading: false, // }); //} // -//export class ActorManager< +//export class WorkerManager< // C extends ClientRaw, // App extends ExtractAppFromClient, -// Registry extends ExtractActorsFromApp, -// ActorName extends keyof Registry, -// AD extends Registry[ActorName], +// Registry extends ExtractWorkersFromApp, +// WorkerName extends keyof Registry, +// AD extends Registry[WorkerName], //> { // #client: C; -// #name: Exclude; -// #options: Parameters["connect"]>; +// #name: Exclude; +// #options: Parameters["connect"]>; // // #listeners: (() => void)[] = []; // // #state: State.Value = State.INIT(); // -// #createPromise: Promise> | null = null; +// #createPromise: Promise> | null = null; // // constructor( // client: C, -// name: Exclude, -// options: Parameters["connect"]>, +// name: Exclude, +// options: Parameters["connect"]>, // ) { // this.#client = client; // this.#name = name; // this.#options = options; // } // -// setOptions(options: Parameters["connect"]>) { +// setOptions(options: Parameters["connect"]>) { // if (shallowEqualObjects(options, this.#options)) { -// if (!this.#state.actor) { +// if (!this.#state.worker) { // this.create(); // } // return; // } // -// this.#state.actor?.dispose(); +// this.#state.worker?.dispose(); // // this.#state = { ...State.INIT() }; // this.#options = options; @@ -119,8 +119,8 @@ // this.#update(); // try { // this.#createPromise = this.#client.connect(this.#name, ...this.#options); -// const actor = (await this.#createPromise) as ActorConn; -// this.#state = { ...State.CREATED(actor) }; +// const worker = (await this.#createPromise) as WorkerConn; +// this.#state = { ...State.CREATED(worker) }; // this.#createPromise = null; // } catch (e) { // this.#state = { ...State.ERRORED(e) }; diff --git a/packages/frameworks/react/README.md b/packages/frameworks/react/README.md index e3791c063..d55c3c7eb 100644 --- a/packages/frameworks/react/README.md +++ b/packages/frameworks/react/README.md @@ -24,7 +24,7 @@ bun add rivetkit/react ## Quick Start ```tsx -import { createClient } from "@rivetkit/actor/client"; +import { createClient } from "rivetkit/client"; import { createReactActorCore } from "@rivetkit/react"; import type { App } from "../counter/src/index"; import React, { useState } from "react"; diff --git a/packages/frameworks/react/package.json b/packages/frameworks/react/package.json index 1895dad93..fa5a30205 100644 --- a/packages/frameworks/react/package.json +++ b/packages/frameworks/react/package.json @@ -27,12 +27,12 @@ "@rivetkit/framework-base": "workspace:*" }, "peerDependencies": { - "@rivetkit/actor": "*", "react": "^18 || ^19", - "react-dom": "^18 || ^19" + "react-dom": "^18 || ^19", + "rivetkit": "*" }, "devDependencies": { - "@rivetkit/actor": "workspace:^", + "rivetkit": "workspace:^", "tsup": "^8.3.6", "typescript": "^5.5.2", "vitest": "^3.1.1" diff --git a/packages/frameworks/react/src/mod.tsx b/packages/frameworks/react/src/mod.tsx index d6e53f9c4..27c0a3264 100644 --- a/packages/frameworks/react/src/mod.tsx +++ b/packages/frameworks/react/src/mod.tsx @@ -5,7 +5,7 @@ // ExtractAppFromClient, // ExtractActorsFromApp, // ClientRaw, -//} from "@rivetkit/actor/client"; +//} from "rivetkit/client"; //import { ActorManager } from "@rivetkit/framework-base"; //import { // useCallback, diff --git a/packages/platforms/bun/package.json b/packages/platforms/bun/package.json index 8360bef0a..c354b04d0 100644 --- a/packages/platforms/bun/package.json +++ b/packages/platforms/bun/package.json @@ -26,16 +26,16 @@ "check-types": "tsc --noEmit" }, "peerDependencies": { - "@rivetkit/actor": "*", "@rivetkit/file-system": "*", - "@rivetkit/memory": "*" + "@rivetkit/memory": "*", + "rivetkit": "*" }, "devDependencies": { - "@rivetkit/actor": "workspace:*", "@rivetkit/file-system": "workspace:^", "@rivetkit/memory": "workspace:*", "@types/bun": "^1.2.2", "hono": "^4.7.0", + "rivetkit": "workspace:*", "tsup": "^8.4.0", "typescript": "^5.5.2" }, diff --git a/packages/platforms/bun/src/config.ts b/packages/platforms/bun/src/config.ts index 69dae5fe5..55b30fe8f 100644 --- a/packages/platforms/bun/src/config.ts +++ b/packages/platforms/bun/src/config.ts @@ -1,4 +1,4 @@ -import { DriverConfigSchema } from "@rivetkit/actor/driver-helpers"; +import { DriverConfigSchema } from "rivetkit/driver-helpers"; import { z } from "zod"; export const ConfigSchema = DriverConfigSchema.extend({ diff --git a/packages/platforms/bun/src/log.ts b/packages/platforms/bun/src/log.ts index b3ee8e756..c58352cda 100644 --- a/packages/platforms/bun/src/log.ts +++ b/packages/platforms/bun/src/log.ts @@ -1,4 +1,4 @@ -import { getLogger } from "@rivetkit/actor/log"; +import { getLogger } from "rivetkit/log"; export const LOGGER_NAME = "bun"; diff --git a/packages/platforms/bun/src/mod.ts b/packages/platforms/bun/src/mod.ts index 87906e4a5..83e437f19 100644 --- a/packages/platforms/bun/src/mod.ts +++ b/packages/platforms/bun/src/mod.ts @@ -1,22 +1,22 @@ import type { Serve, Server, ServerWebSocket, WebSocketHandler } from "bun"; -import { assertUnreachable } from "@rivetkit/actor/utils"; -import { CoordinateTopology } from "@rivetkit/actor/topologies/coordinate"; +import { assertUnreachable } from "rivetkit/utils"; +import { CoordinateTopology } from "rivetkit/topologies/coordinate"; import { ConfigSchema, type InputConfig } from "./config"; import { logger } from "./log"; import { createBunWebSocket } from "hono/bun"; import type { Hono } from "hono"; -import { type ActorCoreApp, StandaloneTopology } from "@rivetkit/actor"; +import { type WorkerCoreApp, StandaloneTopology } from "rivetkit"; import { MemoryGlobalState, MemoryManagerDriver, - MemoryActorDriver, + MemoryWorkerDriver, } from "@rivetkit/memory"; -import { FileSystemActorDriver, FileSystemGlobalState, FileSystemManagerDriver } from "@rivetkit/file-system"; +import { FileSystemWorkerDriver, FileSystemGlobalState, FileSystemManagerDriver } from "@rivetkit/file-system"; export { InputConfig as Config } from "./config"; export function createRouter( - app: ActorCoreApp, + app: WorkerCoreApp, inputConfig?: InputConfig, ): { router: Hono; @@ -35,22 +35,22 @@ export function createRouter( // Configure default configuration if (!config.topology) config.topology = "standalone"; - if (!config.drivers.manager || !config.drivers.actor) { + if (!config.drivers.manager || !config.drivers.worker) { if (config.mode === "file-system") { const fsState = new FileSystemGlobalState(); if (!config.drivers.manager) { config.drivers.manager = new FileSystemManagerDriver(app, fsState); } - if (!config.drivers.actor) { - config.drivers.actor = new FileSystemActorDriver(fsState); + if (!config.drivers.worker) { + config.drivers.worker = new FileSystemWorkerDriver(fsState); } } else if (config.mode === "memory") { const memoryState = new MemoryGlobalState(); if (!config.drivers.manager) { config.drivers.manager = new MemoryManagerDriver(app, memoryState); } - if (!config.drivers.actor) { - config.drivers.actor = new MemoryActorDriver(memoryState); + if (!config.drivers.worker) { + config.drivers.worker = new MemoryWorkerDriver(memoryState); } } else { assertUnreachable(config.mode); @@ -72,7 +72,7 @@ export function createRouter( } export function createHandler( - app: ActorCoreApp, + app: WorkerCoreApp, inputConfig?: InputConfig, ): Serve { const config = ConfigSchema.parse(inputConfig); @@ -88,7 +88,7 @@ export function createHandler( } export function serve( - app: ActorCoreApp, + app: WorkerCoreApp, inputConfig: InputConfig, ): Server { const config = ConfigSchema.parse(inputConfig); @@ -96,7 +96,7 @@ export function serve( const handler = createHandler(app, config); const server = Bun.serve(handler); - logger().info("actorcore started", { + logger().info("workercore started", { hostname: config.hostname, port: config.port, }); diff --git a/packages/platforms/cloudflare-workers/package.json b/packages/platforms/cloudflare-workers/package.json index 33e511b0a..c5a296388 100644 --- a/packages/platforms/cloudflare-workers/package.json +++ b/packages/platforms/cloudflare-workers/package.json @@ -27,12 +27,12 @@ "test": "vitest run tests" }, "peerDependencies": { - "@rivetkit/actor": "*" + "rivetkit": "*" }, "devDependencies": { "@cloudflare/workers-types": "^4.20250129.0", - "@rivetkit/actor": "workspace:*", "@types/invariant": "^2", + "rivetkit": "workspace:*", "tsup": "^8.4.0", "typescript": "^5.5.2", "vitest": "^3.1.1", diff --git a/packages/platforms/cloudflare-workers/src/actor-driver.ts b/packages/platforms/cloudflare-workers/src/actor-driver.ts deleted file mode 100644 index 9df789e08..000000000 --- a/packages/platforms/cloudflare-workers/src/actor-driver.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { ActorDriver, AnyActorInstance } from "@rivetkit/actor/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 `CloudflareWorkersActorDriver`. - */ -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 CloudflareWorkersActorDriver 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); - } -} diff --git a/packages/platforms/cloudflare-workers/src/config.ts b/packages/platforms/cloudflare-workers/src/config.ts index db63b0923..31cab579f 100644 --- a/packages/platforms/cloudflare-workers/src/config.ts +++ b/packages/platforms/cloudflare-workers/src/config.ts @@ -1,4 +1,4 @@ -import { DriverConfigSchema } from "@rivetkit/actor/driver-helpers"; +import { DriverConfigSchema } from "rivetkit/driver-helpers"; import { z } from "zod"; export const ConfigSchema = DriverConfigSchema.default({}); diff --git a/packages/platforms/cloudflare-workers/src/handler.ts b/packages/platforms/cloudflare-workers/src/handler.ts index d34e13165..5751989bf 100644 --- a/packages/platforms/cloudflare-workers/src/handler.ts +++ b/packages/platforms/cloudflare-workers/src/handler.ts @@ -1,53 +1,53 @@ import { type DurableObjectConstructor, - type ActorHandlerInterface, - createActorDurableObject, -} from "./actor-handler-do"; + type WorkerHandlerInterface, + createWorkerDurableObject, +} from "./worker-handler-do"; import { ConfigSchema, type InputConfig } from "./config"; -import { assertUnreachable } from "@rivetkit/actor/utils"; +import { assertUnreachable } from "rivetkit/utils"; import type { Hono } from "hono"; -import { PartitionTopologyManager } from "@rivetkit/actor/topologies/partition"; +import { PartitionTopologyManager } from "rivetkit/topologies/partition"; import { logger } from "./log"; import { CloudflareWorkersManagerDriver } from "./manager-driver"; -import { ActorCoreApp } from "@rivetkit/actor"; +import { WorkerCoreApp } from "rivetkit"; import { upgradeWebSocket } from "./websocket"; /** Cloudflare Workers env */ export interface Bindings { - ACTOR_KV: KVNamespace; - ACTOR_DO: DurableObjectNamespace; + WORKER_KV: KVNamespace; + WORKER_DO: DurableObjectNamespace; } export function createHandler( - app: ActorCoreApp, + app: WorkerCoreApp, inputConfig?: InputConfig, ): { handler: ExportedHandler; - ActorHandler: DurableObjectConstructor; + WorkerHandler: DurableObjectConstructor; } { // Create router - const { router, ActorHandler } = createRouter(app, inputConfig); + const { router, WorkerHandler } = createRouter(app, inputConfig); // Create Cloudflare handler const handler = { fetch: router.fetch, } satisfies ExportedHandler; - return { handler, ActorHandler }; + return { handler, WorkerHandler }; } export function createRouter( - app: ActorCoreApp, + app: WorkerCoreApp, inputConfig?: InputConfig, ): { router: Hono<{ Bindings: Bindings }>; - ActorHandler: DurableObjectConstructor; + WorkerHandler: DurableObjectConstructor; } { const driverConfig = ConfigSchema.parse(inputConfig); // Configur drivers // - // Actor driver will get set in `ActorHandler` + // Worker driver will get set in `WorkerHandler` if (!driverConfig.drivers) driverConfig.drivers = {}; if (!driverConfig.drivers.manager) driverConfig.drivers.manager = new CloudflareWorkersManagerDriver(); @@ -57,7 +57,7 @@ export function createRouter( driverConfig.getUpgradeWebSocket = () => upgradeWebSocket; // Create Durable Object - const ActorHandler = createActorDurableObject(app, driverConfig); + const WorkerHandler = createWorkerDurableObject(app, driverConfig); driverConfig.topology = driverConfig.topology ?? "partition"; if (driverConfig.topology === "partition") { @@ -65,21 +65,21 @@ export function createRouter( app.config, driverConfig, { - onProxyRequest: async (c, actorRequest, actorId): Promise => { + onProxyRequest: async (c, workerRequest, workerId): Promise => { logger().debug("forwarding request to durable object", { - actorId, - method: actorRequest.method, - url: actorRequest.url, + workerId, + method: workerRequest.method, + url: workerRequest.url, }); - const id = c.env.ACTOR_DO.idFromString(actorId); - const stub = c.env.ACTOR_DO.get(id); + const id = c.env.WORKER_DO.idFromString(workerId); + const stub = c.env.WORKER_DO.get(id); - return await stub.fetch(actorRequest); + return await stub.fetch(workerRequest); }, - onProxyWebSocket: async (c, path, actorId) => { + onProxyWebSocket: async (c, path, workerId) => { logger().debug("forwarding websocket to durable object", { - actorId, + workerId, path, }); @@ -92,13 +92,13 @@ export function createRouter( } // Update path on URL - const newUrl = new URL(`http://actor${path}`); - const actorRequest = new Request(newUrl, c.req.raw); + const newUrl = new URL(`http://worker${path}`); + const workerRequest = new Request(newUrl, c.req.raw); - const id = c.env.ACTOR_DO.idFromString(actorId); - const stub = c.env.ACTOR_DO.get(id); + const id = c.env.WORKER_DO.idFromString(workerId); + const stub = c.env.WORKER_DO.get(id); - return await stub.fetch(actorRequest); + return await stub.fetch(workerRequest); }, }, ); @@ -108,7 +108,7 @@ export function createRouter( Bindings: Bindings; }>; - return { router, ActorHandler }; + return { router, WorkerHandler }; } else if ( driverConfig.topology === "standalone" || driverConfig.topology === "coordinate" diff --git a/packages/platforms/cloudflare-workers/src/log.ts b/packages/platforms/cloudflare-workers/src/log.ts index f941b4a57..c5771efff 100644 --- a/packages/platforms/cloudflare-workers/src/log.ts +++ b/packages/platforms/cloudflare-workers/src/log.ts @@ -1,4 +1,4 @@ -import { getLogger } from "@rivetkit/actor/log"; +import { getLogger } from "rivetkit/log"; export const LOGGER_NAME = "driver-cloudflare-workers"; diff --git a/packages/platforms/cloudflare-workers/src/manager-driver.ts b/packages/platforms/cloudflare-workers/src/manager-driver.ts index 64f73aaa3..e72ea735f 100644 --- a/packages/platforms/cloudflare-workers/src/manager-driver.ts +++ b/packages/platforms/cloudflare-workers/src/manager-driver.ts @@ -2,31 +2,31 @@ import type { ManagerDriver, GetForIdInput, GetWithKeyInput, - ActorOutput, + WorkerOutput, CreateInput, GetOrCreateWithKeyInput, -} from "@rivetkit/actor/driver-helpers"; -import { ActorAlreadyExists } from "@rivetkit/actor/errors"; +} from "rivetkit/driver-helpers"; +import { WorkerAlreadyExists } from "rivetkit/errors"; import { Bindings } from "./mod"; import { logger } from "./log"; import { serializeNameAndKey, serializeKey } from "./util"; -// Actor metadata structure -interface ActorData { +// Worker metadata structure +interface WorkerData { name: string; key: string[]; } // Key constants similar to Redis implementation const KEYS = { - ACTOR: { - // Combined key for actor metadata (name and key) - metadata: (actorId: string) => `actor:${actorId}:metadata`, + WORKER: { + // Combined key for worker metadata (name and key) + metadata: (workerId: string) => `worker:${workerId}:metadata`, - // Key index function for actor lookup + // Key index function for worker lookup keyIndex: (name: string, key: string[] = []) => { // Use serializeKey for consistent handling of all keys - return `actor_key:${serializeKey(key)}`; + return `worker_key:${serializeKey(key)}`; }, }, }; @@ -34,27 +34,27 @@ const KEYS = { export class CloudflareWorkersManagerDriver implements ManagerDriver { async getForId({ c, - actorId, - }: GetForIdInput<{ Bindings: Bindings }>): Promise { + workerId, + }: GetForIdInput<{ Bindings: Bindings }>): Promise { if (!c) throw new Error("Missing Hono context"); - // Get actor metadata from KV (combined name and key) - const actorData = (await c.env.ACTOR_KV.get(KEYS.ACTOR.metadata(actorId), { + // Get worker metadata from KV (combined name and key) + const workerData = (await c.env.WORKER_KV.get(KEYS.WORKER.metadata(workerId), { type: "json", - })) as ActorData | null; + })) as WorkerData | null; - // If the actor doesn't exist, return undefined - if (!actorData) { + // If the worker doesn't exist, return undefined + if (!workerData) { return undefined; } - // Generate durable ID from actorId for meta - const durableId = c.env.ACTOR_DO.idFromString(actorId); + // Generate durable ID from workerId for meta + const durableId = c.env.WORKER_DO.idFromString(workerId); return { - actorId, - name: actorData.name, - key: actorData.key, + workerId, + name: workerData.name, + key: workerData.key, meta: durableId, }; } @@ -64,120 +64,120 @@ export class CloudflareWorkersManagerDriver implements ManagerDriver { name, key, }: GetWithKeyInput<{ Bindings: Bindings }>): Promise< - ActorOutput | undefined + WorkerOutput | undefined > { if (!c) throw new Error("Missing Hono context"); const log = logger(); - log.debug("getWithKey: searching for actor", { name, key }); + log.debug("getWithKey: searching for worker", { name, key }); // Generate deterministic ID from the name and key - // This is aligned with how createActor generates IDs + // This is aligned with how createWorker generates IDs const nameKeyString = serializeNameAndKey(name, key); - const durableId = c.env.ACTOR_DO.idFromName(nameKeyString); - const actorId = durableId.toString(); + const durableId = c.env.WORKER_DO.idFromName(nameKeyString); + const workerId = durableId.toString(); - // Check if the actor metadata exists - const actorData = await c.env.ACTOR_KV.get(KEYS.ACTOR.metadata(actorId), { + // Check if the worker metadata exists + const workerData = await c.env.WORKER_KV.get(KEYS.WORKER.metadata(workerId), { type: "json", }); - if (!actorData) { - log.debug("getWithKey: no actor found with matching name and key", { + if (!workerData) { + log.debug("getWithKey: no worker found with matching name and key", { name, key, - actorId, + workerId, }); return undefined; } - log.debug("getWithKey: found actor with matching name and key", { - actorId, + log.debug("getWithKey: found worker with matching name and key", { + workerId, name, key, }); - return this.#buildActorOutput(c, actorId); + return this.#buildWorkerOutput(c, workerId); } 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.createActor(input); + return await this.createWorker(input); } } - async createActor({ + async createWorker({ c, name, key, input, - }: CreateInput<{ Bindings: Bindings }>): Promise { + }: CreateInput<{ Bindings: Bindings }>): Promise { if (!c) throw new Error("Missing Hono context"); const log = logger(); - // 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); + // 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); } - // 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 + // 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 const nameKeyString = serializeNameAndKey(name, key); - const durableId = c.env.ACTOR_DO.idFromName(nameKeyString); - const actorId = durableId.toString(); + const durableId = c.env.WORKER_DO.idFromName(nameKeyString); + const workerId = durableId.toString(); - // Init actor - const actor = c.env.ACTOR_DO.get(durableId); - await actor.initialize({ + // Init worker + const worker = c.env.WORKER_DO.get(durableId); + await worker.initialize({ name, key, input, }); - // Store combined actor metadata (name and key) - const actorData: ActorData = { name, key }; - await c.env.ACTOR_KV.put( - KEYS.ACTOR.metadata(actorId), - JSON.stringify(actorData), + // Store combined worker metadata (name and key) + const workerData: WorkerData = { name, key }; + await c.env.WORKER_KV.put( + KEYS.WORKER.metadata(workerId), + JSON.stringify(workerData), ); // Add to key index for lookups by name and key - await c.env.ACTOR_KV.put(KEYS.ACTOR.keyIndex(name, key), actorId); + await c.env.WORKER_KV.put(KEYS.WORKER.keyIndex(name, key), workerId); return { - actorId, + workerId, name, key, meta: durableId, }; } - // Helper method to build actor output from an ID - async #buildActorOutput( + // Helper method to build worker output from an ID + async #buildWorkerOutput( c: any, - actorId: string, - ): Promise { - const actorData = (await c.env.ACTOR_KV.get(KEYS.ACTOR.metadata(actorId), { + workerId: string, + ): Promise { + const workerData = (await c.env.WORKER_KV.get(KEYS.WORKER.metadata(workerId), { type: "json", - })) as ActorData | null; + })) as WorkerData | null; - if (!actorData) { + if (!workerData) { return undefined; } // Generate durable ID for meta - const durableId = c.env.ACTOR_DO.idFromString(actorId); + const durableId = c.env.WORKER_DO.idFromString(workerId); return { - actorId, - name: actorData.name, - key: actorData.key, + workerId, + name: workerData.name, + key: workerData.key, meta: durableId, }; } diff --git a/packages/platforms/cloudflare-workers/src/util.ts b/packages/platforms/cloudflare-workers/src/util.ts index 4f58aaf8d..b6385dc6c 100644 --- a/packages/platforms/cloudflare-workers/src/util.ts +++ b/packages/platforms/cloudflare-workers/src/util.ts @@ -5,7 +5,7 @@ export const KEY_SEPARATOR = ","; /** * Serializes an array of key strings into a single string for use with idFromName * - * @param name The actor name + * @param name The worker 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/worker-driver.ts b/packages/platforms/cloudflare-workers/src/worker-driver.ts new file mode 100644 index 000000000..89a3b6a33 --- /dev/null +++ b/packages/platforms/cloudflare-workers/src/worker-driver.ts @@ -0,0 +1,66 @@ +import { 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); + } +} diff --git a/packages/platforms/cloudflare-workers/src/actor-handler-do.ts b/packages/platforms/cloudflare-workers/src/worker-handler-do.ts similarity index 56% rename from packages/platforms/cloudflare-workers/src/actor-handler-do.ts rename to packages/platforms/cloudflare-workers/src/worker-handler-do.ts index 1d473ca46..67a13cd55 100644 --- a/packages/platforms/cloudflare-workers/src/actor-handler-do.ts +++ b/packages/platforms/cloudflare-workers/src/worker-handler-do.ts @@ -1,46 +1,46 @@ import { DurableObject } from "cloudflare:workers"; -import type { ActorCoreApp, ActorKey } from "@rivetkit/actor"; +import type { WorkerCoreApp, WorkerKey } from "rivetkit"; import { logger } from "./log"; import type { Config } from "./config"; -import { PartitionTopologyActor } from "@rivetkit/actor/topologies/partition"; +import { PartitionTopologyWorker } from "rivetkit/topologies/partition"; import { CloudflareDurableObjectGlobalState, - CloudflareWorkersActorDriver, -} from "./actor-driver"; + CloudflareWorkersWorkerDriver, +} from "./worker-driver"; export const KEYS = { - INITIALIZED: "@rivetkit/actor:initialized", - NAME: "@rivetkit/actor:name", - KEY: "@rivetkit/actor:key", - INPUT: "@rivetkit/actor:input", - PERSISTED_DATA: "@rivetkit/actor:data", + INITIALIZED: "rivetkit:initialized", + NAME: "rivetkit:name", + KEY: "rivetkit:key", + INPUT: "rivetkit:input", + PERSISTED_DATA: "rivetkit:data", }; -export interface ActorHandlerInterface extends DurableObject { - initialize(req: ActorInitRequest): Promise; +export interface WorkerHandlerInterface extends DurableObject { + initialize(req: WorkerInitRequest): Promise; } -export interface ActorInitRequest { +export interface WorkerInitRequest { name: string; - key: ActorKey; + key: WorkerKey; input?: unknown; } interface InitializedData { name: string; - key: ActorKey; + key: WorkerKey; } export type DurableObjectConstructor = new ( ...args: ConstructorParameters ) => DurableObject; -interface LoadedActor { - actorTopology: PartitionTopologyActor; +interface LoadedWorker { + workerTopology: PartitionTopologyWorker; } -export function createActorDurableObject( - app: ActorCoreApp, +export function createWorkerDurableObject( + app: WorkerCoreApp, config: Config, ): DurableObjectConstructor { const globalState = new CloudflareDurableObjectGlobalState(); @@ -48,19 +48,19 @@ export function createActorDurableObject( /** * Startup steps: * 1. If not already created call `initialize`, otherwise check KV to ensure it's initialized - * 2. Load actor + * 2. Load worker * 3. Start service requests */ - return class ActorHandler + return class WorkerHandler extends DurableObject - implements ActorHandlerInterface + implements WorkerHandlerInterface { #initialized?: InitializedData; #initializedPromise?: PromiseWithResolvers; - #actor?: LoadedActor; + #worker?: LoadedWorker; - async #loadActor(): Promise { + async #loadWorker(): Promise { // Wait for init if (!this.#initialized) { // Wait for init @@ -75,9 +75,9 @@ export function createActorDurableObject( ]); if (res.get(KEYS.INITIALIZED)) { const name = res.get(KEYS.NAME) as string; - if (!name) throw new Error("missing actor name"); - const key = res.get(KEYS.KEY) as ActorKey; - if (!key) throw new Error("missing actor key"); + if (!name) throw new Error("missing worker name"); + const key = res.get(KEYS.KEY) as WorkerKey; + if (!key) throw new Error("missing worker key"); logger().debug("already initialized", { name, key }); @@ -90,45 +90,45 @@ export function createActorDurableObject( } // Check if already loaded - if (this.#actor) { - return this.#actor; + if (this.#worker) { + return this.#worker; } if (!this.#initialized) throw new Error("Not initialized"); // Create topology if (!config.drivers) config.drivers = {}; - if (!config.drivers.actor) { - config.drivers.actor = new CloudflareWorkersActorDriver(globalState); + if (!config.drivers.worker) { + config.drivers.worker = new CloudflareWorkersWorkerDriver(globalState); } - const actorTopology = new PartitionTopologyActor(app.config, config); + const workerTopology = new PartitionTopologyWorker(app.config, config); // Register DO with global state // 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 actorId = this.ctx.id.toString(); - globalState.setDOState(actorId, { ctx: this.ctx, env: this.env }); + const workerId = this.ctx.id.toString(); + globalState.setDOState(workerId, { ctx: this.ctx, env: this.env }); - // Save actor - this.#actor = { - actorTopology, + // Save worker + this.#worker = { + workerTopology, }; - // Start actor - await actorTopology.start( - actorId, + // Start worker + await workerTopology.start( + workerId, this.#initialized.name, this.#initialized.key, // TODO: "unknown", ); - return this.#actor; + return this.#worker; } /** RPC called by the service that creates the DO to initialize it. */ - async initialize(req: ActorInitRequest) { + async initialize(req: WorkerInitRequest) { // TODO: Need to add this to a core promise that needs to be resolved before start await this.ctx.storage.put({ @@ -142,20 +142,20 @@ export function createActorDurableObject( key: req.key, }; - logger().debug("initialized actor", { key: req.key }); + logger().debug("initialized worker", { key: req.key }); - // Preemptively actor so the lifecycle hooks are called - await this.#loadActor(); + // Preemptively worker so the lifecycle hooks are called + await this.#loadWorker(); } async fetch(request: Request): Promise { - const { actorTopology } = await this.#loadActor(); - return await actorTopology.router.fetch(request); + const { workerTopology } = await this.#loadWorker(); + return await workerTopology.router.fetch(request); } async alarm(): Promise { - const { actorTopology } = await this.#loadActor(); - await actorTopology.actor.onAlarm(); + const { workerTopology } = await this.#loadWorker(); + await workerTopology.worker.onAlarm(); } }; } diff --git a/packages/platforms/cloudflare-workers/tests/driver-tests.test.ts b/packages/platforms/cloudflare-workers/tests/driver-tests.test.ts index 7aa02aedd..aeac5d4b2 100644 --- a/packages/platforms/cloudflare-workers/tests/driver-tests.test.ts +++ b/packages/platforms/cloudflare-workers/tests/driver-tests.test.ts @@ -1,11 +1,11 @@ -import { runDriverTests } from "@rivetkit/actor/driver-test-suite"; +import { runDriverTests } from "rivetkit/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/actor/test"; +import { getPort } from "rivetkit/test"; const execPromise = promisify(exec); @@ -20,12 +20,12 @@ runDriverTests({ // Create a temporary directory for the test const uuid = crypto.randomUUID(); - const tmpDir = path.join(os.tmpdir(), `actor-core-cloudflare-test-${uuid}`); + const tmpDir = path.join(os.tmpdir(), `worker-core-cloudflare-test-${uuid}`); await fs.mkdir(tmpDir, { recursive: true }); // Create package.json with workspace dependencies const packageJson = { - name: "@rivetkit/actor-test", + name: "rivetkit-test", private: true, version: "1.0.0", type: "module", @@ -35,7 +35,7 @@ runDriverTests({ dependencies: { wrangler: "4.8.0", "@rivetkit/cloudflare-workers": "workspace:*", - "@rivetkit/actor": "workspace:*", + "rivetkit": "workspace:*", }, packageManager: "yarn@4.7.0+sha512.5a0afa1d4c1d844b3447ee3319633797bcd6385d9a44be07993ae52ff4facabccafb4af5dcd1c2f9a94ac113e5e9ff56f6130431905884414229e284e37bb7c9", @@ -58,7 +58,7 @@ runDriverTests({ // Create a wrangler.json file const wranglerConfig = { - name: "@rivetkit/actor-test", + name: "rivetkit-test", main: "src/index.ts", compatibility_date: "2025-01-29", compatibility_flags: ["nodejs_compat"], @@ -67,21 +67,21 @@ runDriverTests({ }, migrations: [ { - new_classes: ["ActorHandler"], + new_classes: ["WorkerHandler"], tag: "v1", }, ], durable_objects: { bindings: [ { - class_name: "ActorHandler", - name: "ACTOR_DO", + class_name: "WorkerHandler", + name: "WORKER_DO", }, ], }, kv_namespaces: [ { - binding: "ACTOR_KV", + binding: "WORKER_KV", id: "test", // Will be replaced with a mock in dev mode }, ], @@ -103,10 +103,10 @@ runDriverTests({ import { app } from "${appPath.replace(/\.ts$/, "")}"; // Create handlers for Cloudflare Workers -const { handler, ActorHandler } = createHandler(app); +const { handler, WorkerHandler } = createHandler(app); // Export the handlers for Cloudflare -export { handler as default, ActorHandler }; +export { handler as default, WorkerHandler }; `; await fs.writeFile(path.join(srcDir, "index.ts"), indexContent); diff --git a/packages/platforms/cloudflare-workers/tests/id-generation.test.ts b/packages/platforms/cloudflare-workers/tests/id-generation.test.ts index 00550f851..ef456e97d 100644 --- a/packages/platforms/cloudflare-workers/tests/id-generation.test.ts +++ b/packages/platforms/cloudflare-workers/tests/id-generation.test.ts @@ -4,7 +4,7 @@ import { CloudflareWorkersManagerDriver } from "../src/manager-driver"; describe("Deterministic ID generation", () => { test("should generate consistent IDs for the same name and key", () => { - const name = "test-actor"; + const name = "test-worker"; const key = ["key1", "key2"]; // Test that serializeNameAndKey produces a consistent string @@ -12,30 +12,30 @@ describe("Deterministic ID generation", () => { const serialized2 = serializeNameAndKey(name, key); expect(serialized1).toBe(serialized2); - expect(serialized1).toBe("test-actor:key1,key2"); + expect(serialized1).toBe("test-worker:key1,key2"); }); test("should properly escape special characters in keys", () => { - const name = "test-actor"; + const name = "test-worker"; const key = ["key,with,commas", "normal-key"]; const serialized = serializeNameAndKey(name, key); - expect(serialized).toBe("test-actor:key\\,with\\,commas,normal-key"); + expect(serialized).toBe("test-worker:key\\,with\\,commas,normal-key"); }); - test("should properly escape colons in actor names", () => { - const name = "test:actor:with:colons"; + 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\\:actor\\:with\\:colons:key1,key2"); + expect(serialized).toBe("test\\:worker\\:with\\:colons:key1,key2"); }); test("should handle empty key arrays", () => { - const name = "test-actor"; + const name = "test-worker"; const key: string[] = []; const serialized = serializeNameAndKey(name, key); - expect(serialized).toBe("test-actor:(none)"); + expect(serialized).toBe("test-worker:(none)"); }); }); \ No newline at end of file diff --git a/packages/platforms/cloudflare-workers/tests/key-indexes.test.ts b/packages/platforms/cloudflare-workers/tests/key-indexes.test.ts index c2aca626f..4e2237a71 100644 --- a/packages/platforms/cloudflare-workers/tests/key-indexes.test.ts +++ b/packages/platforms/cloudflare-workers/tests/key-indexes.test.ts @@ -4,39 +4,39 @@ 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`, + WORKER: { + metadata: (workerId: string) => `worker:${workerId}:metadata`, keyIndex: (name: string, key: string[] = []) => { // Use serializeKey for consistent handling of all keys - return `actor_key:${serializeKey(key)}`; + return `worker_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)"); + 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.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"); + 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.ACTOR.keyIndex("test-actor", ["key1", "key2"])) - .toBe(`actor_key:key1,key2`); + expect(KEYS.WORKER.keyIndex("test-worker", ["key1", "key2"])) + .toBe(`worker_key:key1,key2`); // Test with special characters - expect(KEYS.ACTOR.keyIndex("test-actor", ["key,with,commas"])) - .toBe("actor_key:key\\,with\\,commas"); + expect(KEYS.WORKER.keyIndex("test-worker", ["key,with,commas"])) + .toBe("worker_key:key\\,with\\,commas"); }); test("metadata key creates proper pattern", () => { - expect(KEYS.ACTOR.metadata("123-456")).toBe("actor:123-456:metadata"); + expect(KEYS.WORKER.metadata("123-456")).toBe("worker:123-456:metadata"); }); }); \ No newline at end of file diff --git a/packages/platforms/cloudflare-workers/tests/key-serialization.test.ts b/packages/platforms/cloudflare-workers/tests/key-serialization.test.ts index 754f5787b..49bea67a1 100644 --- a/packages/platforms/cloudflare-workers/tests/key-serialization.test.ts +++ b/packages/platforms/cloudflare-workers/tests/key-serialization.test.ts @@ -92,8 +92,8 @@ describe("Key serialization and deserialization", () => { }); 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`); + expect(serializeNameAndKey("worker", ["a,b", EMPTY_KEY, "c,d"])) + .toBe(`worker:a\\,b${KEY_SEPARATOR}\\${EMPTY_KEY}${KEY_SEPARATOR}c\\,d`); }); }); diff --git a/packages/platforms/nodejs/package.json b/packages/platforms/nodejs/package.json index 9ae6de1b6..5c18f8964 100644 --- a/packages/platforms/nodejs/package.json +++ b/packages/platforms/nodejs/package.json @@ -26,15 +26,15 @@ "check-types": "tsc --noEmit" }, "peerDependencies": { - "@rivetkit/actor": "*", "@rivetkit/file-system": "*", - "@rivetkit/memory": "*" + "@rivetkit/memory": "*", + "rivetkit": "*" }, "devDependencies": { - "@rivetkit/actor": "workspace:*", "@rivetkit/file-system": "workspace:*", "@rivetkit/memory": "workspace:*", "hono": "^4.7.0", + "rivetkit": "workspace:*", "tsup": "^8.4.0", "typescript": "^5.5.2" }, diff --git a/packages/platforms/nodejs/src/config.ts b/packages/platforms/nodejs/src/config.ts index 69dae5fe5..55b30fe8f 100644 --- a/packages/platforms/nodejs/src/config.ts +++ b/packages/platforms/nodejs/src/config.ts @@ -1,4 +1,4 @@ -import { DriverConfigSchema } from "@rivetkit/actor/driver-helpers"; +import { DriverConfigSchema } from "rivetkit/driver-helpers"; import { z } from "zod"; export const ConfigSchema = DriverConfigSchema.extend({ diff --git a/packages/platforms/nodejs/src/log.ts b/packages/platforms/nodejs/src/log.ts index 14046e59e..6d4105f9d 100644 --- a/packages/platforms/nodejs/src/log.ts +++ b/packages/platforms/nodejs/src/log.ts @@ -1,4 +1,4 @@ -import { getLogger } from "@rivetkit/actor/log"; +import { getLogger } from "rivetkit/log"; export const LOGGER_NAME = "nodejs"; diff --git a/packages/platforms/nodejs/src/mod.ts b/packages/platforms/nodejs/src/mod.ts index fbcfd4d53..bd607b61c 100644 --- a/packages/platforms/nodejs/src/mod.ts +++ b/packages/platforms/nodejs/src/mod.ts @@ -1,18 +1,18 @@ import { serve as honoServe, type ServerType } from "@hono/node-server"; import { createNodeWebSocket, type NodeWebSocket } from "@hono/node-ws"; -import { assertUnreachable } from "@rivetkit/actor/utils"; -import { CoordinateTopology } from "@rivetkit/actor/topologies/coordinate"; +import { assertUnreachable } from "rivetkit/utils"; +import { CoordinateTopology } from "rivetkit/topologies/coordinate"; import { logger } from "./log"; import type { Hono } from "hono"; -import { StandaloneTopology, type ActorCoreApp } from "@rivetkit/actor"; +import { StandaloneTopology, type WorkerCoreApp } from "rivetkit"; import { MemoryGlobalState, MemoryManagerDriver, - MemoryActorDriver, + MemoryWorkerDriver, } from "@rivetkit/memory"; import { type InputConfig, ConfigSchema } from "./config"; import { - FileSystemActorDriver, + FileSystemWorkerDriver, FileSystemGlobalState, FileSystemManagerDriver, } from "@rivetkit/file-system"; @@ -20,7 +20,7 @@ import { export { InputConfig as Config } from "./config"; export function createRouter( - app: ActorCoreApp, + app: WorkerCoreApp, inputConfig?: InputConfig, ): { router: Hono; @@ -30,22 +30,22 @@ export function createRouter( // Configure default configuration if (!config.topology) config.topology = "standalone"; - if (!config.drivers.manager || !config.drivers.actor) { + if (!config.drivers.manager || !config.drivers.worker) { if (config.mode === "file-system") { const fsState = new FileSystemGlobalState(); if (!config.drivers.manager) { config.drivers.manager = new FileSystemManagerDriver(app, fsState); } - if (!config.drivers.actor) { - config.drivers.actor = new FileSystemActorDriver(fsState); + if (!config.drivers.worker) { + config.drivers.worker = new FileSystemWorkerDriver(fsState); } } else if (config.mode === "memory") { const memoryState = new MemoryGlobalState(); if (!config.drivers.manager) { config.drivers.manager = new MemoryManagerDriver(app, memoryState); } - if (!config.drivers.actor) { - config.drivers.actor = new MemoryActorDriver(memoryState); + if (!config.drivers.worker) { + config.drivers.worker = new MemoryWorkerDriver(memoryState); } } else { assertUnreachable(config.mode); @@ -81,7 +81,7 @@ export function createRouter( } export function serve( - app: ActorCoreApp, + app: WorkerCoreApp, inputConfig?: InputConfig, ): ServerType { const config = ConfigSchema.parse(inputConfig); @@ -95,7 +95,7 @@ export function serve( }); injectWebSocket(server); - logger().info("actorcore started", { + logger().info("workercore started", { hostname: config.hostname, port: config.port, }); diff --git a/packages/platforms/rivet/package.json b/packages/platforms/rivet/package.json index fff34c480..67f028e88 100644 --- a/packages/platforms/rivet/package.json +++ b/packages/platforms/rivet/package.json @@ -27,14 +27,14 @@ "test": "vitest run" }, "peerDependencies": { - "@rivetkit/actor": "*" + "rivetkit": "*" }, "devDependencies": { "@rivet-gg/actor-core": "^25.1.0", - "@rivetkit/actor": "workspace:*", "@types/deno": "^2.0.0", "@types/invariant": "^2", "@types/node": "^22.13.1", + "rivetkit": "workspace:*", "tsup": "^8.4.0", "typescript": "^5.5.2", "vitest": "^3.1.1" diff --git a/packages/platforms/rivet/src/actor-driver.ts b/packages/platforms/rivet/src/actor-driver.ts deleted file mode 100644 index 540d93c7c..000000000 --- a/packages/platforms/rivet/src/actor-driver.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { ActorContext } from "@rivet-gg/actor-core"; -import type { ActorDriver, AnyActorInstance } from "@rivetkit/actor/driver-helpers"; - -export interface ActorDriverContext { - ctx: ActorContext; -} - -export class RivetActorDriver implements ActorDriver { - #ctx: ActorContext; - - constructor(ctx: ActorContext) { - this.#ctx = ctx; - } - - getContext(_actorId: string): ActorDriverContext { - return { ctx: this.#ctx }; - } - - async readInput(_actorId: string): Promise { - // Read input - // - // We need to have a separate exists flag in order to represent `undefined` - const entries = await this.#ctx.kv.getBatch([ - ["@rivetkit/actor", "input", "exists"], - ["@rivetkit/actor", "input", "data"], - ]); - - if (entries.get(["@rivetkit/actor", "input", "exists"]) === true) { - return await entries.get(["@rivetkit/actor", "input", "data"]); - } else { - return undefined; - } - } - - async readPersistedData(_actorId: string): Promise { - let data = await this.#ctx.kv.get(["@rivetkit/actor", "data"]); - - // HACK: Modify to be undefined if null. This will be fixed in Actors v2. - if (data === null) data = undefined; - - return data; - } - - async writePersistedData(_actorId: string, data: unknown): Promise { - // Use "state" as the key for persisted data - await this.#ctx.kv.put(["@rivetkit/actor", "data"], data); - } - - async setAlarm(actor: AnyActorInstance, timestamp: number): Promise { - const timeout = Math.max(0, timestamp - Date.now()); - setTimeout(() => { - actor.onAlarm(); - }, timeout); - } -} diff --git a/packages/platforms/rivet/src/config.ts b/packages/platforms/rivet/src/config.ts index ec96207bb..b52520cf2 100644 --- a/packages/platforms/rivet/src/config.ts +++ b/packages/platforms/rivet/src/config.ts @@ -1,9 +1,9 @@ -import type { ActorCoreApp } from "@rivetkit/actor"; -import { DriverConfigSchema } from "@rivetkit/actor/driver-helpers"; +import type { WorkerCoreApp } from "rivetkit"; +import { DriverConfigSchema } from "rivetkit/driver-helpers"; import { z } from "zod"; export const ConfigSchema = DriverConfigSchema.extend({ - app: z.custom>(), + app: z.custom>(), }); export type InputConfig = z.input; export type Config = z.infer; diff --git a/packages/platforms/rivet/src/log.ts b/packages/platforms/rivet/src/log.ts index 0df63382c..1ad9a2899 100644 --- a/packages/platforms/rivet/src/log.ts +++ b/packages/platforms/rivet/src/log.ts @@ -1,4 +1,4 @@ -import { getLogger } from "@rivetkit/actor/log"; +import { getLogger } from "rivetkit/log"; export const LOGGER_NAME = "driver-rivet"; diff --git a/packages/platforms/rivet/src/manager-driver.ts b/packages/platforms/rivet/src/manager-driver.ts index d3d292bea..441bca19e 100644 --- a/packages/platforms/rivet/src/manager-driver.ts +++ b/packages/platforms/rivet/src/manager-driver.ts @@ -1,23 +1,23 @@ -import { assertUnreachable } from "@rivetkit/actor/utils"; -import { ActorAlreadyExists, InternalError } from "@rivetkit/actor/errors"; +import { assertUnreachable } from "rivetkit/utils"; +import { WorkerAlreadyExists, InternalError } from "rivetkit/errors"; import type { ManagerDriver, GetForIdInput, GetWithKeyInput, - ActorOutput, + WorkerOutput, GetOrCreateWithKeyInput, CreateInput, -} from "@rivetkit/actor/driver-helpers"; +} from "rivetkit/driver-helpers"; import { logger } from "./log"; import { type RivetClientConfig, rivetRequest } from "./rivet-client"; import { serializeKeyForTag, deserializeKeyFromTag } from "./util"; -export interface ActorState { +export interface WorkerState { key: string[]; destroyedAt?: number; } -export interface GetActorMeta { +export interface GetWorkerMeta { endpoint: string; } @@ -28,38 +28,38 @@ export class RivetManagerDriver implements ManagerDriver { this.#clientConfig = clientConfig; } - async getForId({ actorId }: GetForIdInput): Promise { + async getForId({ workerId }: GetForIdInput): Promise { try { - // Get actor - const res = await rivetRequest( + // Get worker + const res = await rivetRequest( this.#clientConfig, "GET", - `/actors/${encodeURIComponent(actorId)}`, + `/workers/${encodeURIComponent(workerId)}`, ); - // Check if actor exists and not destroyed - if (res.actor.destroyedAt) { + // Check if worker exists and not destroyed + if (res.worker.destroyedAt) { return undefined; } - // Ensure actor has required tags - if (!("name" in res.actor.tags)) { - throw new Error(`Actor ${res.actor.id} missing 'name' in tags.`); + // Ensure worker has required tags + if (!("name" in res.worker.tags)) { + throw new Error(`Worker ${res.worker.id} missing 'name' in tags.`); } - if (res.actor.tags.role !== "actor") { - throw new Error(`Actor ${res.actor.id} does not have an actor role.`); + if (res.worker.tags.role !== "worker") { + throw new Error(`Worker ${res.worker.id} does not have a worker role.`); } - if (res.actor.tags.framework !== "@rivetkit/actor") { - throw new Error(`Actor ${res.actor.id} is not an ActorCore actor.`); + if (res.worker.tags.framework !== "rivetkit") { + throw new Error(`Worker ${res.worker.id} is not an WorkerCore worker.`); } return { - actorId: res.actor.id, - name: res.actor.tags.name, - key: this.#extractKeyFromRivetTags(res.actor.tags), + workerId: res.worker.id, + name: res.worker.tags.name, + key: this.#extractKeyFromRivetTags(res.worker.tags), meta: { - endpoint: buildActorEndpoint(res.actor), - } satisfies GetActorMeta, + endpoint: buildWorkerEndpoint(res.worker), + } satisfies GetWorkerMeta, }; } catch (error) { // Handle not found or other errors @@ -70,19 +70,19 @@ export class RivetManagerDriver implements ManagerDriver { async getWithKey({ name, key, - }: GetWithKeyInput): Promise { + }: GetWithKeyInput): Promise { // Convert key array to Rivet's tag format const rivetTags = this.#convertKeyToRivetTags(name, key); - // Query actors with matching tags - const { actors } = await rivetRequest( + // Query workers with matching tags + const { workers } = await rivetRequest( this.#clientConfig, "GET", - `/actors?tags_json=${encodeURIComponent(JSON.stringify(rivetTags))}`, + `/workers?tags_json=${encodeURIComponent(JSON.stringify(rivetTags))}`, ); - // Filter actors to ensure they're valid - const validActors = actors.filter((a: RivetActor) => { + // Filter workers to ensure they're valid + const validWorkers = workers.filter((a: RivetWorker) => { // Verify all ports have hostname and port for (const portName in a.network.ports) { const port = a.network.ports[portName]; @@ -91,69 +91,69 @@ export class RivetManagerDriver implements ManagerDriver { return true; }); - if (validActors.length === 0) { + if (validWorkers.length === 0) { return undefined; } - // For consistent results, sort by ID if multiple actors match - const actor = - validActors.length > 1 - ? validActors.sort((a, b) => a.id.localeCompare(b.id))[0] - : validActors[0]; + // For consistent results, sort by ID if multiple workers match + const worker = + validWorkers.length > 1 + ? validWorkers.sort((a, b) => a.id.localeCompare(b.id))[0] + : validWorkers[0]; - // Ensure actor has required tags - if (!("name" in actor.tags)) { - throw new Error(`Actor ${actor.id} missing 'name' in tags.`); + // Ensure worker has required tags + if (!("name" in worker.tags)) { + throw new Error(`Worker ${worker.id} missing 'name' in tags.`); } return { - actorId: actor.id, - name: actor.tags.name, - key: this.#extractKeyFromRivetTags(actor.tags), + workerId: worker.id, + name: worker.tags.name, + key: this.#extractKeyFromRivetTags(worker.tags), meta: { - endpoint: buildActorEndpoint(actor), - } satisfies GetActorMeta, + endpoint: buildWorkerEndpoint(worker), + } satisfies GetWorkerMeta, }; } async getOrCreateWithKey( input: GetOrCreateWithKeyInput, - ): Promise { + ): Promise { const getOutput = await this.getWithKey(input); if (getOutput) { return getOutput; } else { - return await this.createActor(input); + return await this.createWorker(input); } } - async createActor({ + async createWorker({ name, key, region, 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); + }: 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); } - // Create the actor request - let actorLogLevel: string | undefined = undefined; + // Create the worker request + let workerLogLevel: string | undefined = undefined; if (typeof Deno !== "undefined") { - actorLogLevel = Deno.env.get("_ACTOR_LOG_LEVEL"); + workerLogLevel = Deno.env.get("_WORKER_LOG_LEVEL"); } else if (typeof process !== "undefined") { // Do this after Deno since `process` is sometimes polyfilled - actorLogLevel = process.env._ACTOR_LOG_LEVEL; + workerLogLevel = process.env._WORKER_LOG_LEVEL; } const createRequest = { tags: this.#convertKeyToRivetTags(name, key), build_tags: { name, - role: "actor", - framework: "@rivetkit/actor", + role: "worker", + framework: "rivetkit", current: "true", }, region, @@ -166,9 +166,9 @@ export class RivetManagerDriver implements ManagerDriver { }, }, runtime: { - environment: actorLogLevel + environment: workerLogLevel ? { - _LOG_LEVEL: actorLogLevel, + _LOG_LEVEL: workerLogLevel, } : {}, }, @@ -177,19 +177,19 @@ export class RivetManagerDriver implements ManagerDriver { }, }; - logger().info("creating actor", { ...createRequest }); + logger().info("creating worker", { ...createRequest }); - // Create the actor - const { actor } = await rivetRequest< + // Create the worker + const { worker } = await rivetRequest< typeof createRequest, - { actor: RivetActor } - >(this.#clientConfig, "POST", "/actors", createRequest); + { worker: RivetWorker } + >(this.#clientConfig, "POST", "/workers", createRequest); - // Initialize the actor + // Initialize the worker try { - const endpoint = buildActorEndpoint(actor); + const endpoint = buildWorkerEndpoint(worker); const url = `${endpoint}/initialize`; - logger().debug("initializing actor", { url, input: JSON.stringify(input) }); + logger().debug("initializing worker", { url, input: JSON.stringify(input) }); const res = await fetch(url, { method: "POST", @@ -200,32 +200,32 @@ export class RivetManagerDriver implements ManagerDriver { }); if (!res.ok) { throw new InternalError( - `Actor initialize request failed (${res.status}):\n${await res.text()}`, + `Worker initialize request failed (${res.status}):\n${await res.text()}`, ); } } catch (error) { - logger().error("failed to initialize actor, destroying actor", { - actorId: actor.id, + logger().error("failed to initialize worker, destroying worker", { + workerId: worker.id, error, }); - // Destroy the actor since it failed to initialize - await rivetRequest( + // Destroy the worker since it failed to initialize + await rivetRequest( this.#clientConfig, "DELETE", - `/actors/${actor.id}`, + `/workers/${worker.id}`, ); throw error; } return { - actorId: actor.id, + workerId: worker.id, name, - key: this.#extractKeyFromRivetTags(actor.tags), + key: this.#extractKeyFromRivetTags(worker.tags), meta: { - endpoint: buildActorEndpoint(actor), - } satisfies GetActorMeta, + endpoint: buildWorkerEndpoint(worker), + } satisfies GetWorkerMeta, }; } @@ -234,8 +234,8 @@ export class RivetManagerDriver implements ManagerDriver { return { name, key: serializeKeyForTag(key), - role: "actor", - framework: "@rivetkit/actor", + role: "worker", + framework: "rivetkit", }; } @@ -265,9 +265,9 @@ export class RivetManagerDriver implements ManagerDriver { } } -function buildActorEndpoint(actor: RivetActor): string { +function buildWorkerEndpoint(worker: RivetWorker): string { // Fetch port - const httpPort = actor.network.ports.http; + const httpPort = worker.network.ports.http; if (!httpPort) throw new Error("missing http port"); let hostname = httpPort.hostname; if (!hostname) throw new Error("missing hostname"); @@ -299,6 +299,6 @@ function buildActorEndpoint(actor: RivetActor): string { } // biome-ignore lint/suspicious/noExplicitAny: will add api types later -type RivetActor = any; +type RivetWorker = any; // biome-ignore lint/suspicious/noExplicitAny: will add api types later type RivetBuild = any; diff --git a/packages/platforms/rivet/src/manager-handler.ts b/packages/platforms/rivet/src/manager-handler.ts index 42ed87b76..eda892f5b 100644 --- a/packages/platforms/rivet/src/manager-handler.ts +++ b/packages/platforms/rivet/src/manager-handler.ts @@ -1,12 +1,12 @@ -import { setupLogging } from "@rivetkit/actor/log"; -import { stringifyError } from "@rivetkit/actor/utils"; -import type { ActorContext } from "@rivet-gg/actor-core"; +import { setupLogging } from "rivetkit/log"; +import { stringifyError } from "rivetkit/utils"; +import type { WorkerContext } from "@rivet-gg/worker-core"; import { logger } from "./log"; -import { GetActorMeta, RivetManagerDriver } from "./manager-driver"; +import { GetWorkerMeta, RivetManagerDriver } from "./manager-driver"; import type { RivetClientConfig } from "./rivet-client"; import type { RivetHandler } from "./util"; import { createWebSocketProxy } from "./ws-proxy"; -import { PartitionTopologyManager } from "@rivetkit/actor/topologies/partition"; +import { PartitionTopologyManager } from "rivetkit/topologies/partition"; import { type InputConfig, ConfigSchema } from "./config"; import { proxy } from "hono/proxy"; import invariant from "invariant"; @@ -27,7 +27,7 @@ export function createManagerHandlerInner( const driverConfig = ConfigSchema.parse(inputConfig); const handler = { - async start(ctx: ActorContext): Promise { + async start(ctx: WorkerContext): Promise { setupLogging(); const portStr = Deno.env.get("PORT_HTTP"); @@ -96,33 +96,33 @@ export function createManagerHandlerInner( driverConfig.app.config, driverConfig, { - onProxyRequest: async (c, actorRequest, _actorId, metaRaw) => { + onProxyRequest: async (c, workerRequest, _workerId, metaRaw) => { invariant(metaRaw, "meta not provided"); - const meta = metaRaw as GetActorMeta; + const meta = metaRaw as GetWorkerMeta; - const parsedRequestUrl = new URL(actorRequest.url); - const actorUrl = `${meta.endpoint}${parsedRequestUrl.pathname}${parsedRequestUrl.search}`; + const parsedRequestUrl = new URL(workerRequest.url); + const workerUrl = `${meta.endpoint}${parsedRequestUrl.pathname}${parsedRequestUrl.search}`; - logger().debug("proxying request to rivet actor", { - method: actorRequest.method, - url: actorUrl, + logger().debug("proxying request to rivet worker", { + method: workerRequest.method, + url: workerUrl, }); - const proxyRequest = new Request(actorUrl, actorRequest); + const proxyRequest = new Request(workerUrl, workerRequest); return await proxy(proxyRequest); }, - onProxyWebSocket: async (c, path, actorId, metaRaw) => { + onProxyWebSocket: async (c, path, workerId, metaRaw) => { invariant(metaRaw, "meta not provided"); - const meta = metaRaw as GetActorMeta; + const meta = metaRaw as GetWorkerMeta; - const actorUrl = `${meta.endpoint}${path}`; + const workerUrl = `${meta.endpoint}${path}`; - logger().debug("proxying websocket to rivet actor", { - url: actorUrl, + logger().debug("proxying websocket to rivet worker", { + url: workerUrl, }); // TODO: fix as any - return createWebSocketProxy(c, actorUrl) as any; + return createWebSocketProxy(c, workerUrl) as any; }, }, ); diff --git a/packages/platforms/rivet/src/mod.ts b/packages/platforms/rivet/src/mod.ts index 3d2d3febc..4b73b634c 100644 --- a/packages/platforms/rivet/src/mod.ts +++ b/packages/platforms/rivet/src/mod.ts @@ -1,3 +1,3 @@ -export { createActorHandler } from "./actor-handler"; +export { createWorkerHandler } from "./worker-handler"; export { createManagerHandler } from "./manager-handler"; export type { InputConfig as Config } from "./config"; diff --git a/packages/platforms/rivet/src/rivet-client.ts b/packages/platforms/rivet/src/rivet-client.ts index 5710a60e8..28f42035b 100644 --- a/packages/platforms/rivet/src/rivet-client.ts +++ b/packages/platforms/rivet/src/rivet-client.ts @@ -1,4 +1,4 @@ -import { httpUserAgent } from "@rivetkit/actor/utils"; +import { httpUserAgent } from "rivetkit/utils"; export interface RivetClientConfig { endpoint: string; diff --git a/packages/platforms/rivet/src/util.ts b/packages/platforms/rivet/src/util.ts index 5dda225c6..3bcab0a68 100644 --- a/packages/platforms/rivet/src/util.ts +++ b/packages/platforms/rivet/src/util.ts @@ -1,8 +1,8 @@ -import type { ActorContext } from "@rivet-gg/actor-core"; +import type { WorkerContext } from "@rivet-gg/worker-core"; import invariant from "invariant"; export interface RivetHandler { - start(ctx: ActorContext): Promise; + start(ctx: WorkerContext): Promise; } // Constants for key handling diff --git a/packages/platforms/rivet/src/worker-driver.ts b/packages/platforms/rivet/src/worker-driver.ts new file mode 100644 index 000000000..9eae13099 --- /dev/null +++ b/packages/platforms/rivet/src/worker-driver.ts @@ -0,0 +1,55 @@ +import type { WorkerContext } from "@rivet-gg/worker-core"; +import type { WorkerDriver, AnyWorkerInstance } from "rivetkit/driver-helpers"; + +export interface WorkerDriverContext { + ctx: WorkerContext; +} + +export class RivetWorkerDriver implements WorkerDriver { + #ctx: WorkerContext; + + constructor(ctx: WorkerContext) { + this.#ctx = ctx; + } + + getContext(_workerId: string): WorkerDriverContext { + return { ctx: this.#ctx }; + } + + async readInput(_workerId: string): Promise { + // Read input + // + // We need to have a separate exists flag in order to represent `undefined` + const entries = await this.#ctx.kv.getBatch([ + ["rivetkit", "input", "exists"], + ["rivetkit", "input", "data"], + ]); + + if (entries.get(["rivetkit", "input", "exists"]) === true) { + return await entries.get(["rivetkit", "input", "data"]); + } else { + return undefined; + } + } + + async readPersistedData(_workerId: 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. + if (data === null) data = undefined; + + return data; + } + + async writePersistedData(_workerId: 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 { + const timeout = Math.max(0, timestamp - Date.now()); + setTimeout(() => { + worker.onAlarm(); + }, timeout); + } +} diff --git a/packages/platforms/rivet/src/actor-handler.ts b/packages/platforms/rivet/src/worker-handler.ts similarity index 71% rename from packages/platforms/rivet/src/actor-handler.ts rename to packages/platforms/rivet/src/worker-handler.ts index b87f49e58..3ebd9777e 100644 --- a/packages/platforms/rivet/src/actor-handler.ts +++ b/packages/platforms/rivet/src/worker-handler.ts @@ -1,30 +1,30 @@ -import { setupLogging } from "@rivetkit/actor/log"; -import { stringifyError } from "@rivetkit/actor/utils"; -import type { ActorContext } from "@rivet-gg/actor-core"; +import { setupLogging } from "rivetkit/log"; +import { stringifyError } from "rivetkit/utils"; +import type { WorkerContext } from "@rivet-gg/worker-core"; import { upgradeWebSocket } from "hono/deno"; import { logger } from "./log"; import type { RivetHandler } from "./util"; import { deserializeKeyFromTag } from "./util"; -import { PartitionTopologyActor } from "@rivetkit/actor/topologies/partition"; +import { PartitionTopologyWorker } from "rivetkit/topologies/partition"; import { ConfigSchema, type InputConfig } from "./config"; -import { RivetActorDriver } from "./actor-driver"; +import { RivetWorkerDriver } from "./worker-driver"; import { rivetRequest } from "./rivet-client"; import invariant from "invariant"; -export function createActorHandler(inputConfig: InputConfig): RivetHandler { +export function createWorkerHandler(inputConfig: InputConfig): RivetHandler { try { - return createActorHandlerInner(inputConfig); + return createWorkerHandlerInner(inputConfig); } catch (error) { - logger().error("failed to start actor", { error: stringifyError(error) }); + logger().error("failed to start worker", { error: stringifyError(error) }); Deno.exit(1); } } -function createActorHandlerInner(inputConfig: InputConfig): RivetHandler { +function createWorkerHandlerInner(inputConfig: InputConfig): RivetHandler { const driverConfig = ConfigSchema.parse(inputConfig); const handler = { - async start(ctx: ActorContext): Promise { + async start(ctx: WorkerContext): Promise { setupLogging(); const portStr = Deno.env.get("PORT_HTTP"); @@ -41,14 +41,14 @@ function createActorHandlerInner(inputConfig: InputConfig): RivetHandler { // Initialization promise const initializedPromise = Promise.withResolvers(); - if ((await ctx.kv.get(["@rivetkit/actor", "initialized"])) === true) { + if ((await ctx.kv.get(["rivetkit", "initialized"])) === true) { initializedPromise.resolve(undefined); } - // Setup actor driver + // Setup worker driver if (!driverConfig.drivers) driverConfig.drivers = {}; - if (!driverConfig.drivers.actor) { - driverConfig.drivers.actor = new RivetActorDriver(ctx); + if (!driverConfig.drivers.worker) { + driverConfig.drivers.worker = new RivetWorkerDriver(ctx); } // Setup WebSocket upgrader @@ -102,15 +102,15 @@ function createActorHandlerInner(inputConfig: InputConfig): RivetHandler { }, }; - // Create actor topology + // Create worker topology driverConfig.topology = driverConfig.topology ?? "partition"; - const actorTopology = new PartitionTopologyActor( + const workerTopology = new PartitionTopologyWorker( driverConfig.app.config, driverConfig, ); // Set a catch-all route - const router = actorTopology.router; + const router = workerTopology.router; // TODO: This needs to be secured // TODO: This needs to assert this has only been called once @@ -126,8 +126,8 @@ function createActorHandlerInner(inputConfig: InputConfig): RivetHandler { if (body.input) { await ctx.kv.putBatch( new Map([ - [["@rivetkit/actor", "input", "exists"], true], - [["@rivetkit/actor", "input", "data"], body.input], + [["rivetkit", "input", "exists"], true], + [["rivetkit", "input", "data"], body.input], ]), ); } @@ -151,20 +151,20 @@ function createActorHandlerInner(inputConfig: InputConfig): RivetHandler { ); // Assert name exists - if (!("name" in ctx.metadata.actor.tags)) { + if (!("name" in ctx.metadata.worker.tags)) { throw new Error( - `Tags for actor ${ctx.metadata.actor.id} do not contain property name: ${JSON.stringify(ctx.metadata.actor.tags)}`, + `Tags for worker ${ctx.metadata.worker.id} do not contain property name: ${JSON.stringify(ctx.metadata.worker.tags)}`, ); } // Extract key from Rivet's tag format - const key = extractKeyFromRivetTags(ctx.metadata.actor.tags); + const key = extractKeyFromRivetTags(ctx.metadata.worker.tags); - // Start actor after initialized + // Start worker after initialized await initializedPromise.promise; - await actorTopology.start( - ctx.metadata.actor.id, - ctx.metadata.actor.tags.name, + await workerTopology.start( + ctx.metadata.worker.id, + ctx.metadata.worker.tags.name, key, ctx.metadata.region.id, ); diff --git a/packages/platforms/rivet/tests/deployment.test.ts b/packages/platforms/rivet/tests/deployment.test.ts index 91cfd2979..d1e462f21 100644 --- a/packages/platforms/rivet/tests/deployment.test.ts +++ b/packages/platforms/rivet/tests/deployment.test.ts @@ -6,11 +6,11 @@ import { deployToRivet } from "./rivet-deploy"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -// Simple counter actor definition to deploy -const COUNTER_ACTOR = ` -import { actor, setup } from "@rivetkit/actor"; +// Simple counter worker definition to deploy +const COUNTER_WORKER = ` +import { worker, setup } from "rivetkit"; -const counter = actor({ +const counter = worker({ state: { count: 0 }, actions: { increment: (c, amount) => { @@ -25,14 +25,14 @@ const counter = actor({ }); export const app = setup({ - actors: { counter }, + workers: { counter }, }); export type App = typeof app; `; test("Rivet deployment tests", async () => { - // Create a temporary path for the counter actor + // Create a temporary path for the counter worker const tempFilePath = path.join( __dirname, "../../../..", @@ -43,8 +43,8 @@ test("Rivet deployment tests", async () => { // Ensure target directory exists await fs.mkdir(path.dirname(tempFilePath), { recursive: true }); - // Write the counter actor file - await fs.writeFile(tempFilePath, COUNTER_ACTOR); + // Write the counter worker file + await fs.writeFile(tempFilePath, COUNTER_WORKER); // Run the deployment const result = await deployToRivet(tempFilePath, true); diff --git a/packages/platforms/rivet/tests/driver-tests.test.ts b/packages/platforms/rivet/tests/driver-tests.test.ts index 16843b684..df8e5b0f7 100644 --- a/packages/platforms/rivet/tests/driver-tests.test.ts +++ b/packages/platforms/rivet/tests/driver-tests.test.ts @@ -1,4 +1,4 @@ -import { runDriverTests } from "@rivetkit/actor/driver-test-suite"; +import { runDriverTests } from "rivetkit/driver-test-suite"; import { deployToRivet, RIVET_CLIENT_CONFIG } from "./rivet-deploy"; import { type RivetClientConfig, rivetRequest } from "../src/rivet-client"; import invariant from "invariant"; @@ -17,8 +17,8 @@ const driverTestConfig = { managerEndpoint, }); - // Cleanup actors from previous tests - await deleteAllActors(RIVET_CLIENT_CONFIG, !alreadyDeployedManager); + // Cleanup workers from previous tests + await deleteAllWorkers(RIVET_CLIENT_CONFIG, !alreadyDeployedManager); if (!alreadyDeployedApps.has(appPath)) { console.log(`Starting Rivet driver tests with app: ${appPath}`); @@ -41,30 +41,30 @@ const driverTestConfig = { return { endpoint: managerEndpoint, async cleanup() { - await deleteAllActors(RIVET_CLIENT_CONFIG, false); + await deleteAllWorkers(RIVET_CLIENT_CONFIG, false); }, }; }, }; -async function deleteAllActors( +async function deleteAllWorkers( clientConfig: RivetClientConfig, deleteManager: boolean, ) { - console.log("Listing actors to delete"); - const { actors } = await rivetRequest< + console.log("Listing workers to delete"); + const { workers } = await rivetRequest< void, - { actors: { id: string; tags: Record }[] } - >(clientConfig, "GET", "/actors"); + { workers: { id: string; tags: Record }[] } + >(clientConfig, "GET", "/workers"); - for (const actor of actors) { - if (!deleteManager && actor.tags.name === "manager") continue; + for (const worker of workers) { + if (!deleteManager && worker.tags.name === "manager") continue; - console.log(`Deleting actor ${actor.id} (${JSON.stringify(actor.tags)})`); + console.log(`Deleting worker ${worker.id} (${JSON.stringify(worker.tags)})`); await rivetRequest( clientConfig, "DELETE", - `/actors/${actor.id}`, + `/workers/${worker.id}`, ); } } diff --git a/packages/platforms/rivet/tests/rivet-deploy.ts b/packages/platforms/rivet/tests/rivet-deploy.ts index ab69ba24f..84e9c99ef 100644 --- a/packages/platforms/rivet/tests/rivet-deploy.ts +++ b/packages/platforms/rivet/tests/rivet-deploy.ts @@ -28,23 +28,23 @@ export async function deployToRivet(appPath: string, deployManager: boolean) { // Create a temporary directory for the test const uuid = crypto.randomUUID(); - const appName = `actor-core-test-${uuid}`; + const appName = `worker-core-test-${uuid}`; const tmpDir = path.join(os.tmpdir(), appName); console.log(`Creating temp directory: ${tmpDir}`); await fs.mkdir(tmpDir, { recursive: true }); // Create package.json with workspace dependencies const packageJson = { - name: "@rivetkit/actor-test", + name: "rivetkit-test", private: true, version: "1.0.0", type: "module", scripts: { - deploy: "@rivetkit/actor deploy rivet app.ts --env prod", + deploy: "rivetkit deploy rivet app.ts --env prod", }, dependencies: { "@rivetkit/rivet": "workspace:*", - "@rivetkit/actor": "workspace:*", + "rivetkit": "workspace:*", }, packageManager: "yarn@4.7.0+sha512.5a0afa1d4c1d844b3447ee3319633797bcd6385d9a44be07993ae52ff4facabccafb4af5dcd1c2f9a94ac113e5e9ff56f6130431905884414229e284e37bb7c9", @@ -90,11 +90,11 @@ export async function deployToRivet(appPath: string, deployManager: boolean) { console.log(`Creating app.ts with content: ${appTsContent}`); await fs.writeFile(path.join(tmpDir, "app.ts"), appTsContent); - // Build and deploy to Rivet using actor-core CLI + // Build and deploy to Rivet using worker-core CLI console.log("Building and deploying to Rivet..."); if (!process.env._RIVET_SKIP_DEPLOY) { - // Deploy using the actor-core CLI + // Deploy using the worker-core CLI console.log("Spawning rivetkit/cli deploy command..."); const deployProcess = spawn( "npx", @@ -114,7 +114,7 @@ export async function deployToRivet(appPath: string, deployManager: boolean) { RIVET_ENDPOINT: RIVET_API_ENDPOINT, RIVET_CLOUD_TOKEN: rivetCloudToken, _RIVET_MANAGER_LOG_LEVEL: "DEBUG", - _RIVET_ACTOR_LOG_LEVEL: "DEBUG", + _RIVET_WORKER_LOG_LEVEL: "DEBUG", //CI: "1", }, stdio: "inherit", // Stream output directly to console diff --git a/scripts/release.ts b/scripts/release.ts index e3cbf55b9..9b6b86c84 100755 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -153,7 +153,7 @@ async function publishRustClient(version: string) { // Check if package already exists const { exitCode } = await $({ nothrow: true, - })`cargo search actor-core-client --limit 1 | grep "@rivetkit/actor-client = \\"${version}\\""`; + })`cargo search actor-core-client --limit 1 | grep "rivetkit-client = \\"${version}\\""`; if (exitCode === 0) { console.log( @@ -352,7 +352,7 @@ async function getPublicPackages() { function validatePackages(publicPackages: any[]) { const nonActorCorePackages = publicPackages.filter( (pkg) => - pkg.name !== "@rivetkit/actor" && + pkg.name !== "rivetkit" && pkg.name !== "create-actor" && !pkg.name.startsWith("@rivetkit/"), ); diff --git a/yarn.lock b/yarn.lock index fd5c4e7f5..3b0ed7117 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1610,57 +1610,23 @@ __metadata: languageName: node linkType: hard -"@rivetkit/actor@workspace:*, @rivetkit/actor@workspace:^, @rivetkit/actor@workspace:packages/actor": - version: 0.0.0-use.local - resolution: "@rivetkit/actor@workspace:packages/actor" - dependencies: - "@hono/node-server": "npm:^1.14.0" - "@hono/node-ws": "npm:^1.1.1" - "@hono/zod-openapi": "npm:^0.19.6" - "@types/invariant": "npm:^2" - "@types/node": "npm:^22.13.1" - "@types/ws": "npm:^8" - bundle-require: "npm:^5.1.0" - cbor-x: "npm:^1.6.0" - eventsource: "npm:^3.0.5" - hono: "npm:^4.7.0" - invariant: "npm:^2.2.4" - on-change: "npm:^5.0.1" - p-retry: "npm:^6.2.1" - tsup: "npm:^8.4.0" - tsx: "npm:^4.19.4" - typescript: "npm:^5.7.3" - vitest: "npm:^3.1.1" - ws: "npm:^8.18.1" - zod: "npm:^3.24.1" - peerDependencies: - eventsource: ^3.0.5 - ws: ^8.0.0 - peerDependenciesMeta: - eventsource: - optional: true - ws: - optional: true - languageName: unknown - linkType: soft - "@rivetkit/bun@workspace:packages/platforms/bun": version: 0.0.0-use.local resolution: "@rivetkit/bun@workspace:packages/platforms/bun" dependencies: - "@rivetkit/actor": "workspace:*" "@rivetkit/file-system": "workspace:^" "@rivetkit/memory": "workspace:*" "@types/bun": "npm:^1.2.2" dedent: "npm:^1.5.3" hono: "npm:^4.7.0" + rivetkit: "workspace:*" tsup: "npm:^8.4.0" typescript: "npm:^5.5.2" zod: "npm:^3.24.2" peerDependencies: - "@rivetkit/actor": "*" "@rivetkit/file-system": "*" "@rivetkit/memory": "*" + rivetkit: "*" languageName: unknown linkType: soft @@ -1669,17 +1635,17 @@ __metadata: resolution: "@rivetkit/cloudflare-workers@workspace:packages/platforms/cloudflare-workers" dependencies: "@cloudflare/workers-types": "npm:^4.20250129.0" - "@rivetkit/actor": "workspace:*" "@types/invariant": "npm:^2" hono: "npm:^4.7.2" invariant: "npm:^2.2.4" + rivetkit: "workspace:*" tsup: "npm:^8.4.0" typescript: "npm:^5.5.2" vitest: "npm:^3.1.1" wrangler: "npm:^3.101.0" zod: "npm:^3.24.2" peerDependencies: - "@rivetkit/actor": "*" + rivetkit: "*" languageName: unknown linkType: soft @@ -1687,17 +1653,17 @@ __metadata: version: 0.0.0-use.local resolution: "@rivetkit/file-system@workspace:packages/drivers/file-system" dependencies: - "@rivetkit/actor": "workspace:*" "@types/invariant": "npm:^2" "@types/node": "npm:^22.14.0" env-paths: "npm:^3.0.0" hono: "npm:^4.7.0" invariant: "npm:^2.2.4" + rivetkit: "workspace:*" tsup: "npm:^8.4.0" typescript: "npm:^5.5.2" vitest: "npm:^3.1.1" peerDependencies: - "@rivetkit/actor": "*" + rivetkit: "*" languageName: unknown linkType: soft @@ -1705,12 +1671,12 @@ __metadata: version: 0.0.0-use.local resolution: "@rivetkit/framework-base@workspace:packages/frameworks/framework-base" dependencies: - "@rivetkit/actor": "workspace:*" + rivetkit: "workspace:*" tsup: "npm:^8.3.6" typescript: "npm:^5.5.2" vitest: "npm:^3.1.1" peerDependencies: - "@rivetkit/actor": "*" + rivetkit: "*" languageName: unknown linkType: soft @@ -1718,14 +1684,14 @@ __metadata: version: 0.0.0-use.local resolution: "@rivetkit/memory@workspace:packages/drivers/memory" dependencies: - "@rivetkit/actor": "workspace:*" "@types/node": "npm:^22.13.1" hono: "npm:^4.7.0" + rivetkit: "workspace:*" tsup: "npm:^8.4.0" typescript: "npm:^5.5.2" vitest: "npm:^3.1.1" peerDependencies: - "@rivetkit/actor": "*" + rivetkit: "*" languageName: unknown linkType: soft @@ -1735,17 +1701,17 @@ __metadata: dependencies: "@hono/node-server": "npm:^1.13.8" "@hono/node-ws": "npm:^1.0.8" - "@rivetkit/actor": "workspace:*" "@rivetkit/file-system": "workspace:*" "@rivetkit/memory": "workspace:*" hono: "npm:^4.7.0" + rivetkit: "workspace:*" tsup: "npm:^8.4.0" typescript: "npm:^5.5.2" zod: "npm:^3.24.2" peerDependencies: - "@rivetkit/actor": "*" "@rivetkit/file-system": "*" "@rivetkit/memory": "*" + rivetkit: "*" languageName: unknown linkType: soft @@ -1753,15 +1719,15 @@ __metadata: version: 0.0.0-use.local resolution: "@rivetkit/react@workspace:packages/frameworks/react" dependencies: - "@rivetkit/actor": "workspace:^" "@rivetkit/framework-base": "workspace:*" + rivetkit: "workspace:^" tsup: "npm:^8.3.6" typescript: "npm:^5.5.2" vitest: "npm:^3.1.1" peerDependencies: - "@rivetkit/actor": "*" react: ^18 || ^19 react-dom: ^18 || ^19 + rivetkit: "*" languageName: unknown linkType: soft @@ -1769,18 +1735,18 @@ __metadata: version: 0.0.0-use.local resolution: "@rivetkit/redis@workspace:packages/drivers/redis" dependencies: - "@rivetkit/actor": "workspace:*" "@types/node": "npm:^22.13.1" dedent: "npm:^1.5.3" hono: "npm:^4.7.0" ioredis: "npm:^5.4.2" p-retry: "npm:^6.2.1" + rivetkit: "workspace:*" tsup: "npm:^8.4.0" typescript: "npm:^5.5.2" vitest: "npm:^3.1.1" zx: "npm:^7.2.3" peerDependencies: - "@rivetkit/actor": "workspace:*" + rivetkit: "workspace:*" languageName: unknown linkType: soft @@ -1789,18 +1755,18 @@ __metadata: resolution: "@rivetkit/rivet@workspace:packages/platforms/rivet" dependencies: "@rivet-gg/actor-core": "npm:^25.1.0" - "@rivetkit/actor": "workspace:*" "@types/deno": "npm:^2.0.0" "@types/invariant": "npm:^2" "@types/node": "npm:^22.13.1" hono: "npm:^4.7.0" invariant: "npm:^2.2.4" + rivetkit: "workspace:*" tsup: "npm:^8.4.0" typescript: "npm:^5.5.2" vitest: "npm:^3.1.1" zod: "npm:^3.24.2" peerDependencies: - "@rivetkit/actor": "*" + rivetkit: "*" languageName: unknown linkType: soft @@ -2623,8 +2589,8 @@ __metadata: version: 0.0.0-use.local resolution: "chat-room-python@workspace:examples/chat-room-python" dependencies: - "@rivetkit/actor": "workspace:*" "@types/node": "npm:^22.13.9" + rivetkit: "workspace:*" tsx: "npm:^3.12.7" typescript: "npm:^5.5.2" languageName: unknown @@ -2634,10 +2600,10 @@ __metadata: version: 0.0.0-use.local resolution: "chat-room@workspace:examples/chat-room" dependencies: - "@rivetkit/actor": "workspace:*" "@types/node": "npm:^22.13.9" "@types/prompts": "npm:^2" prompts: "npm:^2.4.2" + rivetkit: "workspace:*" tsx: "npm:^3.12.7" typescript: "npm:^5.5.2" vitest: "npm:^3.1.1" @@ -2850,8 +2816,8 @@ __metadata: version: 0.0.0-use.local resolution: "counter@workspace:examples/counter" dependencies: - "@rivetkit/actor": "workspace:*" "@types/node": "npm:^22.13.9" + rivetkit: "workspace:*" tsx: "npm:^3.12.7" typescript: "npm:^5.7.3" vitest: "npm:^3.1.1" @@ -4467,7 +4433,6 @@ __metadata: "@hono/node-server": "npm:^1.14.1" "@linear/sdk": "npm:^7.0.0" "@octokit/rest": "npm:^19.0.13" - "@rivetkit/actor": "workspace:*" "@types/dotenv": "npm:^8.2.3" "@types/express": "npm:^5" "@types/node": "npm:^22.13.9" @@ -4480,6 +4445,7 @@ __metadata: hono: "npm:^4.7.7" linear-webhook: "npm:^0.1.3" prompts: "npm:^2.4.2" + rivetkit: "workspace:*" tsx: "npm:^3.12.7" typescript: "npm:^5.5.2" vitest: "npm:^3.1.1" @@ -5385,10 +5351,10 @@ __metadata: resolution: "resend-streaks@workspace:examples/resend-streaks" dependencies: "@date-fns/tz": "npm:^1.2.0" - "@rivetkit/actor": "workspace:*" "@types/node": "npm:^22.13.9" date-fns: "npm:^4.1.0" resend: "npm:^2.0.0" + rivetkit: "workspace:*" tsx: "npm:^3.12.7" typescript: "npm:^5.7.3" vitest: "npm:^3.1.1" @@ -5439,6 +5405,40 @@ __metadata: languageName: node linkType: hard +"rivetkit@workspace:*, rivetkit@workspace:^, rivetkit@workspace:packages/core": + version: 0.0.0-use.local + resolution: "rivetkit@workspace:packages/core" + dependencies: + "@hono/node-server": "npm:^1.14.0" + "@hono/node-ws": "npm:^1.1.1" + "@hono/zod-openapi": "npm:^0.19.6" + "@types/invariant": "npm:^2" + "@types/node": "npm:^22.13.1" + "@types/ws": "npm:^8" + bundle-require: "npm:^5.1.0" + cbor-x: "npm:^1.6.0" + eventsource: "npm:^3.0.5" + hono: "npm:^4.7.0" + invariant: "npm:^2.2.4" + on-change: "npm:^5.0.1" + p-retry: "npm:^6.2.1" + tsup: "npm:^8.4.0" + tsx: "npm:^4.19.4" + typescript: "npm:^5.7.3" + vitest: "npm:^3.1.1" + ws: "npm:^8.18.1" + zod: "npm:^3.24.1" + peerDependencies: + eventsource: ^3.0.5 + ws: ^8.0.0 + peerDependenciesMeta: + eventsource: + optional: true + ws: + optional: true + languageName: unknown + linkType: soft + "rollup-plugin-inject@npm:^3.0.0": version: 3.0.2 resolution: "rollup-plugin-inject@npm:3.0.2"