From 077bd4e24add02138848a58327b9a03299f71316 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Wed, 6 May 2026 21:18:19 -0700 Subject: [PATCH] fix(rivetkit): fix default spawn engine behavior --- examples/CLAUDE.md | 6 +- examples/actor-actions-vercel/src/server.ts | 3 +- examples/ai-agent-vercel/src/server.ts | 3 +- .../src/deploy-with-rivet-cloud.ts | 2 +- .../src/deploy-with-rivet-self-hosted.ts | 2 +- .../src/utils.ts | 1 - examples/ai-generated-actor/src/server.ts | 3 +- examples/chat-room-render/README.md | 2 +- examples/chat-room-render/render.yaml | 2 +- examples/chat-room-render/src/env.ts | 11 +- examples/chat-room-render/src/index.ts | 17 +- examples/chat-room-render/src/server.ts | 7 +- examples/chat-room-vercel/src/server.ts | 3 +- .../src/server.ts | 3 +- .../cross-actor-actions-vercel/src/server.ts | 3 +- .../src/server.ts | 3 +- examples/cursors-vercel/src/server.ts | 3 +- examples/dynamic-actors/src/server.ts | 3 +- examples/elysia/src/index.ts | 2 - examples/elysia/src/server.ts | 6 +- .../src/server.ts | 3 +- .../src/server.ts | 3 +- examples/hello-world-render/README.md | 2 +- examples/hello-world-render/render.yaml | 2 +- examples/hello-world-render/src/config/env.ts | 15 +- .../hello-world-render/src/http/server.ts | 5 +- .../src/http/web-request.ts | 2 +- examples/hello-world-vercel/src/server.ts | 3 +- examples/hono-react-vercel/src/server.ts | 3 +- examples/hono-react/src/index.ts | 2 - examples/hono-react/src/server.ts | 6 +- examples/hono/src/index.ts | 2 - examples/hono/src/server.ts | 6 +- examples/kitchen-sink-vercel/src/index.ts | 39 -- examples/kitchen-sink-vercel/src/server.ts | 4 +- examples/kitchen-sink/package.json | 2 +- .../kitchen-sink/scripts/mock-agentic-loop.ts | 2 +- .../scripts/sqlite-memory-soak.ts | 2 - examples/kitchen-sink/src/cloudflare.ts | 4 +- examples/kitchen-sink/src/index.ts | 53 --- examples/kitchen-sink/src/server.ts | 31 +- .../supabase/functions/rivetkit/index.ts | 4 +- examples/multi-region-vercel/src/server.ts | 3 +- .../src/server.ts | 3 +- .../multiplayer-game-vercel/src/server.ts | 3 +- .../native-websockets-vercel/src/server.ts | 3 +- .../per-tenant-database-vercel/src/server.ts | 3 +- .../raw-fetch-handler-vercel/src/server.ts | 3 +- examples/raw-fetch-handler/src/index.ts | 2 - examples/raw-fetch-handler/src/server.ts | 6 +- .../src/server.ts | 3 +- .../raw-websocket-handler-proxy/src/index.ts | 2 - .../raw-websocket-handler-proxy/src/server.ts | 6 +- .../src/server.ts | 3 +- examples/react-render/README.md | 2 +- examples/react-render/render.yaml | 2 +- examples/react-render/src/env.ts | 9 +- examples/react-render/src/index.ts | 17 +- examples/react-render/src/server.ts | 7 +- examples/react-vercel/src/server.ts | 3 +- .../sandbox-coding-agent-vercel/src/server.ts | 3 +- examples/scheduling-vercel/src/server.ts | 3 +- examples/state-render/README.md | 2 +- examples/state-render/render.yaml | 2 +- examples/state-render/src/env.ts | 11 +- examples/state-render/src/index.ts | 17 +- examples/state-render/src/server.ts | 7 +- examples/state-vercel/src/server.ts | 3 +- examples/stream-render/README.md | 2 +- examples/stream-render/render.yaml | 2 +- examples/stream-render/src/env.ts | 9 +- examples/stream-render/src/index.ts | 17 +- examples/stream-render/src/server.ts | 7 +- examples/stream-vercel/src/server.ts | 3 +- examples/trpc/src/index.ts | 2 - examples/trpc/src/server.ts | 6 +- .../src/registry/envoy_callbacks.rs | 12 +- .../rivetkit-core/src/registry/mod.rs | 11 + .../src/registry/runner_config.rs | 144 +++++- .../rivetkit-core/tests/serverless.rs | 4 + .../packages/rivetkit/tests/client.rs | 1 + .../artifacts/registry-config.json | 153 ++---- .../packages/rivetkit-napi/index.d.ts | 5 + .../packages/rivetkit-napi/src/registry.rs | 14 + .../packages/rivetkit/runtime/index.ts | 8 +- .../packages/rivetkit/src/client/config.ts | 2 +- .../packages/rivetkit/src/common/router.ts | 2 +- .../src/drivers/engine/actor-driver.ts | 6 +- .../rivetkit/src/registry/config/envoy.ts | 66 +-- .../rivetkit/src/registry/config/index.ts | 440 +++++++++++------- .../src/registry/config/serverless.ts | 22 +- .../packages/rivetkit/src/registry/index.ts | 428 +++++++++++++---- .../packages/rivetkit/src/registry/native.ts | 31 +- .../packages/rivetkit/src/registry/runtime.ts | 23 +- .../rivetkit/src/serverless/configure.ts | 109 ----- .../packages/rivetkit/src/utils/env-vars.ts | 101 +++- .../tests/driver/serverless-handler.test.ts | 15 +- .../fixtures/driver-test-suite-runtime.ts | 10 +- .../driver-test-suite-wasm-runtime.ts | 10 +- .../tests/fixtures/napi-runtime-server.ts | 6 +- .../tests/napi-runtime-integration.test.ts | 9 +- .../tests/native-runtime-errors.test.ts | 2 +- .../rivetkit/tests/platforms/deno.test.ts | 3 +- .../platforms/shared-platform-harness.ts | 5 +- .../platforms/supabase-functions.test.ts | 10 +- .../tests/registry-constructor.test.ts | 35 +- .../tests/runtime-mode-config.test.ts | 231 +++++++++ .../rivetkit/tests/runtime-parity.test.ts | 61 ++- .../rivetkit/tests/runtime-selection.test.ts | 2 +- .../rivetkit/tests/wasm-host-smoke.test.ts | 1 - .../rivetkit/tests/wasm-runtime.test.ts | 12 +- website/src/content/docs/actors/actions.mdx | 5 +- website/src/content/docs/actors/index.mdx | 33 +- website/src/content/docs/actors/limits.mdx | 6 +- website/src/content/docs/actors/postgres.mdx | 4 +- .../docs/actors/quickstart/backend.mdx | 6 +- .../content/docs/actors/quickstart/react.mdx | 4 +- .../content/docs/actors/request-handler.mdx | 4 +- website/src/content/docs/actors/sandbox.mdx | 2 +- .../content/docs/actors/sqlite-drizzle.mdx | 2 +- website/src/content/docs/actors/sqlite.mdx | 4 +- .../content/docs/actors/troubleshooting.mdx | 4 +- website/src/content/docs/actors/versions.mdx | 74 ++- .../content/docs/actors/websocket-handler.mdx | 4 +- .../content/docs/agent-os/agent-to-agent.mdx | 2 +- .../src/content/docs/agent-os/agents/pi.mdx | 2 +- .../content/docs/agent-os/authentication.mdx | 4 +- .../content/docs/agent-os/configuration.mdx | 2 +- website/src/content/docs/agent-os/cron.mdx | 10 +- website/src/content/docs/agent-os/events.mdx | 4 +- .../src/content/docs/agent-os/filesystem.mdx | 8 +- website/src/content/docs/agent-os/index.mdx | 22 +- .../content/docs/agent-os/llm-credentials.mdx | 2 +- .../src/content/docs/agent-os/multiplayer.mdx | 8 +- .../src/content/docs/agent-os/networking.mdx | 8 +- .../src/content/docs/agent-os/permissions.mdx | 6 +- .../src/content/docs/agent-os/persistence.mdx | 4 +- .../src/content/docs/agent-os/processes.mdx | 12 +- website/src/content/docs/agent-os/queues.mdx | 6 +- .../src/content/docs/agent-os/quickstart.mdx | 2 +- .../src/content/docs/agent-os/security.mdx | 6 +- .../src/content/docs/agent-os/sessions.mdx | 18 +- website/src/content/docs/agent-os/sqlite.mdx | 2 +- website/src/content/docs/agent-os/tools.mdx | 2 +- .../src/content/docs/agent-os/webhooks.mdx | 2 +- .../src/content/docs/agent-os/workflows.mdx | 4 +- website/src/content/docs/clients/index.mdx | 2 +- .../src/content/docs/clients/javascript.mdx | 6 +- website/src/content/docs/clients/react.mdx | 6 +- website/src/content/docs/clients/swift.mdx | 2 +- website/src/content/docs/clients/swiftui.mdx | 2 +- .../src/content/docs/connect/cloudflare.mdx | 27 +- .../src/content/docs/connect/freestyle.mdx | 6 +- website/src/content/docs/connect/supabase.mdx | 19 +- website/src/content/docs/connect/vercel.mdx | 3 +- .../src/content/docs/general/endpoints.mdx | 22 +- .../docs/general/environment-variables.mdx | 65 +-- .../src/content/docs/general/http-server.mdx | 33 +- .../docs/general/production-checklist.mdx | 2 +- .../docs/general/registry-configuration.mdx | 20 +- .../content/docs/general/runtime-modes.mdx | 205 +++++--- .../posts/2026-05-06-rivet-2-3-0/page.mdx | 89 ++++ 162 files changed, 2056 insertions(+), 1191 deletions(-) delete mode 100644 rivetkit-typescript/packages/rivetkit/src/serverless/configure.ts create mode 100644 rivetkit-typescript/packages/rivetkit/tests/runtime-mode-config.test.ts create mode 100644 website/src/content/posts/2026-05-06-rivet-2-3-0/page.mdx diff --git a/examples/CLAUDE.md b/examples/CLAUDE.md index db67b29e2e..61df131471 100644 --- a/examples/CLAUDE.md +++ b/examples/CLAUDE.md @@ -350,7 +350,8 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; ``` @@ -364,7 +365,8 @@ const app = new Hono(); app.get("/api/foo", (c) => c.text("bar")); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; ``` diff --git a/examples/actor-actions-vercel/src/server.ts b/examples/actor-actions-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/actor-actions-vercel/src/server.ts +++ b/examples/actor-actions-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/ai-agent-vercel/src/server.ts b/examples/ai-agent-vercel/src/server.ts index 8c065a6b5e..d17bc8a1aa 100644 --- a/examples/ai-agent-vercel/src/server.ts +++ b/examples/ai-agent-vercel/src/server.ts @@ -2,7 +2,8 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); +const handler = registry.fetchHandler({ path: "/api/rivet" }); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/ai-and-user-generated-actors-freestyle/src/deploy-with-rivet-cloud.ts b/examples/ai-and-user-generated-actors-freestyle/src/deploy-with-rivet-cloud.ts index e399e8299d..fcc611c759 100644 --- a/examples/ai-and-user-generated-actors-freestyle/src/deploy-with-rivet-cloud.ts +++ b/examples/ai-and-user-generated-actors-freestyle/src/deploy-with-rivet-cloud.ts @@ -74,7 +74,7 @@ export async function deployWithRivetCloud( VITE_RIVET_DATACENTER: datacenter, RIVET_ENDPOINT: endpoint, RIVET_NAMESPACE: namespace.access.engineNamespaceName, - RIVET_RUNNER_TOKEN: runnerToken, + RIVET_TOKEN: runnerToken, RIVET_PUBLISHABLE_TOKEN: publishableToken, }, log, diff --git a/examples/ai-and-user-generated-actors-freestyle/src/deploy-with-rivet-self-hosted.ts b/examples/ai-and-user-generated-actors-freestyle/src/deploy-with-rivet-self-hosted.ts index 18cc1af1c5..a644dbe0d5 100644 --- a/examples/ai-and-user-generated-actors-freestyle/src/deploy-with-rivet-self-hosted.ts +++ b/examples/ai-and-user-generated-actors-freestyle/src/deploy-with-rivet-self-hosted.ts @@ -46,7 +46,7 @@ export async function deployWithRivetSelfHosted( VITE_RIVET_DATACENTER: datacenter, RIVET_ENDPOINT: endpoint, RIVET_NAMESPACE: namespace.name, - RIVET_RUNNER_TOKEN: token, + RIVET_TOKEN: token, RIVET_PUBLISHABLE_TOKEN: token, }, log, diff --git a/examples/ai-and-user-generated-actors-freestyle/src/utils.ts b/examples/ai-and-user-generated-actors-freestyle/src/utils.ts index 32e71fac53..308646d173 100644 --- a/examples/ai-and-user-generated-actors-freestyle/src/utils.ts +++ b/examples/ai-and-user-generated-actors-freestyle/src/utils.ts @@ -100,7 +100,6 @@ export async function deployToFreestyle(config: { envVars: { LOG_LEVEL: "debug", FREESTYLE_ENDPOINT: `https://${config.domain}`, - RIVET_RUNNER_KIND: "serverless", ...config.envVars, }, timeout: 60 * 5, diff --git a/examples/ai-generated-actor/src/server.ts b/examples/ai-generated-actor/src/server.ts index 01104b2d77..b245d98a9b 100644 --- a/examples/ai-generated-actor/src/server.ts +++ b/examples/ai-generated-actor/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors/index.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/chat-room-render/README.md b/examples/chat-room-render/README.md index e4d88e7ba3..f1f82c062f 100644 --- a/examples/chat-room-render/README.md +++ b/examples/chat-room-render/README.md @@ -44,7 +44,7 @@ The chat room demonstrates core Rivet Actor patterns for real-time communication 3. In the Rivet dashboard, point **Connect your backend** at your service's HTTPS URL. -> **`RIVET_ENVOY_VERSION`** is automatically derived from Render's `RENDER_GIT_COMMIT` — no manual bump needed per deploy. Set it explicitly to override. +> **`RIVET_VERSION`** is automatically derived from Render's `RENDER_GIT_COMMIT` — no manual bump needed per deploy. Set it explicitly to override. ## Resources diff --git a/examples/chat-room-render/render.yaml b/examples/chat-room-render/render.yaml index 970ce3e5ca..9de0004235 100644 --- a/examples/chat-room-render/render.yaml +++ b/examples/chat-room-render/render.yaml @@ -6,7 +6,7 @@ # # Monorepo: set Root Directory to `examples/chat-room-render`. # -# RIVET_ENVOY_VERSION is derived automatically from RENDER_GIT_COMMIT. +# RIVET_VERSION is derived automatically from RENDER_GIT_COMMIT. services: - type: web diff --git a/examples/chat-room-render/src/env.ts b/examples/chat-room-render/src/env.ts index 798cb18ea0..784fb1e0a5 100644 --- a/examples/chat-room-render/src/env.ts +++ b/examples/chat-room-render/src/env.ts @@ -1,19 +1,14 @@ /** - * Derive `RIVET_ENVOY_VERSION` from Render's `RENDER_GIT_COMMIT` so deploys + * Derive `RIVET_VERSION` from Render's `RENDER_GIT_COMMIT` so deploys * get a unique version automatically — no manual bump needed. */ function ensureRivetEnvoyVersion(): void { - if (process.env.RIVET_ENVOY_VERSION) return; - - if (process.env.RIVET_RUNNER_VERSION) { - process.env.RIVET_ENVOY_VERSION = process.env.RIVET_RUNNER_VERSION; - return; - } + if (process.env.RIVET_VERSION) return; const sha = process.env.RENDER_GIT_COMMIT; if (sha && /^[0-9a-f]{7,40}$/i.test(sha)) { const n = Number.parseInt(sha.slice(0, 8), 16); - process.env.RIVET_ENVOY_VERSION = String(n > 0 ? n : 1); + process.env.RIVET_VERSION = String(n > 0 ? n : 1); } } diff --git a/examples/chat-room-render/src/index.ts b/examples/chat-room-render/src/index.ts index 94fed331a9..3ee6831cfb 100644 --- a/examples/chat-room-render/src/index.ts +++ b/examples/chat-room-render/src/index.ts @@ -1,14 +1,9 @@ import "./env.ts"; -import { port, useRivetCloud } from "./env.ts"; -import { registry } from "./actors.ts"; +import { port } from "./env.ts"; -if (useRivetCloud) { - const { serve } = await import("@hono/node-server"); - const { default: app } = await import("./server.ts"); +const { serve } = await import("@hono/node-server"); +const { default: app } = await import("./server.ts"); - serve({ fetch: app.fetch, port }, () => { - console.log(`chat-room-render listening on http://0.0.0.0:${port}`); - }); -} else { - registry.start(); -} +serve({ fetch: app.fetch, port }, () => { + console.log(`chat-room-render listening on http://0.0.0.0:${port}`); +}); diff --git a/examples/chat-room-render/src/server.ts b/examples/chat-room-render/src/server.ts index 3441401abe..cdf53622a4 100644 --- a/examples/chat-room-render/src/server.ts +++ b/examples/chat-room-render/src/server.ts @@ -1,10 +1,15 @@ import { Hono } from "hono"; import { serveStatic } from "@hono/node-server/serve-static"; import { registry } from "./actors.ts"; +import { port } from "./env.ts"; const app = new Hono(); +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: `http://127.0.0.1:${port}/api/rivet`, +}); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); app.get("/health", (c) => c.json({ status: "ok" })); diff --git a/examples/chat-room-vercel/src/server.ts b/examples/chat-room-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/chat-room-vercel/src/server.ts +++ b/examples/chat-room-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/collaborative-document-vercel/src/server.ts b/examples/collaborative-document-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/collaborative-document-vercel/src/server.ts +++ b/examples/collaborative-document-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/cross-actor-actions-vercel/src/server.ts b/examples/cross-actor-actions-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/cross-actor-actions-vercel/src/server.ts +++ b/examples/cross-actor-actions-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/cursors-raw-websocket-vercel/src/server.ts b/examples/cursors-raw-websocket-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/cursors-raw-websocket-vercel/src/server.ts +++ b/examples/cursors-raw-websocket-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/cursors-vercel/src/server.ts b/examples/cursors-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/cursors-vercel/src/server.ts +++ b/examples/cursors-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/dynamic-actors/src/server.ts b/examples/dynamic-actors/src/server.ts index 4363bcd86d..54859381aa 100644 --- a/examples/dynamic-actors/src/server.ts +++ b/examples/dynamic-actors/src/server.ts @@ -45,7 +45,8 @@ app.post("/api/dynamic/:dynamicKey/increment", async (c) => { const count = await dynamicActorHandle(dynamicKey).increment(amount); return c.json({ count }); }); +const handler = registry.fetchHandler({ path: "/api/rivet" }); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/elysia/src/index.ts b/examples/elysia/src/index.ts index 45312eb0ee..3e3d61c376 100644 --- a/examples/elysia/src/index.ts +++ b/examples/elysia/src/index.ts @@ -13,5 +13,3 @@ export const counter = actor({ export const registry = setup({ use: { counter }, }); - -registry.start(); diff --git a/examples/elysia/src/server.ts b/examples/elysia/src/server.ts index 887a1749e5..8d185bc1bf 100644 --- a/examples/elysia/src/server.ts +++ b/examples/elysia/src/server.ts @@ -5,11 +5,15 @@ import { registry } from "./index.ts"; const client = createClient(); const app = new Elysia() - .all("/api/rivet/*", (c) => registry.handler(c.request)) .get("/increment/:name", async ({ params }) => { const counter = client.counter.getOrCreate(params.name); const newCount = await counter.increment(1); return `New Count: ${newCount}`; }); +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: "http://127.0.0.1:3000/api/rivet", +}); +app.all("/api/rivet/*", (c) => handler(c.request)); export default app; diff --git a/examples/experimental-durable-streams-ai-agent-vercel/src/server.ts b/examples/experimental-durable-streams-ai-agent-vercel/src/server.ts index 487fdb0dab..475e5617d2 100644 --- a/examples/experimental-durable-streams-ai-agent-vercel/src/server.ts +++ b/examples/experimental-durable-streams-ai-agent-vercel/src/server.ts @@ -6,5 +6,6 @@ if (!process.env.ANTHROPIC_API_KEY) { } const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/geo-distributed-database-vercel/src/server.ts b/examples/geo-distributed-database-vercel/src/server.ts index 8c065a6b5e..d17bc8a1aa 100644 --- a/examples/geo-distributed-database-vercel/src/server.ts +++ b/examples/geo-distributed-database-vercel/src/server.ts @@ -2,7 +2,8 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); +const handler = registry.fetchHandler({ path: "/api/rivet" }); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/hello-world-render/README.md b/examples/hello-world-render/README.md index e2dd923b49..11bc094140 100644 --- a/examples/hello-world-render/README.md +++ b/examples/hello-world-render/README.md @@ -34,7 +34,7 @@ This example demonstrates the core RivetKit concepts with a simple counter: 3. In the Rivet dashboard, point **Connect your backend** at your service's HTTPS URL. -> **`RIVET_ENVOY_VERSION`** is automatically derived from Render's `RENDER_GIT_COMMIT` — no manual bump needed per deploy. Set it explicitly to override. +> **`RIVET_VERSION`** is automatically derived from Render's `RENDER_GIT_COMMIT` — no manual bump needed per deploy. Set it explicitly to override. ## Resources diff --git a/examples/hello-world-render/render.yaml b/examples/hello-world-render/render.yaml index bfc842f676..ebea7b2104 100644 --- a/examples/hello-world-render/render.yaml +++ b/examples/hello-world-render/render.yaml @@ -10,7 +10,7 @@ # # Rivet Cloud: RIVET_ENDPOINT (sk) and RIVET_PUBLIC_ENDPOINT (pk) for the browser bundle. # Use Rivet’s regional api-*.rivet.dev host from the Rivet dashboard. -# RIVET_ENVOY_VERSION: optional override; if unset, the app derives a version from RENDER_GIT_COMMIT on Render. +# RIVET_VERSION: optional override; if unset, the app derives a version from RENDER_GIT_COMMIT on Render. services: - type: web diff --git a/examples/hello-world-render/src/config/env.ts b/examples/hello-world-render/src/config/env.ts index 3cd0d44e50..2e8440e2c8 100644 --- a/examples/hello-world-render/src/config/env.ts +++ b/examples/hello-world-render/src/config/env.ts @@ -1,29 +1,24 @@ import { RIVET_GLOBAL_API_HOSTNAME } from "./rivet-constants"; /** - * RivetKit uses `RIVET_ENVOY_VERSION` for runner versioning and deploy drains + * RivetKit uses `RIVET_VERSION` for runner versioning and deploy drains * ([docs](https://rivet.dev/docs/actors/versions)). On Render, `RENDER_GIT_COMMIT` is * injected at build and runtime ([Render default env](https://render.com/docs/environment-variables)), * so we derive a stable integer from the commit SHA—no manual bump per deploy. * - * Override with `RIVET_ENVOY_VERSION` (or legacy `RIVET_RUNNER_VERSION`) when needed. + * Override with `RIVET_VERSION` when needed. */ function ensureRivetEnvoyVersionFromEnvironment(): void { if ( - process.env.RIVET_ENVOY_VERSION !== undefined && - process.env.RIVET_ENVOY_VERSION !== "" + process.env.RIVET_VERSION !== undefined && + process.env.RIVET_VERSION !== "" ) { return; } - const legacy = process.env.RIVET_RUNNER_VERSION; - if (legacy !== undefined && legacy !== "") { - process.env.RIVET_ENVOY_VERSION = legacy; - return; - } const sha = process.env.RENDER_GIT_COMMIT; if (sha && /^[0-9a-f]{7,40}$/i.test(sha)) { const n = Number.parseInt(sha.slice(0, 8), 16); - process.env.RIVET_ENVOY_VERSION = String(n > 0 ? n : 1); + process.env.RIVET_VERSION = String(n > 0 ? n : 1); } } diff --git a/examples/hello-world-render/src/http/server.ts b/examples/hello-world-render/src/http/server.ts index 1f5a3832bc..5cf81f533d 100644 --- a/examples/hello-world-render/src/http/server.ts +++ b/examples/hello-world-render/src/http/server.ts @@ -5,11 +5,12 @@ import { pipeWebResponseToNode } from "./pipe-response"; import { createStaticAndProbeHandler } from "./serve-static"; export function startProductionServer(options: { - registry: { handler: (req: Request) => Promise }; + registry: { fetchHandler: (opts: { path: string }) => (req: Request) => Promise }; port: number; publicDir: string; }): void { const { registry, port, publicDir } = options; + const handler = registry.fetchHandler({ path: "/api/rivet" }); const handleStaticAndProbes = createStaticAndProbeHandler({ publicDir, getServiceName: serviceName, @@ -22,7 +23,7 @@ export function startProductionServer(options: { if (url.pathname === "/api/rivet" || url.pathname.startsWith("/api/rivet/")) { try { const webReq = incomingMessageToRequest(req, port); - const webRes = await registry.handler(webReq); + const webRes = await handler(webReq); await pipeWebResponseToNode(res, webRes); } catch (err) { console.error(err); diff --git a/examples/hello-world-render/src/http/web-request.ts b/examples/hello-world-render/src/http/web-request.ts index cc355fea19..2941aa8e80 100644 --- a/examples/hello-world-render/src/http/web-request.ts +++ b/examples/hello-world-render/src/http/web-request.ts @@ -1,6 +1,6 @@ import type { IncomingMessage } from "node:http"; -/** Build a WHATWG `Request` from an incoming Node request (for `registry.handler`). */ +/** Build a WHATWG `Request` from an incoming Node request. */ export function incomingMessageToRequest( req: IncomingMessage, port: number, diff --git a/examples/hello-world-vercel/src/server.ts b/examples/hello-world-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/hello-world-vercel/src/server.ts +++ b/examples/hello-world-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/hono-react-vercel/src/server.ts b/examples/hono-react-vercel/src/server.ts index 6f45f441e6..8c8cf93722 100644 --- a/examples/hono-react-vercel/src/server.ts +++ b/examples/hono-react-vercel/src/server.ts @@ -15,7 +15,8 @@ app.post("/increment/:name", async (c) => { return c.text(String(newCount)); }); +const handler = registry.fetchHandler({ path: "/api/rivet" }); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/hono-react/src/index.ts b/examples/hono-react/src/index.ts index 1b0ab854f2..5939fc3512 100644 --- a/examples/hono-react/src/index.ts +++ b/examples/hono-react/src/index.ts @@ -17,5 +17,3 @@ export const counter = actor({ export const registry = setup({ use: { counter }, }); - -registry.start(); diff --git a/examples/hono-react/src/server.ts b/examples/hono-react/src/server.ts index 4bcbda25d0..e38ab96370 100644 --- a/examples/hono-react/src/server.ts +++ b/examples/hono-react/src/server.ts @@ -15,7 +15,11 @@ app.post("/increment/:name", async (c) => { return c.text(String(newCount)); }); +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: "http://127.0.0.1:3000/api/rivet", +}); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/hono/src/index.ts b/examples/hono/src/index.ts index 45312eb0ee..3e3d61c376 100644 --- a/examples/hono/src/index.ts +++ b/examples/hono/src/index.ts @@ -13,5 +13,3 @@ export const counter = actor({ export const registry = setup({ use: { counter }, }); - -registry.start(); diff --git a/examples/hono/src/server.ts b/examples/hono/src/server.ts index eb6d6a7a8f..61294b712a 100644 --- a/examples/hono/src/server.ts +++ b/examples/hono/src/server.ts @@ -5,8 +5,12 @@ import { registry } from "./index.ts"; const client = createClient(); const app = new Hono(); +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: "http://127.0.0.1:3000/api/rivet", +}); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); app.post("/increment/:name", async (c) => { const name = c.req.param("name"); diff --git a/examples/kitchen-sink-vercel/src/index.ts b/examples/kitchen-sink-vercel/src/index.ts index a6ec2a22de..ac8661ca8b 100644 --- a/examples/kitchen-sink-vercel/src/index.ts +++ b/examples/kitchen-sink-vercel/src/index.ts @@ -120,46 +120,7 @@ import { testSqliteBench } from "./actors/testing/test-sqlite-bench.ts"; // AI import { aiAgent } from "./actors/ai/ai-agent.ts"; -function numberFromEnv(name: string, fallback: number): number { - const value = process.env[name]; - if (value === undefined || value === "") return fallback; - - const parsed = Number(value); - if (!Number.isFinite(parsed)) { - throw new Error(`${name} must be a finite number`); - } - - return parsed; -} - -function serverlessPoolConfig() { - const url = - process.env.RIVET_SERVERLESS_URL ?? - process.env.KITCHEN_SINK_SERVERLESS_URL ?? - (process.env.RIVET_RUN_ENGINE === "1" - ? "http://127.0.0.1:3000/api/rivet" - : undefined); - - if (!url) return undefined; - - return { - name: process.env.RIVET_POOL, - url, - requestLifespan: numberFromEnv("RIVET_SERVERLESS_REQUEST_LIFESPAN", 30), - drainGracePeriod: numberFromEnv("RIVET_SERVERLESS_DRAIN_GRACE_PERIOD", 5), - metadataPollInterval: numberFromEnv( - "RIVET_SERVERLESS_METADATA_POLL_INTERVAL_MS", - 1000, - ), - metadata: { - source: "kitchen-sink-vercel", - smoke: "raw-websocket-serverless", - }, - }; -} - export const registry = setup({ - configurePool: serverlessPoolConfig(), use: { // Overview + state basics counter, diff --git a/examples/kitchen-sink-vercel/src/server.ts b/examples/kitchen-sink-vercel/src/server.ts index ce03591313..23d95ebb64 100644 --- a/examples/kitchen-sink-vercel/src/server.ts +++ b/examples/kitchen-sink-vercel/src/server.ts @@ -1,3 +1,5 @@ import { registry } from "./index.ts"; -export default registry.serve(); +const fetch = registry.fetchHandler({ path: "/api/rivet" }); + +export default { fetch }; diff --git a/examples/kitchen-sink/package.json b/examples/kitchen-sink/package.json index acb2ac47df..41cb15570d 100644 --- a/examples/kitchen-sink/package.json +++ b/examples/kitchen-sink/package.json @@ -6,7 +6,7 @@ "packageManager": "pnpm@10.13.1", "scripts": { "dev": "VITE_RIVET_ENDPOINT=http://127.0.0.1:6420 concurrently -n server,vite \"node --import @rivetkit/sql-loader --import tsx src/server.ts\" \"vite\"", - "dev:serverless": "RIVET_RUN_ENGINE=1 concurrently -n server,vite,configure \"node --import @rivetkit/sql-loader --import tsx src/server.ts\" \"vite\" \"pnpm dev:serverless:configure\"", + "dev:serverless": "NODE_ENV=development KITCHEN_SINK_SERVERLESS_URL=http://127.0.0.1:3000/api/rivet concurrently -n server,vite,configure \"node --import @rivetkit/sql-loader --import tsx src/server.ts\" \"vite\" \"pnpm dev:serverless:configure\"", "dev:serverless:configure": "node -e \"void (async () => { const port = process.env.PORT ?? '3000'; const url = 'http://127.0.0.1:' + port + '/api/rivet/metadata'; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); for (let i = 0; i < 120; i++) { try { const res = await fetch(url); if (res.ok) { console.log('serverless pool configured'); return; } console.log('serverless configuration returned ' + res.status); } catch {} await sleep(1000); } throw new Error('timed out waiting for serverless configuration at ' + url); })();\"", "check-types": "echo 'skipped - workflow history types broken'", "build": "vite build", diff --git a/examples/kitchen-sink/scripts/mock-agentic-loop.ts b/examples/kitchen-sink/scripts/mock-agentic-loop.ts index 789a8f9baa..2ef265e30f 100644 --- a/examples/kitchen-sink/scripts/mock-agentic-loop.ts +++ b/examples/kitchen-sink/scripts/mock-agentic-loop.ts @@ -531,7 +531,7 @@ async function startLocalKitchenSinkServer() { detached: true, env: { ...process.env, - RIVET_RUN_ENGINE: "1", + NODE_ENV: "development", RIVET_ENGINE_BINARY: resolveEngineBinary(), RIVETKIT_RUNTIME: process.env.RIVETKIT_RUNTIME ?? "native", RIVETKIT_STORAGE_PATH: diff --git a/examples/kitchen-sink/scripts/sqlite-memory-soak.ts b/examples/kitchen-sink/scripts/sqlite-memory-soak.ts index a450f09bf6..8606d45789 100644 --- a/examples/kitchen-sink/scripts/sqlite-memory-soak.ts +++ b/examples/kitchen-sink/scripts/sqlite-memory-soak.ts @@ -686,8 +686,6 @@ async function startKitchenSinkServer( MALLOC_ARENA_MAX: process.env.MALLOC_ARENA_MAX ?? "2", MALLOC_TRIM_THRESHOLD_: process.env.MALLOC_TRIM_THRESHOLD_ ?? "131072", }; - delete env.RIVET_RUN_ENGINE; - const nodeArgs = [ ...(args.forceGcSamples ? ["--expose-gc"] : []), "--import", diff --git a/examples/kitchen-sink/src/cloudflare.ts b/examples/kitchen-sink/src/cloudflare.ts index d8c8afbf38..8a4e92bb6a 100644 --- a/examples/kitchen-sink/src/cloudflare.ts +++ b/examples/kitchen-sink/src/cloudflare.ts @@ -22,7 +22,6 @@ const registry = setup({ sqliteBackend: "remote", }, noWelcome: true, - startEngine: false, use: { counter, rawHttpActor, @@ -30,6 +29,7 @@ const registry = setup({ testCounterSqlite, }, }); +const handler = registry.fetchHandler({ path: "/api/rivet" }); function matchesRivetPath(pathname: string) { return pathname === "/api/rivet" || pathname.startsWith("/api/rivet/"); @@ -42,7 +42,7 @@ export default { return Response.json({ ok: true }); } if (matchesRivetPath(url.pathname)) { - return registry.handler(request); + return handler(request); } return new Response("not found", { status: 404 }); }, diff --git a/examples/kitchen-sink/src/index.ts b/examples/kitchen-sink/src/index.ts index a4d5f2fb04..12b7fe4dd9 100644 --- a/examples/kitchen-sink/src/index.ts +++ b/examples/kitchen-sink/src/index.ts @@ -126,60 +126,7 @@ import { sleepCloseFuzz } from "./actors/testing/sleep-close-fuzz.ts"; // AI import { aiAgent } from "./actors/ai/ai-agent.ts"; -function numberFromEnv(name: string, fallback: number): number { - const value = process.env[name]; - if (value === undefined || value === "") return fallback; - - const parsed = Number(value); - if (!Number.isFinite(parsed)) { - throw new Error(`${name} must be a finite number`); - } - - return parsed; -} - -function serverlessPoolConfig() { - const url = - process.env.RIVET_SERVERLESS_URL ?? - process.env.KITCHEN_SINK_SERVERLESS_URL ?? - (process.env.RIVET_RUN_ENGINE === "1" - ? "http://127.0.0.1:3000/api/rivet" - : undefined); - - if (!url) return undefined; - - return { - name: process.env.RIVET_POOL, - url, - requestLifespan: numberFromEnv( - "RIVET_SERVERLESS_REQUEST_LIFESPAN", - 15 * 60, - ), - drainGracePeriod: numberFromEnv( - "RIVET_SERVERLESS_DRAIN_GRACE_PERIOD", - 15 * 60, - ), - metadataPollInterval: numberFromEnv( - "RIVET_SERVERLESS_METADATA_POLL_INTERVAL_MS", - 1000, - ), - metadata: { - source: "kitchen-sink", - smoke: "raw-websocket-serverless", - }, - }; -} - export const registry = setup({ - configurePool: serverlessPoolConfig(), - serverless: { - publicToken: - process.env.RIVET_PUBLIC_TOKEN ?? process.env.RIVET_TOKEN ?? "dev", - maxStartPayloadBytes: numberFromEnv( - "RIVET_SERVERLESS_MAX_START_PAYLOAD_BYTES", - 16 * 1024 * 1024, - ), - }, use: { // Overview + state basics counter, diff --git a/examples/kitchen-sink/src/server.ts b/examples/kitchen-sink/src/server.ts index 521c4ea6da..57b2c9fd2b 100644 --- a/examples/kitchen-sink/src/server.ts +++ b/examples/kitchen-sink/src/server.ts @@ -6,10 +6,9 @@ import * as v8 from "node:v8"; const app = new Hono(); const port = Number.parseInt(process.env.PORT ?? "3000", 10); -const serverlessMode = - process.env.RIVET_RUN_ENGINE === "1" || - process.env.RIVET_SERVERLESS_URL !== undefined || - process.env.KITCHEN_SINK_SERVERLESS_URL !== undefined; +const serverlessUrl = + process.env.RIVET_SERVERLESS_URL ?? + process.env.KITCHEN_SINK_SERVERLESS_URL; process.on("exit", (code) => { console.log(JSON.stringify({ kind: "process_exit", code, pid: process.pid })); @@ -155,23 +154,17 @@ app.use("*", async (c, next) => { // ); }); -if (serverlessMode) { - app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); - app.all("/api/rivet", (c) => registry.handler(c.req.raw)); -} else { - registry.start(); -} +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: serverlessUrl ?? `http://127.0.0.1:${port}/api/rivet`, +}); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); +app.all("/api/rivet", (c) => handler(c.req.raw)); const server = serve({ fetch: app.fetch, port }, () => { - if (serverlessMode) { - console.log( - `serverless RivetKit listening on http://127.0.0.1:${port}/api/rivet`, - ); - } else { - console.log( - `kitchen sink diagnostics listening on http://127.0.0.1:${port}`, - ); - } + console.log( + `kitchen sink listening on http://127.0.0.1:${port} with RivetKit at /api/rivet`, + ); }); const httpServer = server as unknown as HttpServer; httpServer.requestTimeout = 0; diff --git a/examples/kitchen-sink/supabase/functions/rivetkit/index.ts b/examples/kitchen-sink/supabase/functions/rivetkit/index.ts index a3ffefd499..ceb554892c 100644 --- a/examples/kitchen-sink/supabase/functions/rivetkit/index.ts +++ b/examples/kitchen-sink/supabase/functions/rivetkit/index.ts @@ -34,7 +34,6 @@ const registry = setup({ sqliteBackend: "remote", }, noWelcome: true, - startEngine: false, use: { counter, rawHttpActor, @@ -42,6 +41,7 @@ const registry = setup({ testCounterSqlite, }, }); +const rivetHandler = registry.fetchHandler({ path: "/api/rivet" }); function matchesRivetPath(pathname: string) { return pathname === "/api/rivet" || pathname.includes("/api/rivet/"); @@ -64,7 +64,7 @@ async function handler(request: Request) { return Response.json({ ok: true }); } if (matchesRivetPath(url.pathname)) { - return registry.handler(normalizeRivetRequest(request)); + return rivetHandler(normalizeRivetRequest(request)); } return new Response("not found", { status: 404 }); } diff --git a/examples/multi-region-vercel/src/server.ts b/examples/multi-region-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/multi-region-vercel/src/server.ts +++ b/examples/multi-region-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/multiplayer-game-patterns-vercel/src/server.ts b/examples/multiplayer-game-patterns-vercel/src/server.ts index cb57c85c76..b165bedcd7 100644 --- a/examples/multiplayer-game-patterns-vercel/src/server.ts +++ b/examples/multiplayer-game-patterns-vercel/src/server.ts @@ -2,6 +2,7 @@ import { Hono } from "hono"; import { registry } from "./actors/index.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/multiplayer-game-vercel/src/server.ts b/examples/multiplayer-game-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/multiplayer-game-vercel/src/server.ts +++ b/examples/multiplayer-game-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/native-websockets-vercel/src/server.ts b/examples/native-websockets-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/native-websockets-vercel/src/server.ts +++ b/examples/native-websockets-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/per-tenant-database-vercel/src/server.ts b/examples/per-tenant-database-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/per-tenant-database-vercel/src/server.ts +++ b/examples/per-tenant-database-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/raw-fetch-handler-vercel/src/server.ts b/examples/raw-fetch-handler-vercel/src/server.ts index 2d057a7b80..a9ebc4863c 100644 --- a/examples/raw-fetch-handler-vercel/src/server.ts +++ b/examples/raw-fetch-handler-vercel/src/server.ts @@ -33,8 +33,9 @@ app.all("/forward/:name/*", async (c) => { return response; }); +const handler = registry.fetchHandler({ path: "/api/rivet" }); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/raw-fetch-handler/src/index.ts b/examples/raw-fetch-handler/src/index.ts index 44e260a89f..b27d945857 100644 --- a/examples/raw-fetch-handler/src/index.ts +++ b/examples/raw-fetch-handler/src/index.ts @@ -45,5 +45,3 @@ function createCounterRouter(): Hono { export const registry = setup({ use: { counter }, }); - -registry.start(); diff --git a/examples/raw-fetch-handler/src/server.ts b/examples/raw-fetch-handler/src/server.ts index 8dd190b955..bfc642bcd1 100644 --- a/examples/raw-fetch-handler/src/server.ts +++ b/examples/raw-fetch-handler/src/server.ts @@ -33,8 +33,12 @@ app.all("/forward/:name/*", async (c) => { return response; }); +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: "http://127.0.0.1:3000/api/rivet", +}); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/raw-websocket-handler-proxy-vercel/src/server.ts b/examples/raw-websocket-handler-proxy-vercel/src/server.ts index ab4d667beb..57d0514fee 100644 --- a/examples/raw-websocket-handler-proxy-vercel/src/server.ts +++ b/examples/raw-websocket-handler-proxy-vercel/src/server.ts @@ -46,7 +46,8 @@ app.get( }; }), ); +const handler = registry.fetchHandler({ path: "/api/rivet" }); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/raw-websocket-handler-proxy/src/index.ts b/examples/raw-websocket-handler-proxy/src/index.ts index ce1c81c65d..e6741c3954 100644 --- a/examples/raw-websocket-handler-proxy/src/index.ts +++ b/examples/raw-websocket-handler-proxy/src/index.ts @@ -75,5 +75,3 @@ export const chatRoom = actor({ export const registry = setup({ use: { chatRoom }, }); - -registry.start(); diff --git a/examples/raw-websocket-handler-proxy/src/server.ts b/examples/raw-websocket-handler-proxy/src/server.ts index 72fc0ffb13..65c969ec9d 100644 --- a/examples/raw-websocket-handler-proxy/src/server.ts +++ b/examples/raw-websocket-handler-proxy/src/server.ts @@ -46,7 +46,11 @@ app.get( }; }), ); +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: "http://127.0.0.1:3000/api/rivet", +}); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/raw-websocket-handler-vercel/src/server.ts b/examples/raw-websocket-handler-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/raw-websocket-handler-vercel/src/server.ts +++ b/examples/raw-websocket-handler-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/react-render/README.md b/examples/react-render/README.md index ebc67580b3..d46eb21aa2 100644 --- a/examples/react-render/README.md +++ b/examples/react-render/README.md @@ -44,7 +44,7 @@ This example demonstrates React frontend integration with Rivet Actors: 3. In the Rivet dashboard, point **Connect your backend** at your service's HTTPS URL. -> **`RIVET_ENVOY_VERSION`** is automatically derived from Render's `RENDER_GIT_COMMIT` — no manual bump needed per deploy. Set it explicitly to override. +> **`RIVET_VERSION`** is automatically derived from Render's `RENDER_GIT_COMMIT` — no manual bump needed per deploy. Set it explicitly to override. ## Resources diff --git a/examples/react-render/render.yaml b/examples/react-render/render.yaml index d126b96a23..c4ada29692 100644 --- a/examples/react-render/render.yaml +++ b/examples/react-render/render.yaml @@ -6,7 +6,7 @@ # # Monorepo: set Root Directory to `examples/react-render`. # -# RIVET_ENVOY_VERSION is derived automatically from RENDER_GIT_COMMIT. +# RIVET_VERSION is derived automatically from RENDER_GIT_COMMIT. services: - type: web diff --git a/examples/react-render/src/env.ts b/examples/react-render/src/env.ts index 1f65a9b17c..0d9eaf6818 100644 --- a/examples/react-render/src/env.ts +++ b/examples/react-render/src/env.ts @@ -1,15 +1,10 @@ function ensureRivetEnvoyVersion(): void { - if (process.env.RIVET_ENVOY_VERSION) return; - - if (process.env.RIVET_RUNNER_VERSION) { - process.env.RIVET_ENVOY_VERSION = process.env.RIVET_RUNNER_VERSION; - return; - } + if (process.env.RIVET_VERSION) return; const sha = process.env.RENDER_GIT_COMMIT; if (sha && /^[0-9a-f]{7,40}$/i.test(sha)) { const n = Number.parseInt(sha.slice(0, 8), 16); - process.env.RIVET_ENVOY_VERSION = String(n > 0 ? n : 1); + process.env.RIVET_VERSION = String(n > 0 ? n : 1); } } diff --git a/examples/react-render/src/index.ts b/examples/react-render/src/index.ts index 76b32096d8..8243c36158 100644 --- a/examples/react-render/src/index.ts +++ b/examples/react-render/src/index.ts @@ -1,14 +1,9 @@ import "./env.ts"; -import { port, useRivetCloud } from "./env.ts"; -import { registry } from "./actors.ts"; +import { port } from "./env.ts"; -if (useRivetCloud) { - const { serve } = await import("@hono/node-server"); - const { default: app } = await import("./server.ts"); +const { serve } = await import("@hono/node-server"); +const { default: app } = await import("./server.ts"); - serve({ fetch: app.fetch, port }, () => { - console.log(`react-render listening on http://0.0.0.0:${port}`); - }); -} else { - registry.start(); -} +serve({ fetch: app.fetch, port }, () => { + console.log(`react-render listening on http://0.0.0.0:${port}`); +}); diff --git a/examples/react-render/src/server.ts b/examples/react-render/src/server.ts index 3441401abe..cdf53622a4 100644 --- a/examples/react-render/src/server.ts +++ b/examples/react-render/src/server.ts @@ -1,10 +1,15 @@ import { Hono } from "hono"; import { serveStatic } from "@hono/node-server/serve-static"; import { registry } from "./actors.ts"; +import { port } from "./env.ts"; const app = new Hono(); +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: `http://127.0.0.1:${port}/api/rivet`, +}); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); app.get("/health", (c) => c.json({ status: "ok" })); diff --git a/examples/react-vercel/src/server.ts b/examples/react-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/react-vercel/src/server.ts +++ b/examples/react-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/sandbox-coding-agent-vercel/src/server.ts b/examples/sandbox-coding-agent-vercel/src/server.ts index 8c065a6b5e..d17bc8a1aa 100644 --- a/examples/sandbox-coding-agent-vercel/src/server.ts +++ b/examples/sandbox-coding-agent-vercel/src/server.ts @@ -2,7 +2,8 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); +const handler = registry.fetchHandler({ path: "/api/rivet" }); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/scheduling-vercel/src/server.ts b/examples/scheduling-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/scheduling-vercel/src/server.ts +++ b/examples/scheduling-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/state-render/README.md b/examples/state-render/README.md index 0fe18ac925..aeecd5e4af 100644 --- a/examples/state-render/README.md +++ b/examples/state-render/README.md @@ -44,7 +44,7 @@ This example demonstrates state management in Rivet Actors: 3. In the Rivet dashboard, point **Connect your backend** at your service's HTTPS URL. -> **`RIVET_ENVOY_VERSION`** is automatically derived from Render's `RENDER_GIT_COMMIT` — no manual bump needed per deploy. Set it explicitly to override. +> **`RIVET_VERSION`** is automatically derived from Render's `RENDER_GIT_COMMIT` — no manual bump needed per deploy. Set it explicitly to override. ## Resources diff --git a/examples/state-render/render.yaml b/examples/state-render/render.yaml index 66c80ec740..b3da8af4ab 100644 --- a/examples/state-render/render.yaml +++ b/examples/state-render/render.yaml @@ -6,7 +6,7 @@ # # Monorepo: set Root Directory to `examples/state-render`. # -# RIVET_ENVOY_VERSION is derived automatically from RENDER_GIT_COMMIT. +# RIVET_VERSION is derived automatically from RENDER_GIT_COMMIT. services: - type: web diff --git a/examples/state-render/src/env.ts b/examples/state-render/src/env.ts index 798cb18ea0..784fb1e0a5 100644 --- a/examples/state-render/src/env.ts +++ b/examples/state-render/src/env.ts @@ -1,19 +1,14 @@ /** - * Derive `RIVET_ENVOY_VERSION` from Render's `RENDER_GIT_COMMIT` so deploys + * Derive `RIVET_VERSION` from Render's `RENDER_GIT_COMMIT` so deploys * get a unique version automatically — no manual bump needed. */ function ensureRivetEnvoyVersion(): void { - if (process.env.RIVET_ENVOY_VERSION) return; - - if (process.env.RIVET_RUNNER_VERSION) { - process.env.RIVET_ENVOY_VERSION = process.env.RIVET_RUNNER_VERSION; - return; - } + if (process.env.RIVET_VERSION) return; const sha = process.env.RENDER_GIT_COMMIT; if (sha && /^[0-9a-f]{7,40}$/i.test(sha)) { const n = Number.parseInt(sha.slice(0, 8), 16); - process.env.RIVET_ENVOY_VERSION = String(n > 0 ? n : 1); + process.env.RIVET_VERSION = String(n > 0 ? n : 1); } } diff --git a/examples/state-render/src/index.ts b/examples/state-render/src/index.ts index b25bd13774..f6d9f2c009 100644 --- a/examples/state-render/src/index.ts +++ b/examples/state-render/src/index.ts @@ -1,14 +1,9 @@ import "./env.ts"; -import { port, useRivetCloud } from "./env.ts"; -import { registry } from "./actors.ts"; +import { port } from "./env.ts"; -if (useRivetCloud) { - const { serve } = await import("@hono/node-server"); - const { default: app } = await import("./server.ts"); +const { serve } = await import("@hono/node-server"); +const { default: app } = await import("./server.ts"); - serve({ fetch: app.fetch, port }, () => { - console.log(`state-render listening on http://0.0.0.0:${port}`); - }); -} else { - registry.start(); -} +serve({ fetch: app.fetch, port }, () => { + console.log(`state-render listening on http://0.0.0.0:${port}`); +}); diff --git a/examples/state-render/src/server.ts b/examples/state-render/src/server.ts index 3441401abe..cdf53622a4 100644 --- a/examples/state-render/src/server.ts +++ b/examples/state-render/src/server.ts @@ -1,10 +1,15 @@ import { Hono } from "hono"; import { serveStatic } from "@hono/node-server/serve-static"; import { registry } from "./actors.ts"; +import { port } from "./env.ts"; const app = new Hono(); +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: `http://127.0.0.1:${port}/api/rivet`, +}); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); app.get("/health", (c) => c.json({ status: "ok" })); diff --git a/examples/state-vercel/src/server.ts b/examples/state-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/state-vercel/src/server.ts +++ b/examples/state-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/stream-render/README.md b/examples/stream-render/README.md index 6ca24ec653..6d7ce0af9d 100644 --- a/examples/stream-render/README.md +++ b/examples/stream-render/README.md @@ -46,7 +46,7 @@ This stream processor uses a Top-K algorithm to efficiently maintain the top 3 v 3. In the Rivet dashboard, point **Connect your backend** at your service's HTTPS URL. -> **`RIVET_ENVOY_VERSION`** is automatically derived from Render's `RENDER_GIT_COMMIT` — no manual bump needed per deploy. Set it explicitly to override. +> **`RIVET_VERSION`** is automatically derived from Render's `RENDER_GIT_COMMIT` — no manual bump needed per deploy. Set it explicitly to override. ## Resources diff --git a/examples/stream-render/render.yaml b/examples/stream-render/render.yaml index 2ff3dbeea1..1b38e42ab8 100644 --- a/examples/stream-render/render.yaml +++ b/examples/stream-render/render.yaml @@ -6,7 +6,7 @@ # # Monorepo: set Root Directory to `examples/stream-render`. # -# RIVET_ENVOY_VERSION is derived automatically from RENDER_GIT_COMMIT. +# RIVET_VERSION is derived automatically from RENDER_GIT_COMMIT. services: - type: web diff --git a/examples/stream-render/src/env.ts b/examples/stream-render/src/env.ts index 1f65a9b17c..0d9eaf6818 100644 --- a/examples/stream-render/src/env.ts +++ b/examples/stream-render/src/env.ts @@ -1,15 +1,10 @@ function ensureRivetEnvoyVersion(): void { - if (process.env.RIVET_ENVOY_VERSION) return; - - if (process.env.RIVET_RUNNER_VERSION) { - process.env.RIVET_ENVOY_VERSION = process.env.RIVET_RUNNER_VERSION; - return; - } + if (process.env.RIVET_VERSION) return; const sha = process.env.RENDER_GIT_COMMIT; if (sha && /^[0-9a-f]{7,40}$/i.test(sha)) { const n = Number.parseInt(sha.slice(0, 8), 16); - process.env.RIVET_ENVOY_VERSION = String(n > 0 ? n : 1); + process.env.RIVET_VERSION = String(n > 0 ? n : 1); } } diff --git a/examples/stream-render/src/index.ts b/examples/stream-render/src/index.ts index d9bc80f3a6..16a64629b4 100644 --- a/examples/stream-render/src/index.ts +++ b/examples/stream-render/src/index.ts @@ -1,14 +1,9 @@ import "./env.ts"; -import { port, useRivetCloud } from "./env.ts"; -import { registry } from "./actors.ts"; +import { port } from "./env.ts"; -if (useRivetCloud) { - const { serve } = await import("@hono/node-server"); - const { default: app } = await import("./server.ts"); +const { serve } = await import("@hono/node-server"); +const { default: app } = await import("./server.ts"); - serve({ fetch: app.fetch, port }, () => { - console.log(`stream-render listening on http://0.0.0.0:${port}`); - }); -} else { - registry.start(); -} +serve({ fetch: app.fetch, port }, () => { + console.log(`stream-render listening on http://0.0.0.0:${port}`); +}); diff --git a/examples/stream-render/src/server.ts b/examples/stream-render/src/server.ts index 3441401abe..cdf53622a4 100644 --- a/examples/stream-render/src/server.ts +++ b/examples/stream-render/src/server.ts @@ -1,10 +1,15 @@ import { Hono } from "hono"; import { serveStatic } from "@hono/node-server/serve-static"; import { registry } from "./actors.ts"; +import { port } from "./env.ts"; const app = new Hono(); +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: `http://127.0.0.1:${port}/api/rivet`, +}); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); app.get("/health", (c) => c.json({ status: "ok" })); diff --git a/examples/stream-vercel/src/server.ts b/examples/stream-vercel/src/server.ts index 95c8895f94..95d73278e7 100644 --- a/examples/stream-vercel/src/server.ts +++ b/examples/stream-vercel/src/server.ts @@ -2,5 +2,6 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/examples/trpc/src/index.ts b/examples/trpc/src/index.ts index 45312eb0ee..3e3d61c376 100644 --- a/examples/trpc/src/index.ts +++ b/examples/trpc/src/index.ts @@ -13,5 +13,3 @@ export const counter = actor({ export const registry = setup({ use: { counter }, }); - -registry.start(); diff --git a/examples/trpc/src/server.ts b/examples/trpc/src/server.ts index 8fcab8f4dc..40680794bb 100644 --- a/examples/trpc/src/server.ts +++ b/examples/trpc/src/server.ts @@ -28,7 +28,11 @@ export type AppRouter = typeof appRouter; const app = new Hono(); app.use("/trpc/*", trpcServer({ router: appRouter })); +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: "http://127.0.0.1:3000/api/rivet", +}); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; diff --git a/rivetkit-rust/packages/rivetkit-core/src/registry/envoy_callbacks.rs b/rivetkit-rust/packages/rivetkit-core/src/registry/envoy_callbacks.rs index 64f823f940..05e8b35239 100644 --- a/rivetkit-rust/packages/rivetkit-core/src/registry/envoy_callbacks.rs +++ b/rivetkit-rust/packages/rivetkit-core/src/registry/envoy_callbacks.rs @@ -150,7 +150,7 @@ impl EnvoyCallbacks for RegistryCallbacks { impl ServeSettings { fn from_env() -> Self { Self { - version: env::var("RIVET_ENVOY_VERSION") + version: env::var("RIVET_VERSION") .ok() .and_then(|value| value.parse().ok()) .unwrap_or(1), @@ -158,8 +158,12 @@ impl ServeSettings { .unwrap_or_else(|_| "http://127.0.0.1:6420".to_owned()), token: Some(env::var("RIVET_TOKEN").unwrap_or_else(|_| "dev".to_owned())), namespace: env::var("RIVET_NAMESPACE").unwrap_or_else(|_| "default".to_owned()), - pool_name: env::var("RIVET_POOL_NAME").unwrap_or_else(|_| "rivetkit-rust".to_owned()), + pool_name: env::var("RIVET_POOL").unwrap_or_else(|_| "rivetkit-rust".to_owned()), engine_binary_path: env::var_os("RIVET_ENGINE_BINARY_PATH").map(PathBuf::from), + dev_serverless_url: None, + dev_serverless_manual: false, + dev_serverless_drain_timeout: None, + dev_serverless_request_timeout: None, handle_inspector_http_in_runtime: false, serverless_base_path: None, serverless_package_version: env!("CARGO_PKG_VERSION").to_owned(), @@ -188,6 +192,10 @@ impl ServeConfig { namespace: settings.namespace, pool_name: settings.pool_name, engine_binary_path: settings.engine_binary_path, + dev_serverless_url: settings.dev_serverless_url, + dev_serverless_manual: settings.dev_serverless_manual, + dev_serverless_drain_timeout: settings.dev_serverless_drain_timeout, + dev_serverless_request_timeout: settings.dev_serverless_request_timeout, handle_inspector_http_in_runtime: settings.handle_inspector_http_in_runtime, serverless_base_path: settings.serverless_base_path, serverless_package_version: settings.serverless_package_version, diff --git a/rivetkit-rust/packages/rivetkit-core/src/registry/mod.rs b/rivetkit-rust/packages/rivetkit-core/src/registry/mod.rs index 09fa72bb39..3d228aeda3 100644 --- a/rivetkit-rust/packages/rivetkit-core/src/registry/mod.rs +++ b/rivetkit-rust/packages/rivetkit-core/src/registry/mod.rs @@ -165,6 +165,10 @@ struct ServeSettings { namespace: String, pool_name: String, engine_binary_path: Option, + dev_serverless_url: Option, + dev_serverless_manual: bool, + dev_serverless_drain_timeout: Option, + dev_serverless_request_timeout: Option, handle_inspector_http_in_runtime: bool, serverless_base_path: Option, serverless_package_version: String, @@ -183,6 +187,10 @@ pub struct ServeConfig { pub namespace: String, pub pool_name: String, pub engine_binary_path: Option, + pub dev_serverless_url: Option, + pub dev_serverless_manual: bool, + pub dev_serverless_drain_timeout: Option, + pub dev_serverless_request_timeout: Option, pub handle_inspector_http_in_runtime: bool, pub serverless_base_path: Option, pub serverless_package_version: String, @@ -509,6 +517,9 @@ impl CoreRegistry { self, config: ServeConfig, ) -> Result { + #[cfg(feature = "native-runtime")] + runner_config::ensure_local_serverless_runner_config(&config).await?; + crate::serverless::CoreServerlessRuntime::new(self.factories, config).await } } diff --git a/rivetkit-rust/packages/rivetkit-core/src/registry/runner_config.rs b/rivetkit-rust/packages/rivetkit-core/src/registry/runner_config.rs index 88b1480b55..8ed37e714c 100644 --- a/rivetkit-rust/packages/rivetkit-core/src/registry/runner_config.rs +++ b/rivetkit-rust/packages/rivetkit-core/src/registry/runner_config.rs @@ -1,9 +1,11 @@ use std::net::IpAddr; use anyhow::{Context, Result}; +use crate::time::sleep; use reqwest::{Client, Url}; use serde::Deserialize; use serde_json::{Map as JsonMap, json}; +use std::time::{Duration, Instant}; use super::ServeConfig; @@ -51,7 +53,12 @@ pub(super) async fn ensure_local_normal_runner_config(config: &ServeConfig) -> R .json(&body) .send() .await - .context("upsert local runner config")?; + .with_context(|| { + format!( + "cannot reach Rivet Engine at {} while updating runner config", + config.endpoint + ) + })?; let status = response.status(); if !status.is_success() { let response_body = response @@ -75,12 +82,145 @@ pub(super) async fn ensure_local_normal_runner_config(config: &ServeConfig) -> R Ok(()) } +pub(super) async fn ensure_local_serverless_runner_config(config: &ServeConfig) -> Result<()> { + let Some(url) = config.dev_serverless_url.as_deref() else { + return Ok(()); + }; + if !is_local_engine_endpoint(&config.endpoint) { + return Ok(()); + } + + let client = Client::builder() + .build() + .context("build reqwest client for serverless runner config")?; + let timeout_ms = std::env::var("RIVET_SERVERLESS_CONFIGURE_TIMEOUT_MS") + .ok() + .and_then(|value| value.parse::().ok()) + .unwrap_or(60_000); + let started_at = Instant::now(); + let mut attempts = 0_u32; + + loop { + attempts += 1; + match try_ensure_local_serverless_runner_config(&client, config, url).await { + Ok(()) => { + tracing::info!( + namespace = %config.namespace, + pool_name = %config.pool_name, + attempts, + "ensured local serverless runner config" + ); + return Ok(()); + } + Err(error) => { + tracing::warn!( + namespace = %config.namespace, + pool_name = %config.pool_name, + attempts, + error = %error, + "serverless runner config attempt failed" + ); + if started_at.elapsed() >= Duration::from_millis(timeout_ms) { + return Err(error).context("failed to configure local serverless runner config"); + } + } + } + + sleep(Duration::from_secs(1)).await; + } +} + +async fn try_ensure_local_serverless_runner_config( + client: &Client, + config: &ServeConfig, + serverless_url: &str, +) -> Result<()> { + let datacenters = get_datacenters(client, config).await?; + let mut runner_datacenters = JsonMap::new(); + let serverless_token = config + .token + .as_ref() + .or(config.serverless_client_token.as_ref()); + let headers = match serverless_token { + Some(token) => json!({ "x-rivet-token": token }), + None => json!({}), + }; + let request_lifespan = config + .dev_serverless_request_timeout + .map(|timeout| (timeout.saturating_add(999) / 1000).max(1)) + .unwrap_or(60 * 60); + let drain_grace_period = config + .dev_serverless_drain_timeout + .map(|timeout| (timeout.saturating_add(999) / 1000).max(1)); + + for datacenter in datacenters.datacenters { + runner_datacenters.insert( + datacenter.name, + json!({ + "serverless": { + "url": serverless_url, + "headers": headers, + "request_lifespan": request_lifespan, + "drain_grace_period": drain_grace_period, + "metadata_poll_interval": 1000, + "max_runners": 100000, + "min_runners": 0, + "runners_margin": 0, + "slots_per_runner": 1, + }, + "metadata": {}, + "drain_on_version_upgrade": true, + }), + ); + } + + let url = engine_api_url( + &config.endpoint, + &["runner-configs", config.pool_name.as_str()], + &config.namespace, + )?; + let body = json!({ + "datacenters": runner_datacenters, + }); + + let response = apply_auth(client.put(url), config) + .json(&body) + .send() + .await + .with_context(|| { + format!( + "cannot reach Rivet Engine at {} while updating serverless runner config", + config.endpoint + ) + })?; + let status = response.status(); + if !status.is_success() { + let response_body = response + .text() + .await + .context("read failed serverless runner config response body")?; + anyhow::bail!( + "failed to upsert local serverless runner config `{}`: {} {}", + config.pool_name, + status, + response_body + ); + } + + Ok(()) +} + async fn get_datacenters(client: &Client, config: &ServeConfig) -> Result { let url = engine_api_url(&config.endpoint, &["datacenters"], &config.namespace)?; let response = apply_auth(client.get(url), config) .send() .await - .context("get local datacenters")?; + .with_context(|| { + format!( + "cannot reach Rivet Engine at {} while listing datacenters", + config.endpoint + ) + })?; let status = response.status(); if !status.is_success() { let response_body = response diff --git a/rivetkit-rust/packages/rivetkit-core/tests/serverless.rs b/rivetkit-rust/packages/rivetkit-core/tests/serverless.rs index 1317144439..1812d9e9b0 100644 --- a/rivetkit-rust/packages/rivetkit-core/tests/serverless.rs +++ b/rivetkit-rust/packages/rivetkit-core/tests/serverless.rs @@ -179,6 +179,10 @@ mod moved_tests { namespace: "default".to_owned(), pool_name: "default".to_owned(), engine_binary_path: None, + dev_serverless_url: None, + dev_serverless_manual: false, + dev_serverless_drain_timeout: None, + dev_serverless_request_timeout: None, handle_inspector_http_in_runtime: true, serverless_base_path: Some("/api/rivet".to_owned()), serverless_package_version: "test-version".to_owned(), diff --git a/rivetkit-rust/packages/rivetkit/tests/client.rs b/rivetkit-rust/packages/rivetkit/tests/client.rs index 5730107aab..5e2a7aaa2c 100644 --- a/rivetkit-rust/packages/rivetkit/tests/client.rs +++ b/rivetkit-rust/packages/rivetkit/tests/client.rs @@ -145,6 +145,7 @@ fn test_envoy_handle(endpoint: String) -> EnvoyHandle { token: Some("secret".to_string()), namespace: "test-ns".to_string(), pool_name: "test-pool".to_string(), + envoy_key: None, prepopulate_actor_names: HashMap::new(), metadata: None, not_global: true, diff --git a/rivetkit-typescript/artifacts/registry-config.json b/rivetkit-typescript/artifacts/registry-config.json index 4375317507..5ca892343c 100644 --- a/rivetkit-typescript/artifacts/registry-config.json +++ b/rivetkit-typescript/artifacts/registry-config.json @@ -22,6 +22,29 @@ "description": "Disable the welcome message on startup. Default: false", "type": "boolean" }, + "pool": { + "description": "Pool name. Defaults to 'default'. Can also be set via RIVET_POOL.", + "type": "string" + }, + "version": { + "description": "Runtime version. Can also be set via RIVET_VERSION.", + "type": "number" + }, + "sqlite": { + "description": "SQLite runtime configuration.", + "type": "object", + "properties": { + "backend": { + "description": "SQLite backend to use. Native defaults to local. Wasm defaults to remote and cannot use local.", + "type": "string", + "enum": [ + "local", + "remote" + ] + } + }, + "additionalProperties": false + }, "logging": { "description": "Logging configuration.", "type": "object", @@ -43,9 +66,19 @@ "additionalProperties": false }, "endpoint": { - "description": "Endpoint URL to connect to Rivet Engine. Supports URL auth syntax: https://namespace:token@api.rivet.dev. Can also be set via RIVET_ENDPOINT environment variable.", + "description": "Deprecated. Use engine.endpoint or RIVET_ENDPOINT.", "type": "string" }, + "engine": { + "type": "object", + "properties": { + "endpoint": { + "description": "Advanced engine endpoint override. Prefer RIVET_ENDPOINT.", + "type": "string" + } + }, + "additionalProperties": false + }, "token": { "description": "Authentication token for Rivet Engine. Can also be set via RIVET_TOKEN environment variable.", "type": "string" @@ -64,127 +97,9 @@ "type": "string" } }, - "staticDir": { - "description": "Directory to serve static files from. When set, registry.start() serves static files alongside the actor API.", - "type": "string" - }, - "httpBasePath": { - "description": "Base path for the local RivetKit API. Default: '/'", - "type": "string" - }, - "httpPort": { - "description": "Port to run the local HTTP server on. Default: 6421", - "type": "number" - }, - "httpHost": { - "description": "Host to bind the local HTTP server to.", - "type": "string" - }, - "startEngine": { - "description": "Starts the full Rust engine process locally. Default: false", - "type": "boolean" - }, "engineVersion": { "description": "Version of the local engine package to use. Defaults to the current RivetKit version.", "type": "string" - }, - "configurePool": { - "description": "Automatically configure serverless runners in the engine.", - "type": "object", - "properties": { - "name": { - "description": "Name of the runner pool.", - "type": "string" - }, - "url": { - "description": "URL of the serverless platform to configure runners.", - "type": "string" - }, - "headers": { - "description": "Headers to include in requests to the serverless platform.", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" - } - }, - "requestLifespan": { - "description": "Maximum lifespan of a request in seconds.", - "type": "number" - }, - "drainGracePeriod": { - "description": "Grace period before the serverless request is forcibly closed, in seconds.", - "type": "number" - }, - "metadata": { - "description": "Additional metadata to pass to the serverless platform.", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "metadataPollInterval": { - "description": "Interval in milliseconds between metadata polls from the engine. Defaults to 10000 milliseconds (10 seconds).", - "type": "number" - }, - "drainOnVersionUpgrade": { - "description": "Drain runners when a new version is deployed. Defaults to true.", - "type": "boolean" - } - }, - "required": [ - "url" - ], - "additionalProperties": false - }, - "serverless": { - "description": "Configuration for serverless deployment mode.", - "type": "object", - "properties": { - "basePath": { - "description": "Base path for serverless API routes. Default: '/api/rivet'", - "type": "string" - }, - "maxStartPayloadBytes": { - "description": "Maximum POST /start body size in bytes. Default: 1048576", - "type": "number" - }, - "publicEndpoint": { - "description": "The endpoint that clients should connect to. Supports URL auth syntax: https://namespace:token@api.rivet.dev", - "type": "string" - }, - "publicToken": { - "description": "Token that clients should use when connecting via the public endpoint.", - "type": "string" - } - }, - "additionalProperties": false - }, - "envoy": { - "description": "Configuration for envoy mode.", - "type": "object", - "properties": { - "totalSlots": { - "description": "Total number of actor slots available. Default: 100000", - "type": "number" - }, - "poolName": { - "description": "Name of this envoy pool. Default: 'default'", - "type": "string" - }, - "envoyKey": { - "description": "Deprecated. Authentication key for the envoy.", - "type": "string" - }, - "version": { - "description": "Version number of this envoy. Default: 1", - "type": "number" - } - }, - "additionalProperties": false } }, "required": [ diff --git a/rivetkit-typescript/packages/rivetkit-napi/index.d.ts b/rivetkit-typescript/packages/rivetkit-napi/index.d.ts index 95af8b0aca..3bb899f1da 100644 --- a/rivetkit-typescript/packages/rivetkit-napi/index.d.ts +++ b/rivetkit-typescript/packages/rivetkit-napi/index.d.ts @@ -149,12 +149,17 @@ export interface JsQueueInspectMessage { createdAtMs: number } export interface JsServeConfig { + mode: string version: number endpoint: string token?: string namespace: string poolName: string engineBinaryPath?: string + devServerlessUrl?: string + devServerlessManual: boolean + devServerlessDrainTimeout?: number + devServerlessRequestTimeout?: number handleInspectorHttpInRuntime?: boolean serverlessBasePath?: string serverlessPackageVersion: string diff --git a/rivetkit-typescript/packages/rivetkit-napi/src/registry.rs b/rivetkit-typescript/packages/rivetkit-napi/src/registry.rs index 5f720d3232..4bbf57ebfb 100644 --- a/rivetkit-typescript/packages/rivetkit-napi/src/registry.rs +++ b/rivetkit-typescript/packages/rivetkit-napi/src/registry.rs @@ -19,12 +19,17 @@ use crate::{NapiInvalidState, napi_anyhow_error}; #[napi(object)] pub struct JsServeConfig { + pub mode: String, pub version: u32, pub endpoint: String, pub token: Option, pub namespace: String, pub pool_name: String, pub engine_binary_path: Option, + pub dev_serverless_url: Option, + pub dev_serverless_manual: bool, + pub dev_serverless_drain_timeout: Option, + pub dev_serverless_request_timeout: Option, pub handle_inspector_http_in_runtime: Option, pub serverless_base_path: Option, pub serverless_package_version: String, @@ -150,6 +155,7 @@ impl CoreRegistry { endpoint = %config.endpoint, namespace = %config.namespace, pool_name = %config.pool_name, + mode = %config.mode, starting_engine = config.engine_binary_path.is_some(), "serving native registry" ); @@ -174,6 +180,10 @@ impl CoreRegistry { namespace: config.namespace, pool_name: config.pool_name, engine_binary_path: config.engine_binary_path.map(PathBuf::from), + dev_serverless_url: config.dev_serverless_url, + dev_serverless_manual: config.dev_serverless_manual, + dev_serverless_drain_timeout: config.dev_serverless_drain_timeout, + dev_serverless_request_timeout: config.dev_serverless_request_timeout, handle_inspector_http_in_runtime: config .handle_inspector_http_in_runtime .unwrap_or(false), @@ -409,6 +419,10 @@ impl CoreRegistry { namespace: config.namespace, pool_name: config.pool_name, engine_binary_path: config.engine_binary_path.map(PathBuf::from), + dev_serverless_url: config.dev_serverless_url, + dev_serverless_manual: config.dev_serverless_manual, + dev_serverless_drain_timeout: config.dev_serverless_drain_timeout, + dev_serverless_request_timeout: config.dev_serverless_request_timeout, handle_inspector_http_in_runtime: config .handle_inspector_http_in_runtime .unwrap_or(true), diff --git a/rivetkit-typescript/packages/rivetkit/runtime/index.ts b/rivetkit-typescript/packages/rivetkit/runtime/index.ts index 88328d91f9..cae53d6554 100644 --- a/rivetkit-typescript/packages/rivetkit/runtime/index.ts +++ b/rivetkit-typescript/packages/rivetkit/runtime/index.ts @@ -29,7 +29,7 @@ async function ensureLocalRunnerConfig(config: RegistryConfig): Promise { const clientConfig = convertRegistryConfigToClientConfig(config); const dcsRes = await getDatacenters(clientConfig); - await updateRunnerConfig(clientConfig, config.envoy.poolName, { + await updateRunnerConfig(clientConfig, config.pool, { datacenters: Object.fromEntries( dcsRes.datacenters.map((dc) => [ dc.name, @@ -83,7 +83,7 @@ export class Runtime { if (config.startEngine) { throw new Error( - "Runtime.create() can no longer spawn the TypeScript engine process. Use Registry.startEnvoy() with the native rivetkit-core engine path instead.", + "Runtime.create() can no longer spawn the TypeScript engine process. Use Registry.start() with the native rivetkit-core engine path instead.", ); } @@ -106,7 +106,7 @@ export class Runtime { async ensureHttpServer(): Promise { throw new Error( - "Runtime.ensureHttpServer() relied on the removed TypeScript routing stack. Use Registry.startEnvoy() with the native rivetkit-core path instead.", + "Runtime.ensureHttpServer() relied on the removed TypeScript routing stack. Use Registry.start() with the native rivetkit-core path instead.", ); } @@ -116,7 +116,7 @@ export class Runtime { ); } - async startEnvoy(): Promise { + async start(): Promise { if (this.#startKind === "serverful") return; this.#startKind = "serverful"; diff --git a/rivetkit-typescript/packages/rivetkit/src/client/config.ts b/rivetkit-typescript/packages/rivetkit/src/client/config.ts index 0999bbb902..f59f1f3bd7 100644 --- a/rivetkit-typescript/packages/rivetkit/src/client/config.ts +++ b/rivetkit-typescript/packages/rivetkit/src/client/config.ts @@ -152,7 +152,7 @@ export function convertRegistryConfigToClientConfig( endpoint: config.endpoint, token: config.token, namespace: config.namespace, - poolName: config.envoy.poolName, + poolName: config.pool, headers: config.headers, gateway: { skipReadyWait: false }, encoding: "bare", diff --git a/rivetkit-typescript/packages/rivetkit/src/common/router.ts b/rivetkit-typescript/packages/rivetkit/src/common/router.ts index dd745a705b..328deb4007 100644 --- a/rivetkit-typescript/packages/rivetkit/src/common/router.ts +++ b/rivetkit-typescript/packages/rivetkit/src/common/router.ts @@ -156,7 +156,7 @@ export function handleMetadataRequest( version: VERSION, envoy: { kind: envoyKind, - version: config.envoy.version, + version: config.version, }, envoyProtocolVersion: envoyProtocol.VERSION, actorNames: buildActorNames(config), diff --git a/rivetkit-typescript/packages/rivetkit/src/drivers/engine/actor-driver.ts b/rivetkit-typescript/packages/rivetkit/src/drivers/engine/actor-driver.ts index 5a74826118..bd2c4ae3b9 100644 --- a/rivetkit-typescript/packages/rivetkit/src/drivers/engine/actor-driver.ts +++ b/rivetkit-typescript/packages/rivetkit/src/drivers/engine/actor-driver.ts @@ -217,11 +217,11 @@ export class EngineActorDriver implements ActorDriver { // Create configuration const envoyConfig: EnvoyConfig = { - version: config.envoy.version, + version: config.version, endpoint: getEndpoint(config), token: config.token, namespace: config.namespace, - poolName: config.envoy.poolName, + poolName: config.pool, notGlobal: true, metadata: { rivetkit: { version: VERSION }, @@ -262,7 +262,7 @@ export class EngineActorDriver implements ActorDriver { msg: "envoy client started", endpoint: config.endpoint, namespace: config.namespace, - poolName: config.envoy.poolName, + poolName: config.pool, }); } diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/config/envoy.ts b/rivetkit-typescript/packages/rivetkit/src/registry/config/envoy.ts index d347bd6f79..2f1e213c86 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/config/envoy.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/config/envoy.ts @@ -1,34 +1,44 @@ import { z } from "zod/v4"; -import { getLogger } from "@/common/log"; -import { - isDev, - getNodeEnv, - getRivetPool, - getRivetTotalSlots, - getRivetEnvoyVersion, -} from "@/utils/env-vars"; -let warnedMissingVersion = false; - -export const EnvoyConfigSchema = z.object({ - poolName: z.string().default(() => getRivetPool() ?? "default"), - version: z.number().default(() => { - const version = getRivetEnvoyVersion(); - if (version !== undefined) return version; - - if (getNodeEnv() === "production" && !warnedMissingVersion) { - warnedMissingVersion = true; - getLogger("rivetkit").error( - "RIVET_ENVOY_VERSION is not set. Actors will not be versioned, which means they won't be drained on deploy. This is only needed when self-hosting or using a custom envoy (not needed for Rivet Compute). Set this as a build arg in your Dockerfile. See https://rivet.dev/docs/actors/versions", - ); +export const EnvoyConfigSchema = z + .object({ + key: z.unknown().optional(), + poolName: z.unknown().optional(), + version: z.unknown().optional(), + envoyKey: z.unknown().optional(), + }) + .superRefine((config, ctx) => { + if (config.key !== undefined) { + ctx.addIssue({ + code: "custom", + message: "envoy.key has been removed. Envoy keys are managed by RivetKit.", + path: ["key"], + }); } + if (config.poolName !== undefined) { + ctx.addIssue({ + code: "custom", + message: "envoy.poolName has been removed. Use top-level pool instead.", + path: ["poolName"], + }); + } + if (config.version !== undefined) { + ctx.addIssue({ + code: "custom", + message: "envoy.version has been removed. Use top-level version instead.", + path: ["version"], + }); + } + if (config.envoyKey !== undefined) { + ctx.addIssue({ + code: "custom", + message: + "envoy.envoyKey has been removed. Envoy keys are managed by RivetKit.", + path: ["envoyKey"], + }); + } + }) + .transform(() => ({})); - return 1; - }), - - // Deprecated. - totalSlots: z.number().default(() => getRivetTotalSlots() ?? 100000), - envoyKey: z.string().optional(), -}); export type EnvoyConfigInput = z.input; export type EnvoyConfig = z.infer; diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts b/rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts index 50a4f12081..e4ab82849d 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts @@ -15,18 +15,17 @@ import { type Logger, LogLevelSchema } from "@/common/log"; import { VERSION } from "@/utils"; import { tryParseEndpoint } from "@/utils/endpoint-parser"; import { + getNodeEnv, getRivetEndpoint, - getRivetEngine, getRivetkitRuntime, getRivetNamespace, - getRivetRunEngine, - getRivetRunEngineVersion, + getRivetPool, getRivetToken, - isDev, + getRivetVersion, + invalidRivetEnvironmentVariables, } from "@/utils/env-vars"; import { EnvoyConfigSchema } from "./envoy"; import { - ConfigurePoolSchema, DEFAULT_SERVERLESS_MAX_START_PAYLOAD_BYTES, ServerlessConfigSchema, } from "./serverless"; @@ -41,6 +40,9 @@ export type RegistryActors = z.infer; export const RuntimeKindSchema = z.enum(["auto", "native", "wasm"]); export type RuntimeKind = z.infer; +export const RuntimeModeSchema = z.enum(["envoy", "serverless"]); +export type RuntimeMode = z.infer; +export type RuntimeModeSource = "entrypoint" | "default"; export type WasmRuntimeBindings = typeof import("@rivetkit/rivetkit-wasm"); export type WasmRuntimeInitInput = Parameters< WasmRuntimeBindings["default"] @@ -75,7 +77,78 @@ export const SqliteConfigSchema = z }); export type SqliteConfig = z.infer; -// TODO: Add sane defaults for NODE_ENV=development +const EngineConfigSchema = z.object({ + endpoint: z.string().optional(), +}); + +const DevServerlessConfigSchema = z.union([ + z.literal("manual"), + z.object({ + url: z.string().url(), + drainTimeout: z.number().int().positive().optional(), + requestTimeout: z.number().int().positive().optional(), + }), +]); + +const EntrypointConfigSchema = z.discriminatedUnion("kind", [ + z.object({ + kind: z.literal("envoy"), + envoy: EnvoyConfigSchema.optional().default(() => + EnvoyConfigSchema.parse({}), + ), + }), + z.object({ + kind: z.literal("serverless"), + startEngine: z.boolean().optional(), + devServerless: DevServerlessConfigSchema.optional(), + serverless: ServerlessConfigSchema.optional().default(() => + ServerlessConfigSchema.parse({}), + ), + }), + z.object({ + kind: z.literal("listen"), + startEngine: z.boolean().optional(), + devServerless: DevServerlessConfigSchema.optional(), + serverless: ServerlessConfigSchema.optional().default(() => + ServerlessConfigSchema.parse({}), + ), + staticDir: z.string().optional(), + httpBasePath: z.string().optional().default("/api/rivet"), + httpPort: z.number().optional().default(6421), + httpHost: z.string().optional(), + }), +]); +export type EntrypointConfig = z.infer; +export type EntrypointConfigInput = z.input; + +function addEnvConfigConflict( + ctx: z.RefinementCtx, + envName: string, + path: (string | number)[], +): void { + ctx.addIssue({ + code: "custom", + message: `${envName} and setup(${path.join(".")}) cannot both be set. Use either the environment variable or setup config, not both.`, + path, + }); +} + +function parseEnvNumber( + ctx: z.RefinementCtx, + envName: string, + value: number | undefined, +): number | undefined { + if (value === undefined) return undefined; + if (!Number.isInteger(value) || value < 0) { + ctx.addIssue({ + code: "custom", + message: `${envName} must be a non-negative integer.`, + }); + return undefined; + } + return value; +} + export const RegistryConfigSchema = z .object({ // MARK: Actors @@ -161,7 +234,14 @@ export const RegistryConfigSchema = z // // created or must be imported async using `await import(...)` // getUpgradeWebSocket: z.custom().optional(), - // MARK: Runner Configuration + // MARK: Runtime Mode + entrypoint: EntrypointConfigSchema.optional(), + mode: z.unknown().optional(), + pool: z.string().optional(), + version: z.number().int().nonnegative().optional(), + devServerless: z.unknown().optional(), + + // MARK: Engine Configuration /** * Endpoint to connect to for Rivet Engine. * @@ -171,18 +251,14 @@ export const RegistryConfigSchema = z * * Can also be set via RIVET_ENDPOINT environment variables. */ - endpoint: z - .string() - .optional() - .transform((val) => val ?? getRivetEngine() ?? getRivetEndpoint()), - token: z - .string() - .optional() - .transform((val) => val ?? getRivetToken()), - namespace: z - .string() - .optional() - .transform((val) => val ?? getRivetNamespace()), + engine: EngineConfigSchema.optional(), + /** + * @deprecated Use engine.endpoint or RIVET_ENDPOINT. + * Kept as an internal escape hatch while framework fixtures migrate. + */ + endpoint: z.string().optional(), + token: z.string().optional(), + namespace: z.string().optional(), headers: z.record(z.string(), z.string()).optional().default({}), // MARK: Client @@ -194,10 +270,10 @@ export const RegistryConfigSchema = z * Directory to serve static files from. * * When set, the local RivetKit server will serve static files from this - * directory. This is used by `registry.start()` to serve a frontend + * directory. This is used by `registry.listen({ static })` to serve a frontend * alongside the actor API. */ - staticDir: z.string().optional(), + staticDir: z.unknown().optional(), /** * @experimental * @@ -205,19 +281,19 @@ export const RegistryConfigSchema = z * For example, if the base path is `/foo`, then the route `/actors` * will be available at `/foo/actors`. */ - httpBasePath: z.string().optional().default("/"), + httpBasePath: z.unknown().optional(), /** * @experimental * * What port to run the local HTTP server on. */ - httpPort: z.number().optional().default(6421), + httpPort: z.unknown().optional(), /** * @experimental * * What host to bind the local HTTP server to. */ - httpHost: z.string().optional(), + httpHost: z.unknown().optional(), // MARK: Engine /** @@ -225,26 +301,14 @@ export const RegistryConfigSchema = z * * Starts the full Rust engine process locally. */ - startEngine: z.boolean().default(() => getRivetRunEngine()), + startEngine: z.unknown().optional(), /** @experimental */ - engineVersion: z - .string() - .optional() - .default(() => getRivetRunEngineVersion() ?? VERSION), - /** - * @experimental - * - * Automatically configure serverless envoys in the engine. - */ - configurePool: ConfigurePoolSchema.optional(), + engineVersion: z.string().optional().default(() => VERSION), + configurePool: z.unknown().optional(), // MARK: Runtime-specific - serverless: ServerlessConfigSchema.optional().default(() => - ServerlessConfigSchema.parse({}), - ), - envoy: EnvoyConfigSchema.optional().default(() => - EnvoyConfigSchema.parse({}), - ), + serverless: z.unknown().optional(), + envoy: z.unknown().optional(), // MARK: Shutdown /** @@ -289,9 +353,48 @@ export const RegistryConfigSchema = z })), }) .transform((config, ctx) => { - const isDevEnv = isDev(); + for (const invalid of invalidRivetEnvironmentVariables()) { + ctx.addIssue({ + code: "custom", + message: invalid.message, + }); + } + const removedSetupFields: Array<[string, string]> = [ + ["mode", "mode has been removed. Call registry.start() or registry.fetchHandler() instead."], + ["startEngine", "startEngine has been removed from setup(). Use dev.startEngine on registry.listen() or registry.fetchHandler()."], + ["staticDir", "staticDir has been removed from setup(). Use registry.listen({ static }) instead."], + ["httpBasePath", "httpBasePath has been removed from setup(). Use the entrypoint path option instead."], + ["httpPort", "httpPort has been removed from setup(). Use registry.listen({ port }) instead."], + ["httpHost", "httpHost has been removed from setup(). Use registry.listen({ host }) instead."], + ["devServerless", "devServerless has been removed from setup(). Use dev on registry.listen() or registry.fetchHandler()."], + ["serverless", "serverless has been removed from setup(). Pass serverless options to registry.fetchHandler()."], + ["envoy", "envoy has been removed from setup(). Call registry.start() for envoy mode."], + ["configurePool", "configurePool has been removed. Use dev on registry.listen() or registry.fetchHandler()."], + ]; + for (const [field, message] of removedSetupFields) { + if ((config as Record)[field] !== undefined) { + ctx.addIssue({ + code: "custom", + message, + path: [field], + }); + } + } + if (config.engine?.endpoint && config.endpoint) { + ctx.addIssue({ + code: "custom", + message: "cannot specify both engine.endpoint and endpoint", + path: ["engine", "endpoint"], + }); + } + + const isProduction = getNodeEnv() === "production"; const sqliteBackend = config.sqlite?.backend ?? config.test?.sqliteBackend; + const entrypoint = config.entrypoint ?? { + kind: "envoy" as const, + envoy: EnvoyConfigSchema.parse({}), + }; if (config.runtime === "wasm" && sqliteBackend === "local") { ctx.addIssue({ @@ -310,52 +413,115 @@ export const RegistryConfigSchema = z ? { backend: "remote" as const } : config.sqlite; - // Parse endpoint string (env var fallback is applied via transform above) - const parsedEndpoint = config.endpoint - ? tryParseEndpoint(ctx, { - endpoint: config.endpoint, - path: ["endpoint"], - namespace: config.namespace, - token: config.token, - }) - : undefined; + const modeSource: RuntimeModeSource = + config.entrypoint !== undefined ? "entrypoint" : "default"; + const mode: RuntimeMode = + entrypoint.kind === "envoy" ? "envoy" : "serverless"; + + const envEndpoint = getRivetEndpoint(); + const configEndpoint = config.engine?.endpoint ?? config.endpoint; + if (envEndpoint !== undefined && configEndpoint !== undefined) { + addEnvConfigConflict( + ctx, + "RIVET_ENDPOINT", + config.engine?.endpoint ? ["engine", "endpoint"] : ["endpoint"], + ); + } + const rawEndpoint = configEndpoint ?? envEndpoint; + const envToken = getRivetToken(); + if (envToken !== undefined && config.token !== undefined) { + addEnvConfigConflict(ctx, "RIVET_TOKEN", ["token"]); + } + const rawToken = config.token ?? envToken; + const envNamespace = getRivetNamespace(); + if (envNamespace !== undefined && config.namespace !== undefined) { + addEnvConfigConflict(ctx, "RIVET_NAMESPACE", ["namespace"]); + } + const rawNamespace = config.namespace ?? envNamespace; + const envPool = getRivetPool(); + if (envPool !== undefined && config.pool !== undefined) { + addEnvConfigConflict(ctx, "RIVET_POOL", ["pool"]); + } + const pool = config.pool ?? envPool ?? "default"; + const envVersion = parseEnvNumber(ctx, "RIVET_VERSION", getRivetVersion()); + if (envVersion !== undefined && config.version !== undefined) { + addEnvConfigConflict(ctx, "RIVET_VERSION", ["version"]); + } + const version = config.version ?? envVersion ?? (isProduction ? undefined : 1); + if (version === undefined) { + ctx.addIssue({ + code: "custom", + message: + "version or RIVET_VERSION is required when NODE_ENV is production. See https://rivet.dev/docs/actors/versions", + path: ["version"], + }); + } + const entrypointEnvoy = + entrypoint.kind === "envoy" ? entrypoint.envoy : EnvoyConfigSchema.parse({}); + const envoy = entrypointEnvoy; + + const shouldManageLocalEngine = + entrypoint.kind === "envoy" + ? !isProduction && rawEndpoint === undefined + : !isProduction && entrypoint.startEngine === true; + const devServerless = + entrypoint.kind === "serverless" || entrypoint.kind === "listen" + ? entrypoint.devServerless + : undefined; + const serverless = + entrypoint.kind === "serverless" || entrypoint.kind === "listen" + ? entrypoint.serverless + : ServerlessConfigSchema.parse({}); + const staticDir = entrypoint.kind === "listen" ? entrypoint.staticDir : undefined; + const httpBasePath = + entrypoint.kind === "listen" ? entrypoint.httpBasePath : "/"; + const httpPort = entrypoint.kind === "listen" ? entrypoint.httpPort : 6421; + const httpHost = entrypoint.kind === "listen" ? entrypoint.httpHost : undefined; // Can't start a local engine and connect to a remote endpoint. - if (config.startEngine && parsedEndpoint) { + if (shouldManageLocalEngine && rawEndpoint !== undefined) { ctx.addIssue({ code: "custom", message: "cannot specify both startEngine and endpoint", }); } - // configurePool requires an engine (via endpoint or startEngine). - if (config.configurePool && !parsedEndpoint && !config.startEngine) { + if (mode === "envoy" && isProduction && rawEndpoint === undefined) { ctx.addIssue({ code: "custom", message: - "configurePool requires either endpoint or startEngine", + "mode: \"envoy\" requires RIVET_ENDPOINT or engine.endpoint outside local development.", + path: ["engine", "endpoint"], }); } - // Flatten the endpoint and apply defaults for namespace/token - // If startEngine is enabled, set endpoint to the engine endpoint. - const endpoint = config.startEngine + // Parse endpoint string after env/config ambiguity checks. + const parsedEndpoint = rawEndpoint + ? tryParseEndpoint(ctx, { + endpoint: rawEndpoint, + path: config.engine?.endpoint ? ["engine", "endpoint"] : ["endpoint"], + namespace: rawNamespace, + token: rawToken, + }) + : undefined; + + const endpoint = shouldManageLocalEngine ? ENGINE_ENDPOINT : (parsedEndpoint?.endpoint ?? - (isDevEnv ? ENGINE_ENDPOINT : undefined)); + (mode === "serverless" ? ENGINE_ENDPOINT : undefined)); const validateServerlessEndpoint = Boolean( - config.startEngine || parsedEndpoint, + mode === "serverless" && (shouldManageLocalEngine || parsedEndpoint), ); // Namespace priority: parsed from endpoint URL > config value (includes env var) > "default" const namespace = - parsedEndpoint?.namespace ?? config.namespace ?? "default"; + parsedEndpoint?.namespace ?? rawNamespace ?? "default"; // Token priority: parsed from endpoint URL > config value (includes env var) - const token = parsedEndpoint?.token ?? config.token; + const token = parsedEndpoint?.token ?? rawToken; // Parse publicEndpoint string (env var fallback is applied via transform in serverless schema) - const parsedPublicEndpoint = config.serverless.publicEndpoint + const parsedPublicEndpoint = serverless.publicEndpoint ? tryParseEndpoint(ctx, { - endpoint: config.serverless.publicEndpoint, + endpoint: serverless.publicEndpoint, path: ["serverless", "publicEndpoint"], }) : undefined; @@ -375,17 +541,24 @@ export const RegistryConfigSchema = z // In dev mode, clients connect directly to the local Rivet Engine. const publicEndpoint = parsedPublicEndpoint?.endpoint ?? - (isDevEnv && config.startEngine ? ENGINE_ENDPOINT : undefined); + (shouldManageLocalEngine ? ENGINE_ENDPOINT : undefined); // We extract publicNamespace to validate that it matches the backend // namespace (see validation above), not for functional use. const publicNamespace = parsedPublicEndpoint?.namespace; const publicToken = - parsedPublicEndpoint?.token ?? config.serverless.publicToken; + parsedPublicEndpoint?.token ?? serverless.publicToken; // If endpoint is set or starting the engine, we'll use the engine driver. return { ...config, sqlite, + mode, + modeSource, + pool, + version: version ?? 1, + startEngine: shouldManageLocalEngine, + devServerless: isProduction ? undefined : devServerless, + envoy, endpoint, namespace, token, @@ -393,8 +566,12 @@ export const RegistryConfigSchema = z publicNamespace, publicToken, validateServerlessEndpoint, + staticDir, + httpBasePath, + httpPort, + httpHost, serverless: { - ...config.serverless, + ...serverless, publicEndpoint, }, }; @@ -403,7 +580,18 @@ export const RegistryConfigSchema = z export type RegistryConfig = z.infer; export type RegistryConfigInput = Omit< z.input, - "use" + | "use" + | "entrypoint" + | "mode" + | "startEngine" + | "staticDir" + | "httpBasePath" + | "httpPort" + | "httpHost" + | "devServerless" + | "serverless" + | "envoy" + | "configurePool" > & { use: A }; export function buildActorNames( @@ -455,48 +643,14 @@ export function buildActorNames( // These schemas are JSON-serializable versions used for documentation generation. // They exclude runtime-only fields (transforms, custom types, Logger instances). -export const DocConfigurePoolSchema = z - .object({ - name: z.string().optional().describe("Name of the runner pool."), - url: z - .string() - .describe("URL of the serverless platform to configure runners."), - headers: z - .record(z.string(), z.string()) - .optional() - .describe( - "Headers to include in requests to the serverless platform.", - ), - requestLifespan: z - .number() - .optional() - .describe("Maximum lifespan of a request in seconds."), - drainGracePeriod: z - .number() - .optional() - .describe( - "Grace period before the serverless request is forcibly closed, in seconds.", - ), - metadata: z - .record(z.string(), z.unknown()) - .optional() - .describe( - "Additional metadata to pass to the serverless platform.", - ), - metadataPollInterval: z - .number() - .optional() - .describe( - "Interval in milliseconds between metadata polls from the engine. Defaults to 10000 milliseconds (10 seconds).", - ), - drainOnVersionUpgrade: z - .boolean() - .optional() - .describe( - "Drain runners when a new version is deployed. Defaults to true.", - ), - }) - .optional(); +export const DocDevServerlessConfigSchema = z + .union([ + z.literal("manual"), + z.object({ + url: z.string().describe("Local serverless endpoint URL."), + }), + ]) + .describe("Local serverless development configuration."); export const DocServerlessConfigSchema = z .object({ @@ -528,24 +682,7 @@ export const DocServerlessConfigSchema = z .describe("Configuration for serverless deployment mode."); export const DocEnvoyConfigSchema = z - .object({ - totalSlots: z - .number() - .optional() - .describe("Total number of actor slots available. Default: 100000"), - poolName: z - .string() - .optional() - .describe("Name of this envoy pool. Default: 'default'"), - envoyKey: z - .string() - .optional() - .describe("Deprecated. Authentication key for the envoy."), - version: z - .number() - .optional() - .describe("Version number of this envoy. Default: 1"), - }) + .object({}) .describe("Configuration for envoy mode."); export const DocSqliteConfigSchema = z @@ -580,6 +717,14 @@ export const DocRegistryConfigSchema = z .boolean() .optional() .describe("Disable the welcome message on startup. Default: false"), + pool: z + .string() + .optional() + .describe("Pool name. Defaults to 'default'. Can also be set via RIVET_POOL."), + version: z + .number() + .optional() + .describe("Runtime version. Can also be set via RIVET_VERSION."), sqlite: DocSqliteConfigSchema, logging: z .object({ @@ -593,8 +738,16 @@ export const DocRegistryConfigSchema = z .string() .optional() .describe( - "Endpoint URL to connect to Rivet Engine. Supports URL auth syntax: https://namespace:token@api.rivet.dev. Can also be set via RIVET_ENDPOINT environment variable.", + "Deprecated. Use engine.endpoint or RIVET_ENDPOINT.", ), + engine: z + .object({ + endpoint: z + .string() + .optional() + .describe("Advanced engine endpoint override. Prefer RIVET_ENDPOINT."), + }) + .optional(), token: z .string() .optional() @@ -613,40 +766,11 @@ export const DocRegistryConfigSchema = z .describe( "Additional headers to include in requests to Rivet Engine.", ), - staticDir: z - .string() - .optional() - .describe( - "Directory to serve static files from. When set, registry.start() serves static files alongside the actor API.", - ), - httpBasePath: z - .string() - .optional() - .describe("Base path for the local RivetKit API. Default: '/'"), - httpPort: z - .number() - .optional() - .describe("Port to run the local HTTP server on. Default: 6421"), - httpHost: z - .string() - .optional() - .describe("Host to bind the local HTTP server to."), - startEngine: z - .boolean() - .optional() - .describe( - "Starts the full Rust engine process locally. Default: false", - ), engineVersion: z .string() .optional() .describe( "Version of the local engine package to use. Defaults to the current RivetKit version.", ), - configurePool: DocConfigurePoolSchema.describe( - "Automatically configure serverless runners in the engine.", - ), - serverless: DocServerlessConfigSchema.optional(), - envoy: DocEnvoyConfigSchema.optional(), }) .describe("RivetKit registry configuration."); diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/config/serverless.ts b/rivetkit-typescript/packages/rivetkit/src/registry/config/serverless.ts index 7064be4665..fcda814fb9 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/config/serverless.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/config/serverless.ts @@ -3,18 +3,16 @@ import { getRivetPublicEndpoint, getRivetPublicToken } from "@/utils/env-vars"; export const DEFAULT_SERVERLESS_MAX_START_PAYLOAD_BYTES = 16 * 1024 * 1024; -export const ConfigurePoolSchema = z - .object({ - name: z.string().optional(), - url: z.string(), - headers: z.record(z.string(), z.string()).optional(), - requestLifespan: z.number().optional(), - drainGracePeriod: z.number().optional(), - metadata: z.record(z.string(), z.unknown()).optional(), - metadataPollInterval: z.number().optional(), - drainOnVersionUpgrade: z.boolean().optional(), - }) - .optional(); +export const DevServerlessConfigSchema = z.union([ + z.literal("manual"), + z.object({ + url: z.url(), + }), +]); +export type DevServerlessConfigInput = z.input< + typeof DevServerlessConfigSchema +>; +export type DevServerlessConfig = z.infer; export const ServerlessConfigSchema = z.object({ // MARK: Routing diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/index.ts b/rivetkit-typescript/packages/rivetkit/src/registry/index.ts index 96e2e73c7c..b0a16f2229 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/index.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/index.ts @@ -1,18 +1,66 @@ +import { Hono } from "hono"; +import { z } from "zod"; +import { toRivetError } from "@/actor/errors"; import { ENGINE_ENDPOINT } from "@/common/engine"; -import { configureServerlessPool } from "@/serverless/configure"; import { VERSION } from "@/utils"; +import { + crossPlatformServe, + loadRuntimeServeStatic, +} from "@/utils/serve"; import { type RegistryActors, type RegistryConfig, + type EntrypointConfigInput, type RegistryConfigInput, RegistryConfigSchema, } from "./config"; +import { EnvoyConfigSchema } from "./config/envoy"; import { logger } from "./log"; import { buildConfiguredRegistry } from "./native"; import type { RuntimeServerlessResponseHead } from "./runtime"; type ShutdownSignal = "SIGINT" | "SIGTERM"; +function metadataError(metadata: unknown): string | undefined { + if ( + typeof metadata === "object" && + metadata !== null && + "error" in metadata && + typeof metadata.error === "string" + ) { + return metadata.error; + } + + return undefined; +} + +function isEngineReachabilityError(message: string): boolean { + return /cannot reach Rivet Engine|Connection refused|ECONNREFUSED|tcp connect error|error sending request/i.test( + message, + ); +} + +function logRegistryServeError(config: RegistryConfig, err: unknown): void { + const rivetError = toRivetError(err); + const detail = metadataError(rivetError.metadata) ?? rivetError.message; + + if (config.endpoint && isEngineReachabilityError(detail)) { + logger().warn( + { + endpoint: config.endpoint, + namespace: config.namespace, + group: rivetError.group, + code: rivetError.code, + error: detail, + }, + "cannot reach Rivet Engine", + ); + return; + } + + logger().warn({ err }, "runtime registry serve errored"); +} + export type FetchHandler = ( request: Request, ...args: any @@ -27,6 +75,93 @@ export interface RegistryDiagnostics { envoyActiveActorCount?: number | null; } +const FetchHandlerDevConfigSchema = z.object({ + url: z.string().url(), + startEngine: z.boolean().optional(), + drainTimeout: z.number().int().positive().optional(), + requestTimeout: z.number().int().positive().optional(), +}); +export type FetchHandlerDevConfig = z.infer< + typeof FetchHandlerDevConfigSchema +>; +export type FetchHandlerDev = false | string | FetchHandlerDevConfig; + +const FetchHandlerOptsSchema = z.object({ + path: z.string().min(1), + dev: z + .union([z.literal(false), z.string().url(), FetchHandlerDevConfigSchema]) + .optional(), + publicEndpoint: z.string().optional(), + publicToken: z.string().optional(), + maxStartPayloadBytes: z.number().int().positive().optional(), +}); +export type FetchHandlerOpts = z.input; + +const StaticConfigSchema = z.union([ + z.boolean(), + z.string().min(1), + z.object({ dir: z.string().min(1) }), +]); +export type StaticConfig = z.input; + +const ListenDevConfigSchema = z.object({ + url: z.string().url().optional(), + startEngine: z.boolean().optional(), + drainTimeout: z.number().int().positive().optional(), + requestTimeout: z.number().int().positive().optional(), +}); +export type ListenDevConfig = z.infer; +export type ListenDev = boolean | ListenDevConfig; + +const ListenOptsSchema = z + .object({ + port: z.number().int().positive(), + host: z.string().optional(), + path: z.string().min(1), + static: StaticConfigSchema.optional(), + dev: z.union([z.boolean(), ListenDevConfigSchema]).optional(), + }) + .strict(); +export type ListenOpts = z.input; + +const StartOptsSchema = z + .object({ + envoy: EnvoyConfigSchema.optional().default(() => + EnvoyConfigSchema.parse({}), + ), + }) + .strict() + .optional() + .default(() => ({ envoy: EnvoyConfigSchema.parse({}) })); +export type StartOpts = z.input; + +type EntrypointKind = "start" | "fetchHandler" | "listen"; + +function isDevelopmentEnv(): boolean { + return ( + typeof process !== "undefined" && + process.env?.NODE_ENV === "development" + ); +} + +function normalizeBasePath(path: string): string { + const trimmed = path.trim(); + if (trimmed === "" || trimmed === "/") return "/"; + return `/${trimmed.replace(/^\/+|\/+$/g, "")}`; +} + +function routeForBasePath(path: string): string { + const normalized = normalizeBasePath(path); + return normalized === "/" ? "/*" : `${normalized}/*`; +} + +function staticDirFromConfig(config: StaticConfig | undefined): string | undefined { + if (config === false) return undefined; + if (config === true || config === undefined) return "public"; + if (typeof config === "string") return config; + return config.dir; +} + export class Registry { #config: RegistryConfigInput; @@ -41,7 +176,8 @@ export class Registry { #runtimeServePromise?: Promise; #runtimeServeConfiguredPromise?: ReturnType; #runtimeServerlessPromise?: ReturnType; - #configureServerlessPoolPromise?: Promise; + #runtimeHttpServerPromise?: Promise<{ closeServer?: () => void }>; + #activeEntrypoint?: EntrypointKind; #welcomePrinted = false; #shutdownInstalled = false; #shutdownInFlight: Promise | null = null; @@ -51,72 +187,51 @@ export class Registry { this.#config = config; } - #ensureServerlessPoolConfigured(config: RegistryConfig): Promise | undefined { - if (!config.configurePool) return undefined; - - if (!this.#configureServerlessPoolPromise) { - this.#configureServerlessPoolPromise = configureServerlessPool(config).catch( - (error) => { - this.#configureServerlessPoolPromise = undefined; - throw error; - }, + #claimEntrypoint(kind: EntrypointKind): void { + if (this.#activeEntrypoint !== undefined) { + throw new Error( + `registry.${kind}() cannot be used after registry.${this.#activeEntrypoint}() has already selected the runtime entrypoint.`, ); - this.#configureServerlessPoolPromise.catch(() => {}); } - - return this.#configureServerlessPoolPromise; + this.#activeEntrypoint = kind; } - /** - * Handle an incoming HTTP request for serverless deployments. - * - * @example - * ```ts - * const app = new Hono(); - * app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); - * export default app; - * ``` - */ - public async handler(request: Request): Promise { - const config = this.parseConfig(); - this.#printWelcome(config, "serverless"); + #parseConfigWithEntrypoint( + entrypoint: EntrypointConfigInput, + ): RegistryConfig { + return RegistryConfigSchema.parse({ + ...this.#config, + entrypoint, + }); + } - if (!this.#runtimeServerlessPromise) { - this.#runtimeServerlessPromise = buildConfiguredRegistry(config); - } + #createServerlessHandler( + config: RegistryConfig, + configuredRegistryPromise: ReturnType, + kind: "serverless" | "listen" = "serverless", + ): FetchHandler { + this.#printWelcome(config, kind); + + return async (request: Request): Promise => { + return await this.#handleServerlessRequest( + request, + config, + configuredRegistryPromise, + ); + }; + } + async #handleServerlessRequest( + request: Request, + config: RegistryConfig, + configuredRegistryPromise: ReturnType, + ): Promise { const { runtime, registry, serveConfig } = - await this.#runtimeServerlessPromise; + await configuredRegistryPromise; const isStartRequest = isServerlessStartRequest( request, serveConfig.serverlessBasePath ?? "/api/rivet", ); - const isMetadataRequest = isServerlessMetadataRequest( - request, - serveConfig.serverlessBasePath ?? "/api/rivet", - ); - const isEngineMetadataRequest = - request.headers.get("user-agent")?.startsWith("RivetEngine/") ?? false; - - if (isStartRequest) { - try { - await this.#ensureServerlessPoolConfigured(config); - } catch (error) { - return new Response( - JSON.stringify({ - group: "guard", - code: "service_unavailable", - message: "Serverless pool is not configured.", - metadata: null, - }), - { - status: 503, - headers: { "content-type": "application/json" }, - }, - ); - } - } - const cancelToken = runtime.createCancellationToken(); const abort = () => runtime.cancelCancellationToken(cancelToken); if (request.signal.aborted) { @@ -240,25 +355,6 @@ export class Registry { throw err; } - if (isMetadataRequest && !isEngineMetadataRequest) { - try { - await this.#ensureServerlessPoolConfigured(config); - } catch (error) { - return new Response( - JSON.stringify({ - group: "guard", - code: "service_unavailable", - message: "Serverless pool is not configured.", - metadata: null, - }), - { - status: 503, - headers: { "content-type": "application/json" }, - }, - ); - } - } - return new Response(stream, { status: head.status, headers: head.headers, @@ -270,13 +366,37 @@ export class Registry { * * @example * ```ts - * export default registry.serve(); + * const fetch = registry.fetchHandler({ path: "/api/rivet" }); + * export default { fetch }; * ``` */ - public serve(): ServerlessHandler { - return { - fetch: (request) => this.handler(request), - }; + public fetchHandler(opts: FetchHandlerOpts): FetchHandler { + const parsed = FetchHandlerOptsSchema.parse(opts); + const dev = this.#resolveFetchHandlerDev(parsed.dev); + const config = this.#parseConfigWithEntrypoint({ + kind: "serverless", + startEngine: dev?.startEngine, + devServerless: dev?.url + ? { + url: dev.url, + drainTimeout: dev.drainTimeout, + requestTimeout: dev.requestTimeout, + } + : undefined, + serverless: { + basePath: normalizeBasePath(parsed.path), + publicEndpoint: parsed.publicEndpoint, + publicToken: parsed.publicToken, + maxStartPayloadBytes: parsed.maxStartPayloadBytes, + }, + }); + + this.#claimEntrypoint("fetchHandler"); + this.#runtimeServerlessPromise = buildConfiguredRegistry(config); + return this.#createServerlessHandler( + config, + this.#runtimeServerlessPromise, + ); } public async diagnostics(): Promise { @@ -299,7 +419,7 @@ export class Registry { /** * Starts an actor envoy for standalone server deployments. */ - #startEnvoy(config: RegistryConfig, printWelcome: boolean) { + #startPersistentRuntime(config: RegistryConfig, printWelcome: boolean) { if (!this.#runtimeServePromise) { const configuredRegistryPromise = buildConfiguredRegistry(config); this.#runtimeServeConfiguredPromise = configuredRegistryPromise; @@ -312,7 +432,7 @@ export class Registry { // rejection unhandled. Downstream awaits (e.g. #runShutdown's // Promise.race) attach their own catches and still observe // resolution via the race. - logger().warn({ err }, "runtime registry serve errored"); + logRegistryServeError(config, err); }); // Install signal handlers once an envoy lifecycle has begun. Only // Mode A ever reaches here. Mode B (handler(request)) intentionally @@ -322,7 +442,7 @@ export class Registry { this.#installSignalHandlers(config, configuredRegistryPromise); } if (printWelcome) { - this.#printWelcome(config, "serverful"); + this.#printWelcome(config, "envoy"); } } @@ -434,6 +554,15 @@ export class Registry { // already logged any serve-side error. await runtimeServePromise.catch(() => undefined); } + const runtimeHttpServerPromise = this.#runtimeHttpServerPromise; + if (runtimeHttpServerPromise !== undefined) { + try { + const server = await runtimeHttpServerPromise; + server.closeServer?.(); + } catch (err) { + logger().warn({ err }, "runtime HTTP server shutdown errored"); + } + } }; await Promise.race([ drain(), @@ -454,12 +583,8 @@ export class Registry { this.#signalHandlers = {}; } - public startEnvoy() { - this.#startEnvoy(this.parseConfig(), true); - } - /** - * Starts the actor envoy for standalone server deployments. + * Starts the actor envoy. * * @example * ```ts @@ -467,14 +592,110 @@ export class Registry { * registry.start(); * ``` */ - public start() { - const config = this.parseConfig(); - this.#startEnvoy(config, true); + public start(opts?: StartOpts): void { + const parsed = StartOptsSchema.parse(opts); + const config = this.#parseConfigWithEntrypoint({ + kind: "envoy", + envoy: parsed.envoy, + }); + this.#claimEntrypoint("start"); + this.#startPersistentRuntime(config, true); + } + + /** + * Starts a local server for serverless deployments. + */ + public listen(opts: ListenOpts): void { + const parsed = ListenOptsSchema.parse(opts); + const dev = this.#resolveListenDev(parsed); + const basePath = normalizeBasePath(parsed.path); + const config = this.#parseConfigWithEntrypoint({ + kind: "listen", + startEngine: dev?.startEngine, + devServerless: dev?.url + ? { + url: dev.url, + drainTimeout: dev.drainTimeout, + requestTimeout: dev.requestTimeout, + } + : undefined, + serverless: { + basePath, + }, + staticDir: staticDirFromConfig(parsed.static), + httpBasePath: basePath, + httpPort: parsed.port, + httpHost: parsed.host, + }); + + this.#claimEntrypoint("listen"); + const configuredRegistryPromise = buildConfiguredRegistry(config); + this.#runtimeServerlessPromise = configuredRegistryPromise; + const handler = this.#createServerlessHandler( + config, + configuredRegistryPromise, + "listen", + ); + this.#runtimeHttpServerPromise = this.#startHttpServer(config, handler); + this.#installSignalHandlers(config, configuredRegistryPromise); + } + + async #startHttpServer( + config: RegistryConfig, + handler: FetchHandler, + ): Promise<{ closeServer?: () => void }> { + const app = new Hono(); + const route = routeForBasePath(config.httpBasePath ?? "/api/rivet"); + app.all(route, (c) => handler(c.req.raw)); + if (config.staticDir) { + const runtime = + "Deno" in globalThis + ? "deno" + : "Bun" in globalThis + ? "bun" + : "node"; + const serveStatic = await loadRuntimeServeStatic(runtime); + app.use("/*", serveStatic({ root: config.staticDir })); + app.get("*", serveStatic({ root: config.staticDir, path: "/index.html" })); + } + return await crossPlatformServe(config, config.httpPort ?? 6421, app); + } + + #resolveFetchHandlerDev( + dev: z.infer["dev"], + ): (FetchHandlerDevConfig & { startEngine: boolean }) | undefined { + if (dev === undefined || dev === false || !isDevelopmentEnv()) { + return undefined; + } + if (typeof dev === "string") { + return { url: dev, startEngine: true }; + } + return { url: dev.url, startEngine: dev.startEngine ?? true }; + } + + #resolveListenDev( + opts: z.infer, + ): (ListenDevConfig & { url: string; startEngine: boolean }) | undefined { + if (opts.dev === false || !isDevelopmentEnv()) { + return undefined; + } + const inferred = opts.dev === undefined; + if (inferred) { + logger().info("RivetKit dev mode enabled via NODE_ENV=development"); + } + const defaultUrl = `http://localhost:${opts.port}${normalizeBasePath(opts.path)}`; + if (opts.dev === undefined || opts.dev === true) { + return { url: defaultUrl, startEngine: true }; + } + return { + url: opts.dev.url ?? defaultUrl, + startEngine: opts.dev.startEngine ?? true, + }; } #printWelcome( config: RegistryConfig, - kind: "serverless" | "serverful", + kind: "serverless" | "envoy" | "listen", ): void { if (config.noWelcome || this.#welcomePrinted) return; this.#welcomePrinted = true; @@ -485,8 +706,12 @@ export class Registry { }; console.log(); - console.log( - ` RivetKit ${VERSION} (Engine - ${kind === "serverless" ? "Serverless" : "Serverful"})`, + console.log(` RivetKit ${VERSION} (${config.mode})`); + logLine("Entrypoint", kind); + logLine("Mode", config.mode); + logLine( + "Engine", + config.startEngine ? "managed local" : "external", ); if (config.namespace !== "default") { @@ -502,6 +727,11 @@ export class Registry { if (kind === "serverless" && config.publicEndpoint) { logLine("Client", config.publicEndpoint); } + if (kind === "listen") { + logLine("HTTP", `${config.httpHost ?? "0.0.0.0"}:${config.httpPort}`); + logLine("Path", config.httpBasePath ?? "/api/rivet"); + logLine("Static", config.staticDir ?? "disabled"); + } logLine("Actors", Object.keys(config.use).length.toString()); console.log(); @@ -516,14 +746,6 @@ function isServerlessStartRequest(request: Request, basePath: string): boolean { return parsed.pathname === `${normalizedBase}/start`; } -function isServerlessMetadataRequest(request: Request, basePath: string): boolean { - if (request.method !== "GET") return false; - const parsed = new URL(request.url); - const normalizedBase = - basePath === "/" ? "" : `/${basePath.replace(/^\/+|\/+$/g, "")}`; - return parsed.pathname === `${normalizedBase}/metadata`; -} - export function setup( input: RegistryConfigInput, ): Registry { diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/native.ts b/rivetkit-typescript/packages/rivetkit/src/registry/native.ts index 5c6a476daf..62cbfb34c8 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/native.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/native.ts @@ -221,6 +221,19 @@ export function normalizeRuntimeConfigForKind( return config; } + if (config.startEngine) { + throw new RivetError( + "config", + "managed_engine_requires_native_runtime", + "Managed local engine startup requires the native runtime.", + { + public: true, + statusCode: 400, + metadata: { runtime: "wasm" }, + }, + ); + } + if (sqliteBackendForConfig(config) === "local") { throw new RivetError( "config", @@ -4655,11 +4668,25 @@ export async function buildServeConfig( } const serveConfig: RuntimeServeConfig = { - version: config.envoy.version, + mode: config.mode, + version: config.version, endpoint: config.endpoint, token: config.token, namespace: config.namespace, - poolName: config.envoy.poolName, + poolName: config.pool, + devServerlessUrl: + typeof config.devServerless === "object" + ? config.devServerless.url + : undefined, + devServerlessManual: config.devServerless === "manual", + devServerlessDrainTimeout: + typeof config.devServerless === "object" + ? config.devServerless.drainTimeout + : undefined, + devServerlessRequestTimeout: + typeof config.devServerless === "object" + ? config.devServerless.requestTimeout + : undefined, handleInspectorHttpInRuntime: true, serverlessBasePath: config.serverless.basePath, serverlessPackageVersion: VERSION, diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/runtime.ts b/rivetkit-typescript/packages/rivetkit/src/registry/runtime.ts index 46cfa4bbfe..b109aa5847 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/runtime.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/runtime.ts @@ -228,12 +228,17 @@ export interface RuntimeActorConfig { } export interface RuntimeServeConfig { + mode: "envoy" | "serverless"; version: number; endpoint: string; token?: string; namespace: string; poolName: string; engineBinaryPath?: string; + devServerlessUrl?: string; + devServerlessManual: boolean; + devServerlessDrainTimeout?: number; + devServerlessRequestTimeout?: number; handleInspectorHttpInRuntime?: boolean; inspectorTestToken?: string; serverlessBasePath?: string; @@ -551,11 +556,25 @@ export async function buildServeConfig( } const serveConfig: RuntimeServeConfig = { - version: config.envoy.version, + mode: config.mode, + version: config.version, endpoint: config.endpoint, token: config.token, namespace: config.namespace, - poolName: config.envoy.poolName, + poolName: config.pool, + devServerlessUrl: + typeof config.devServerless === "object" + ? config.devServerless.url + : undefined, + devServerlessManual: config.devServerless === "manual", + devServerlessDrainTimeout: + typeof config.devServerless === "object" + ? config.devServerless.drainTimeout + : undefined, + devServerlessRequestTimeout: + typeof config.devServerless === "object" + ? config.devServerless.requestTimeout + : undefined, handleInspectorHttpInRuntime: true, serverlessBasePath: config.serverless.basePath, serverlessPackageVersion: version, diff --git a/rivetkit-typescript/packages/rivetkit/src/serverless/configure.ts b/rivetkit-typescript/packages/rivetkit/src/serverless/configure.ts deleted file mode 100644 index 5d927a4b49..0000000000 --- a/rivetkit-typescript/packages/rivetkit/src/serverless/configure.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { convertRegistryConfigToClientConfig } from "@/client/config"; -import { - getDatacenters, - updateRunnerConfig, -} from "@/engine-client/api-endpoints"; -import { stringifyError } from "@/common/utils"; -import type { RegistryConfig } from "@/registry/config"; -import { logger } from "@/registry/log"; - -const DEFAULT_CONFIGURE_TIMEOUT_MS = 60_000; -const CONFIGURE_RETRY_DELAY_MS = 1_000; - -function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -function configureTimeoutMs() { - const value = process.env.RIVET_SERVERLESS_CONFIGURE_TIMEOUT_MS; - if (value === undefined || value === "") return DEFAULT_CONFIGURE_TIMEOUT_MS; - - const parsed = Number(value); - if (!Number.isFinite(parsed) || parsed < 0) { - throw new Error("RIVET_SERVERLESS_CONFIGURE_TIMEOUT_MS must be a finite non-negative number"); - } - - return parsed; -} - -export async function configureServerlessPool( - config: RegistryConfig, -): Promise { - logger().debug({ msg: "configuring serverless pool" }); - - const startedAt = Date.now(); - const timeoutMs = configureTimeoutMs(); - let attempts = 0; - let lastError: unknown; - - while (Date.now() - startedAt <= timeoutMs) { - attempts += 1; - try { - if (!config.namespace) { - throw new Error("namespace is required for serverless configuration"); - } - if (!config.endpoint) { - throw new Error("endpoint is required for serverless configuration"); - } - if (!config.configurePool) { - throw new Error("configurePool is required for serverless configuration"); - } - - const customConfig = config.configurePool; - const clientConfig = convertRegistryConfigToClientConfig(config); - const dcsRes = await getDatacenters(clientConfig); - const poolName = customConfig.name ?? "default"; - const serverlessToken = config.token ?? config.publicToken; - const headers = { - ...(serverlessToken ? { "x-rivet-token": serverlessToken } : {}), - ...(customConfig.headers ?? {}), - }; - const serverlessConfig = { - serverless: { - url: customConfig.url, - headers, - request_lifespan: customConfig.requestLifespan ?? 60 * 60, - drain_grace_period: customConfig.drainGracePeriod, - metadata_poll_interval: - customConfig.metadataPollInterval ?? 1000, - max_runners: 100_000, - min_runners: 0, - runners_margin: 0, - slots_per_runner: 1, - }, - metadata: customConfig.metadata ?? {}, - drain_on_version_upgrade: - customConfig.drainOnVersionUpgrade ?? true, - }; - - await updateRunnerConfig(clientConfig, poolName, { - datacenters: Object.fromEntries( - dcsRes.datacenters.map((dc) => [dc.name, serverlessConfig]), - ), - }); - - logger().info({ - msg: "serverless pool configured successfully", - poolName, - namespace: config.namespace, - attempts, - }); - return; - } catch (error) { - lastError = error; - logger().warn({ - msg: "serverless pool configuration attempt failed", - attempts, - error: stringifyError(error), - }); - await sleep(CONFIGURE_RETRY_DELAY_MS); - } - } - - logger().error({ - msg: "failed to configure serverless pool, validate endpoint is configured correctly then restart this process", - attempts, - error: stringifyError(lastError), - }); - throw lastError; -} diff --git a/rivetkit-typescript/packages/rivetkit/src/utils/env-vars.ts b/rivetkit-typescript/packages/rivetkit/src/utils/env-vars.ts index d3e6d5f50d..ac5664121d 100644 --- a/rivetkit-typescript/packages/rivetkit/src/utils/env-vars.ts +++ b/rivetkit-typescript/packages/rivetkit/src/utils/env-vars.ts @@ -5,6 +5,28 @@ import { getEnvUniversal } from "@/utils"; +function parseNumberEnv(name: string): number | undefined { + const value = getEnvUniversal(name); + if (value === undefined || value === "") return undefined; + return Number(value); +} + +function collectEnvKeys(): string[] { + if (typeof Deno !== "undefined") { + try { + return Object.keys(Deno.env.toObject()); + } catch { + return []; + } + } + + if (typeof process !== "undefined") { + return Object.keys(process.env); + } + + return []; +} + // Rivet configuration export const getRivetEngine = (): string | undefined => getEnvUniversal("RIVET_ENGINE"); @@ -16,20 +38,8 @@ export const getRivetNamespace = (): string | undefined => getEnvUniversal("RIVET_NAMESPACE"); export const getRivetPool = (): string | undefined => getEnvUniversal("RIVET_POOL"); -export const getRivetTotalSlots = (): number | undefined => { - const value = getEnvUniversal("RIVET_TOTAL_SLOTS"); - return value !== undefined ? parseInt(value, 10) : undefined; -}; -export const getRivetRunEngine = (): boolean => - getEnvUniversal("RIVET_RUN_ENGINE") === "1"; -export const getRivetRunEngineVersion = (): string | undefined => - getEnvUniversal("RIVET_RUN_ENGINE_VERSION"); -export const getRivetEnvoyKind = (): string | undefined => - getEnvUniversal("RIVET_ENVOY_KIND"); -export const getRivetEnvoyVersion = (): number | undefined => { - const value = getEnvUniversal("RIVET_ENVOY_VERSION"); - return value !== undefined ? parseInt(value, 10) : undefined; -}; +export const getRivetVersion = (): number | undefined => + parseNumberEnv("RIVET_VERSION"); export const getRivetPublicEndpoint = (): string | undefined => getEnvUniversal("RIVET_PUBLIC_ENDPOINT"); export const getRivetPublicToken = (): string | undefined => @@ -64,7 +74,68 @@ export const getLogHeaders = (): boolean => export const getNodeEnv = (): string | undefined => getEnvUniversal("NODE_ENV"); export const getNextPhase = (): string | undefined => getEnvUniversal("NEXT_PHASE"); -export const isDev = (): boolean => getNodeEnv() !== "production"; +export const isDev = (): boolean => getNodeEnv() === "development"; + +export function invalidRivetEnvironmentVariables(): Array<{ + name: string; + message: string; +}> { + const invalid = new Map(); + + for (const name of collectEnvKeys()) { + if (name === "RIVET_RUNNER" || name.startsWith("RIVET_RUNNER_")) { + invalid.set( + name, + `${name} has been removed. Use registry.start() or registry.fetchHandler() with RIVET_POOL and RIVET_VERSION instead.`, + ); + } + } + + const renamed: Record = { + RIVET_ENVOY_VERSION: "RIVET_VERSION", + RIVET_POOL_NAME: "RIVET_POOL", + }; + for (const [oldName, newName] of Object.entries(renamed)) { + if (getEnvUniversal(oldName) !== undefined) { + invalid.set( + oldName, + `${oldName} has been removed. Use ${newName} instead.`, + ); + } + } + + if (getEnvUniversal("RIVET_MODE") !== undefined) { + invalid.set( + "RIVET_MODE", + "RIVET_MODE has been removed. Runtime mode is selected by registry.start() or registry.fetchHandler().", + ); + } + if (getEnvUniversal("RIVET_ENVOY_KIND") !== undefined) { + invalid.set( + "RIVET_ENVOY_KIND", + "RIVET_ENVOY_KIND has been removed. Runtime mode is selected by registry.start() or registry.fetchHandler().", + ); + } + if (getEnvUniversal("RIVET_ENVOY_KEY") !== undefined) { + invalid.set( + "RIVET_ENVOY_KEY", + "RIVET_ENVOY_KEY has been removed. Envoy keys are managed by RivetKit.", + ); + } + + for (const name of [ + "RIVET_ENGINE", + "RIVET_RUN_ENGINE", + "RIVET_RUN_ENGINE_VERSION", + "RIVET_TOTAL_SLOTS", + ]) { + if (getEnvUniversal(name) !== undefined) { + invalid.set(name, `${name} has been removed from RivetKit setup.`); + } + } + + return Array.from(invalid, ([name, message]) => ({ name, message })); +} // Experimental /** diff --git a/rivetkit-typescript/packages/rivetkit/tests/driver/serverless-handler.test.ts b/rivetkit-typescript/packages/rivetkit/tests/driver/serverless-handler.test.ts index 093de6bd34..82dec95ce2 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/driver/serverless-handler.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/driver/serverless-handler.test.ts @@ -124,13 +124,14 @@ describeDriverMatrix( endpoint: "http://127.0.0.1:6420", token: TOKEN, }); + const handler = registry.fetchHandler({ path: "/api/rivet" }); - const root = await registry.handler( + const root = await handler( new Request("http://runner.test/api/rivet/"), ); expect(await expectText(root)).toContain("RivetKit server"); - const health = await registry.handler( + const health = await handler( new Request("http://runner.test/api/rivet/health"), ); expect(health.status).toBe(200); @@ -139,7 +140,7 @@ describeDriverMatrix( runtime: "rivetkit", }); - const metadata = await registry.handler( + const metadata = await handler( new Request("http://runner.test/api/rivet/metadata"), ); expect(metadata.status).toBe(200); @@ -160,8 +161,9 @@ describeDriverMatrix( endpoint: "http://127.0.0.1:6420", token: TOKEN, }); + const handler = registry.fetchHandler({ path: "/api/rivet" }); - const response = await registry.handler( + const response = await handler( new Request("http://runner.test/api/rivet/start", { method: "POST", body: new Uint8Array(), @@ -199,11 +201,12 @@ describeDriverMatrix( endpoint: engine.endpoint, token: TOKEN, namespace, - envoy: { poolName }, + pool: poolName, }); + const handler = registry.fetchHandler({ path: "/api/rivet" }); const abort = new AbortController(); - const response = await registry.handler( + const response = await handler( new Request("http://runner.test/api/rivet/start", { method: "POST", headers: serverlessHeaders({ diff --git a/rivetkit-typescript/packages/rivetkit/tests/fixtures/driver-test-suite-runtime.ts b/rivetkit-typescript/packages/rivetkit/tests/fixtures/driver-test-suite-runtime.ts index 35f8b7b4dc..08729062d4 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/fixtures/driver-test-suite-runtime.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/fixtures/driver-test-suite-runtime.ts @@ -5,8 +5,6 @@ import { buildConfiguredRegistry } from "../../src/registry/native"; const registryPath = process.env.RIVETKIT_DRIVER_REGISTRY_PATH; const endpoint = process.env.RIVETKIT_TEST_ENDPOINT; -const token = process.env.RIVET_TOKEN ?? "dev"; -const namespace = process.env.RIVET_NAMESPACE ?? "default"; const poolName = process.env.RIVETKIT_TEST_POOL_NAME ?? "default"; const sqliteBackend = process.env.RIVETKIT_TEST_SQLITE_BACKEND ?? "local"; @@ -36,14 +34,8 @@ registry.config.test = { sqliteBackend, }; registry.config.runtime = "native"; -registry.config.startEngine = false; registry.config.endpoint = endpoint; -registry.config.token = token; -registry.config.namespace = namespace; -registry.config.envoy = { - ...registry.config.envoy, - poolName, -}; +registry.config.pool = poolName; const { registry: nativeRegistry, serveConfig } = await buildConfiguredRegistry( registry.parseConfig(), diff --git a/rivetkit-typescript/packages/rivetkit/tests/fixtures/driver-test-suite-wasm-runtime.ts b/rivetkit-typescript/packages/rivetkit/tests/fixtures/driver-test-suite-wasm-runtime.ts index 8a41ee8e75..2ccbf9f0c7 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/fixtures/driver-test-suite-wasm-runtime.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/fixtures/driver-test-suite-wasm-runtime.ts @@ -6,8 +6,6 @@ import { buildConfiguredRegistry } from "../../src/registry/native"; const registryPath = process.env.RIVETKIT_DRIVER_REGISTRY_PATH; const endpoint = process.env.RIVETKIT_TEST_ENDPOINT; -const token = process.env.RIVET_TOKEN ?? "dev"; -const namespace = process.env.RIVET_NAMESPACE ?? "default"; const poolName = process.env.RIVETKIT_TEST_POOL_NAME ?? "default"; const sqliteBackend = process.env.RIVETKIT_TEST_SQLITE_BACKEND ?? "remote"; const wasmPath = @@ -47,14 +45,8 @@ registry.config.wasm = { ...registry.config.wasm, initInput: readFileSync(wasmPath), }; -registry.config.startEngine = false; registry.config.endpoint = endpoint; -registry.config.token = token; -registry.config.namespace = namespace; -registry.config.envoy = { - ...registry.config.envoy, - poolName, -}; +registry.config.pool = poolName; const { runtime, diff --git a/rivetkit-typescript/packages/rivetkit/tests/fixtures/napi-runtime-server.ts b/rivetkit-typescript/packages/rivetkit/tests/fixtures/napi-runtime-server.ts index c6de0e76da..114b9964c8 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/fixtures/napi-runtime-server.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/fixtures/napi-runtime-server.ts @@ -147,11 +147,7 @@ const registry = setup({ integrationActor, }, endpoint, - namespace: process.env.RIVET_NAMESPACE ?? "default", - token: process.env.RIVET_TOKEN ?? "dev", - envoy: { - poolName: process.env.RIVETKIT_TEST_POOL_NAME ?? "default", - }, + pool: process.env.RIVETKIT_TEST_POOL_NAME ?? "default", }); const { registry: nativeRegistry, serveConfig } = await buildNativeRegistry( diff --git a/rivetkit-typescript/packages/rivetkit/tests/napi-runtime-integration.test.ts b/rivetkit-typescript/packages/rivetkit/tests/napi-runtime-integration.test.ts index 93df0e7529..b3d8d3598a 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/napi-runtime-integration.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/napi-runtime-integration.test.ts @@ -296,9 +296,12 @@ describe.sequential("native NAPI runtime integration", () => { const handle = await waitForActorReady( () => - client.integrationActor.create([ - `napi-runtime-${crypto.randomUUID()}`, - ]), + client.integrationActor.create( + [`napi-runtime-${crypto.randomUUID()}`], + { + params: { userId: "integration-test" }, + }, + ), 30_000, ); const actorId = await handle.resolve(); diff --git a/rivetkit-typescript/packages/rivetkit/tests/native-runtime-errors.test.ts b/rivetkit-typescript/packages/rivetkit/tests/native-runtime-errors.test.ts index ddd30f50e8..0e78344967 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/native-runtime-errors.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/native-runtime-errors.test.ts @@ -82,7 +82,7 @@ describe("native runtime config errors", () => { use: { test: testActor, }, - startEngine: false, + endpoint: "http://127.0.0.1:6420", }); const config = registry.parseConfig(); config.endpoint = undefined as never; diff --git a/rivetkit-typescript/packages/rivetkit/tests/platforms/deno.test.ts b/rivetkit-typescript/packages/rivetkit/tests/platforms/deno.test.ts index 1d09681e54..cd243b7dbe 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/platforms/deno.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/platforms/deno.test.ts @@ -73,6 +73,7 @@ const registry = createRegistry({ token: "${token}", runnerName: "${runnerName}", }); +const handler = registry.fetchHandler({ path: "/api/rivet" }); Deno.serve( { @@ -89,7 +90,7 @@ Deno.serve( return new Response("ok"); } - return await registry.handler(request); + return await handler(request); }, ); `, diff --git a/rivetkit-typescript/packages/rivetkit/tests/platforms/shared-platform-harness.ts b/rivetkit-typescript/packages/rivetkit/tests/platforms/shared-platform-harness.ts index b53c1a57d6..694afb71e6 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/platforms/shared-platform-harness.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/platforms/shared-platform-harness.ts @@ -231,10 +231,7 @@ export function createRegistry(config: RegistryConfig) { \t\tendpoint: config.endpoint, \t\tnamespace: config.namespace, \t\ttoken: config.token, -\t\tenvoy: { -\t\t\tpoolName: config.runnerName, -\t\t}, -\t\t...(config.serverless ? { serverless: config.serverless } : {}), +\t\tpool: config.runnerName, \t\tnoWelcome: true, \t}); } diff --git a/rivetkit-typescript/packages/rivetkit/tests/platforms/supabase-functions.test.ts b/rivetkit-typescript/packages/rivetkit/tests/platforms/supabase-functions.test.ts index d5bcdc6517..08c76830fd 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/platforms/supabase-functions.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/platforms/supabase-functions.test.ts @@ -317,10 +317,10 @@ const registry = createRegistry({ namespace: "${namespace}", token: "${token}", runnerName: "${runnerName}", - serverless: { - basePath: SERVERLESS_BASE_PATH, - publicEndpoint: "${publicEndpoint}", - }, +}); +const handler = registry.fetchHandler({ + path: SERVERLESS_BASE_PATH, + publicEndpoint: "${publicEndpoint}", }); Deno.serve(async (request) => { @@ -330,7 +330,7 @@ Deno.serve(async (request) => { return new Response("ok"); } - return await registry.handler(request); + return await handler(request); }); `, ); diff --git a/rivetkit-typescript/packages/rivetkit/tests/registry-constructor.test.ts b/rivetkit-typescript/packages/rivetkit/tests/registry-constructor.test.ts index a86cec2ee6..a41845e7b6 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/registry-constructor.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/registry-constructor.test.ts @@ -41,21 +41,15 @@ describe("Registry constructor", () => { use: { test: testActor, }, - startEngine: false, endpoint: "http://127.0.0.1:6642", token: "dev", namespace: "before-build", - envoy: { - poolName: "before-build-pool", - }, + pool: "before-build-pool", }); registry.config.namespace = "after-build"; registry.config.endpoint = "http://127.0.0.1:7755"; - registry.config.envoy = { - ...registry.config.envoy, - poolName: "after-build-pool", - }; + registry.config.pool = "after-build-pool"; const { serveConfig } = await buildNativeRegistry(registry.parseConfig()); @@ -64,4 +58,29 @@ describe("Registry constructor", () => { expect(serveConfig.namespace).toBe("after-build"); expect(serveConfig.poolName).toBe("after-build-pool"); }); + + test("rejects multiple runtime entrypoints on one registry", () => { + const registry = setup({ + use: { + test: testActor, + }, + endpoint: "http://127.0.0.1:6642", + token: "dev", + namespace: "entrypoint-guard", + pool: "entrypoint-guard", + version: 1, + noWelcome: true, + }); + + registry.fetchHandler({ path: "/api/rivet" }); + + expect(() => registry.start()).toThrow( + /registry\.start\(\) cannot be used after registry\.fetchHandler\(\)/, + ); + expect(() => + registry.listen({ port: 3000, path: "/api/rivet" }), + ).toThrow( + /registry\.listen\(\) cannot be used after registry\.fetchHandler\(\)/, + ); + }); }); diff --git a/rivetkit-typescript/packages/rivetkit/tests/runtime-mode-config.test.ts b/rivetkit-typescript/packages/rivetkit/tests/runtime-mode-config.test.ts new file mode 100644 index 0000000000..accb345a44 --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/tests/runtime-mode-config.test.ts @@ -0,0 +1,231 @@ +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { actor } from "@/actor/mod"; +import { RegistryConfigSchema } from "@/registry/config"; + +const ENV_KEYS = [ + "NODE_ENV", + "RIVET_MODE", + "RIVET_ENDPOINT", + "RIVET_TOKEN", + "RIVET_NAMESPACE", + "RIVET_POOL", + "RIVET_VERSION", + "RIVET_ENVOY_KEY", + "RIVET_RUNNER", + "RIVET_RUNNER_FOO", + "RIVET_ENVOY_VERSION", + "RIVET_ENVOY_KIND", + "RIVET_POOL_NAME", + "RIVET_RUN_ENGINE", + "RIVET_RUN_ENGINE_VERSION", + "RIVET_TOTAL_SLOTS", +] as const; + +const savedEnv = new Map(); + +const testActor = actor({ + state: {}, + actions: {}, +}); + +function parseConfig(input: Record = {}) { + return RegistryConfigSchema.parse({ + use: { test: testActor }, + ...input, + }); +} + +describe("runtime entrypoint config", () => { + beforeEach(() => { + for (const key of ENV_KEYS) { + savedEnv.set(key, process.env[key]); + delete process.env[key]; + } + }); + + afterEach(() => { + for (const key of ENV_KEYS) { + const value = savedEnv.get(key); + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + savedEnv.clear(); + }); + + test("defaults to managed envoy outside production", () => { + const config = parseConfig(); + + expect(config.mode).toBe("envoy"); + expect(config.modeSource).toBe("default"); + expect(config.startEngine).toBe(true); + expect(config.endpoint).toBe("http://127.0.0.1:6420"); + expect(config.pool).toBe("default"); + expect(config.version).toBe(1); + }); + + test("defaults to external envoy in production", () => { + process.env.NODE_ENV = "production"; + process.env.RIVET_VERSION = "7"; + + const config = parseConfig({ + engine: { endpoint: "https://example.com" }, + }); + + expect(config.mode).toBe("envoy"); + expect(config.modeSource).toBe("default"); + expect(config.startEngine).toBe(false); + expect(config.endpoint).toBe("https://example.com/"); + expect(config.version).toBe(7); + }); + + test("requires production version", () => { + process.env.NODE_ENV = "production"; + + expect(() => parseConfig()).toThrow(/RIVET_VERSION is required/); + }); + + test("parses fetch handler entrypoint config", () => { + process.env.NODE_ENV = "development"; + + const config = parseConfig({ + entrypoint: { + kind: "serverless", + startEngine: true, + devServerless: { url: "http://127.0.0.1:3000/api/rivet" }, + serverless: { + basePath: "/api/rivet", + maxStartPayloadBytes: 4096, + }, + }, + }); + + expect(config.mode).toBe("serverless"); + expect(config.modeSource).toBe("entrypoint"); + expect(config.startEngine).toBe(true); + expect(config.endpoint).toBe("http://127.0.0.1:6420"); + expect(config.devServerless).toEqual({ + url: "http://127.0.0.1:3000/api/rivet", + }); + expect(config.serverless.basePath).toBe("/api/rivet"); + expect(config.serverless.maxStartPayloadBytes).toBe(4096); + }); + + test("production ignores serverless dev config", () => { + process.env.NODE_ENV = "production"; + process.env.RIVET_VERSION = "7"; + process.env.RIVET_ENDPOINT = "https://example.com"; + + const config = parseConfig({ + entrypoint: { + kind: "serverless", + startEngine: true, + devServerless: { url: "http://127.0.0.1:3000/api/rivet" }, + }, + }); + + expect(config.mode).toBe("serverless"); + expect(config.startEngine).toBe(false); + expect(config.devServerless).toBeUndefined(); + }); + + test("parses listen entrypoint defaults", () => { + process.env.NODE_ENV = "development"; + + const config = parseConfig({ + entrypoint: { + kind: "listen", + startEngine: true, + devServerless: { url: "http://localhost:3000/api/rivet" }, + httpBasePath: "/api/rivet", + httpPort: 3000, + staticDir: "public", + }, + }); + + expect(config.mode).toBe("serverless"); + expect(config.startEngine).toBe(true); + expect(config.httpBasePath).toBe("/api/rivet"); + expect(config.httpPort).toBe(3000); + expect(config.staticDir).toBe("public"); + }); + + test("parses env-only shared runtime values", () => { + process.env.RIVET_ENDPOINT = "https://ns:token@example.com"; + process.env.RIVET_POOL = "workers"; + process.env.RIVET_VERSION = "9"; + + const config = parseConfig({ + entrypoint: { + kind: "envoy", + }, + }); + + expect(config.mode).toBe("envoy"); + expect(config.modeSource).toBe("entrypoint"); + expect(config.endpoint).toBe("https://example.com/"); + expect(config.namespace).toBe("ns"); + expect(config.token).toBe("token"); + expect(config.pool).toBe("workers"); + expect(config.version).toBe(9); + }); + + test.each([ + ["RIVET_ENDPOINT", "engine.endpoint", { engine: { endpoint: "https://example.com" } }], + ["RIVET_TOKEN", "token", { token: "manual" }], + ["RIVET_NAMESPACE", "namespace", { namespace: "manual" }], + ["RIVET_POOL", "pool", { pool: "manual" }], + ["RIVET_VERSION", "version", { version: 1 }], + ])("rejects %s with manual %s", (envName, _field, input) => { + process.env[envName] = envName === "RIVET_VERSION" ? "2" : "env"; + + expect(() => parseConfig(input)).toThrow( + new RegExp(`${envName}.*cannot both be set`), + ); + }); + + test("rejects manual start envoy key", () => { + expect(() => + parseConfig({ + entrypoint: { + kind: "envoy", + envoy: { key: "manual" }, + }, + }), + ).toThrow(/envoy\.key has been removed/); + }); + + test.each([ + ["RIVET_RUNNER", "RIVET_POOL"], + ["RIVET_RUNNER_FOO", "RIVET_POOL"], + ["RIVET_ENVOY_VERSION", "RIVET_VERSION"], + ["RIVET_ENVOY_KEY", "managed by RivetKit"], + ["RIVET_ENVOY_KIND", "removed"], + ["RIVET_MODE", "removed"], + ["RIVET_POOL_NAME", "RIVET_POOL"], + ["RIVET_RUN_ENGINE", "removed"], + ["RIVET_RUN_ENGINE_VERSION", "removed"], + ["RIVET_TOTAL_SLOTS", "removed"], + ])("rejects legacy env %s", (envName, expected) => { + process.env[envName] = "legacy"; + + expect(() => parseConfig()).toThrow(new RegExp(expected)); + }); + + test.each([ + [{ mode: "serverless" }, /mode has been removed/], + [{ startEngine: false }, /startEngine has been removed/], + [{ staticDir: "public" }, /staticDir has been removed/], + [{ httpBasePath: "/api/rivet" }, /httpBasePath has been removed/], + [{ httpPort: 3000 }, /httpPort has been removed/], + [{ httpHost: "127.0.0.1" }, /httpHost has been removed/], + [{ devServerless: { url: "http://127.0.0.1" } }, /devServerless has been removed/], + [{ serverless: { basePath: "/api/rivet" } }, /serverless has been removed/], + [{ envoy: { key: "old" } }, /envoy has been removed/], + [{ configurePool: { url: "http://127.0.0.1" } }, /configurePool/], + ])("rejects removed setup config", (input, expected) => { + expect(() => parseConfig(input)).toThrow(expected); + }); +}); diff --git a/rivetkit-typescript/packages/rivetkit/tests/runtime-parity.test.ts b/rivetkit-typescript/packages/rivetkit/tests/runtime-parity.test.ts index 12e508e1d3..b52e9702a5 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/runtime-parity.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/runtime-parity.test.ts @@ -312,7 +312,6 @@ function registryConfig(definition: ReturnType): RegistryConfig { token: serveConfig.token, namespace: serveConfig.namespace, noWelcome: true, - startEngine: false, test: { enabled: true, sqliteBackend: "remote", @@ -412,6 +411,51 @@ async function invokePromotedStatus( } } +async function invokePlainInternalError( + runtimeCase: RuntimeCase, +): Promise { + const { runtime, scenario } = runtimeCase; + const definition = actor({ + state: {}, + actions: { + explode: async () => { + throw new Error("plain bridge failure"); + }, + }, + }); + const factory = buildNativeFactory( + runtime, + registryConfig(definition), + definition, + ) as unknown as FakeActorFactory; + const ctx = new FakeActorContext(scenario); + const stateBytes = await factory.callbacks.createState?.(null, { + ctx, + input: encodeValue(null), + }); + ctx.stateBytes = Buffer.from(stateBytes ?? encodeValue({})); + + try { + await factory.callbacks.actions.explode(null, { + ctx, + conn: null, + name: "explode", + args: encodeValue([]), + cancelToken: new FakeCancellationToken(), + }); + throw new Error("expected explode action to fail"); + } catch (error) { + if (!(error instanceof Error)) { + throw error; + } + const decoded = decodeBridgeRivetError(error.message); + if (!decoded) { + throw error; + } + return decoded; + } +} + describe("CoreRuntime NAPI and wasm parity", () => { test.each([ "napi", @@ -437,4 +481,19 @@ describe("CoreRuntime NAPI and wasm parity", () => { expect(nativeError).toMatchObject(promoted); expect(wasmError).toMatchObject(promoted); }); + + test.each([ + "napi", + "wasm", + ] as const)("%s preserves plain internal callback errors across the bridge", async (kind) => { + const error = await invokePlainInternalError(createRuntimeCase(kind)); + + expect(error).toMatchObject({ + group: "rivetkit", + code: "internal_error", + message: "plain bridge failure", + public: false, + statusCode: 500, + }); + }); }); diff --git a/rivetkit-typescript/packages/rivetkit/tests/runtime-selection.test.ts b/rivetkit-typescript/packages/rivetkit/tests/runtime-selection.test.ts index 56ca3d9476..9b17d90b77 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/runtime-selection.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/runtime-selection.test.ts @@ -19,7 +19,7 @@ const testActor = actor({ function parseConfig(input: Record = {}) { return RegistryConfigSchema.parse({ use: { test: testActor }, - startEngine: false, + endpoint: "http://127.0.0.1:6420", ...input, }); } diff --git a/rivetkit-typescript/packages/rivetkit/tests/wasm-host-smoke.test.ts b/rivetkit-typescript/packages/rivetkit/tests/wasm-host-smoke.test.ts index e8163417da..e30e257d8c 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/wasm-host-smoke.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/wasm-host-smoke.test.ts @@ -428,7 +428,6 @@ function smokeRegistryConfig( token: serveConfig.token, namespace: serveConfig.namespace, noWelcome: true, - startEngine: false, test: { enabled: true, sqliteBackend: "remote", diff --git a/rivetkit-typescript/packages/rivetkit/tests/wasm-runtime.test.ts b/rivetkit-typescript/packages/rivetkit/tests/wasm-runtime.test.ts index a9bf9d42d2..6806e3361e 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/wasm-runtime.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/wasm-runtime.test.ts @@ -272,12 +272,12 @@ describe("WasmCoreRuntime", () => { echo: (_ctx, value: string) => ({ value }), }, }); - const config = RegistryConfigSchema.parse({ - use: { portable: portableActor }, - runtime: "wasm", - sqlite: "remote", - startEngine: false, - }); + const config = RegistryConfigSchema.parse({ + use: { portable: portableActor }, + runtime: "wasm", + sqlite: "remote", + endpoint: "http://127.0.0.1:6420", + }); const runtime = new WasmCoreRuntime(fakeWasmBindings()); const factory = buildNativeFactory( runtime, diff --git a/website/src/content/docs/actors/actions.mdx b/website/src/content/docs/actors/actions.mdx index 0fad4251c8..21cf258aa0 100644 --- a/website/src/content/docs/actors/actions.mdx +++ b/website/src/content/docs/actors/actions.mdx @@ -65,7 +65,7 @@ console.log(result); // The value returned by the action Learn more about [communicating with actors from the frontend](/docs/actors/communicating-between-actors). - + ```typescript server.ts import { actor, setup } from "rivetkit"; @@ -92,7 +92,8 @@ const client = createClient("http://localhost:6420"); const app = new Hono(); // Mount Rivet handler -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); // Use the client to call actions on a request app.get("/foo", async (c) => { diff --git a/website/src/content/docs/actors/index.mdx b/website/src/content/docs/actors/index.mdx index 94a258d2e9..fb3503bd7f 100644 --- a/website/src/content/docs/actors/index.mdx +++ b/website/src/content/docs/actors/index.mdx @@ -48,11 +48,34 @@ import { faNodeJs, faReact, faNextjs } from "@rivet-gg/icons"; ## Minimal Project -### Backend + + +```ts index.ts +import { actor, event, setup } from "rivetkit"; + +const counter = actor({ + state: { count: 0 }, + events: { + count: event(), + }, + actions: { + increment: (c, amount: number) => { + c.state.count += amount; + c.broadcast("count", c.state.count); + return c.state.count; + }, + }, +}); -**index.ts** +export const registry = setup({ + use: { counter }, +}); -```ts +registry.start(); +``` + + +```ts index.ts import { actor, event, setup } from "rivetkit"; const counter = actor({ @@ -73,8 +96,10 @@ export const registry = setup({ use: { counter }, }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` + + ### Client Docs diff --git a/website/src/content/docs/actors/limits.mdx b/website/src/content/docs/actors/limits.mdx index 7a220eecef..e51b62c854 100644 --- a/website/src/content/docs/actors/limits.mdx +++ b/website/src/content/docs/actors/limits.mdx @@ -93,7 +93,7 @@ These limits apply to the [SQLite database](/docs/actors/state#sqlite-database) ### KV Preloading -When an actor starts, the engine can pre-fetch KV data declared in the actor name metadata and deliver it alongside the start command. This removes round-trips to storage during actor startup. RivetKit emits the preload manifest from its own key layout and exposes per-actor overrides via `options`. Operators can still enforce a global cap in the [engine config](/docs/self-hosting/configuration) with `pegboard.preload_max_total_bytes`. In serverless mode, this data is serialized into the `/api/rivet/start` request body along with actor config and protocol metadata, so the accepted body size must be larger than the preload budget. RivetKit defaults `serverless.maxStartPayloadBytes` to 16 MiB to leave margin for the default 1 MiB preload budget and larger SQLite startup page preloads. +When an actor starts, the engine can pre-fetch KV data declared in the actor name metadata and deliver it alongside the start command. This removes round-trips to storage during actor startup. RivetKit emits the preload manifest from its own key layout and exposes per-actor overrides via `options`. Operators can still enforce a global cap in the [engine config](/docs/self-hosting/configuration) with `pegboard.preload_max_total_bytes`. In serverless mode, this data is serialized into the `/api/rivet/start` request body along with actor config and protocol metadata, so the accepted body size must be larger than the preload budget. RivetKit defaults `fetchHandler({ maxStartPayloadBytes })` to 16 MiB to leave margin for the default 1 MiB preload budget and larger SQLite startup page preloads. | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| @@ -138,8 +138,8 @@ These timeouts control how actors are shut down when a serverless request reache | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| -| Request lifespan | 3600 seconds (60 min) | — | Total lifespan of a serverless request before drain begins. Configurable via `requestLifespan` in [`configurePool`](/docs/general/registry-configuration). | -| Runner config drain grace period | — | 30 minutes | Time a serverless runner reserves for actors to stop gracefully. Configurable via `drainGracePeriod` in [`configurePool`](/docs/general/registry-configuration). | +| Request lifespan | 3600 seconds (60 min) | — | Total lifespan of a serverless request before drain begins. | +| Runner config drain grace period | — | 30 minutes | Time a serverless runner reserves for actors to stop gracefully. | | Engine serverless drain fallback | — | 10 seconds | Engine-level fallback used when no per-runner config applies. Configurable via [engine config](/docs/self-hosting/configuration) (`pegboard.serverless_drain_grace_period`). | ### Actor Lifecycle diff --git a/website/src/content/docs/actors/postgres.mdx b/website/src/content/docs/actors/postgres.mdx index 6219762324..a2f2e1f27a 100644 --- a/website/src/content/docs/actors/postgres.mdx +++ b/website/src/content/docs/actors/postgres.mdx @@ -108,7 +108,7 @@ export const userActor = actor({ export const registry = setup({ use: { userActor }, }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```typescript client.ts @nocheck @@ -229,7 +229,7 @@ export const userActor = actor({ export const registry = setup({ use: { userActor }, }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```typescript client.ts @nocheck diff --git a/website/src/content/docs/actors/quickstart/backend.mdx b/website/src/content/docs/actors/quickstart/backend.mdx index b247c57664..f0ec5e694b 100644 --- a/website/src/content/docs/actors/quickstart/backend.mdx +++ b/website/src/content/docs/actors/quickstart/backend.mdx @@ -52,7 +52,7 @@ export const registry = setup({ use: { counter }, }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -106,7 +106,7 @@ export const registry = setup({ use: { counter }, }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts client.ts @@ -158,7 +158,7 @@ export const registry = setup({ use: { counter }, }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```tsx Counter.tsx diff --git a/website/src/content/docs/actors/quickstart/react.mdx b/website/src/content/docs/actors/quickstart/react.mdx index cf2c98eec5..aed2d4b1e5 100644 --- a/website/src/content/docs/actors/quickstart/react.mdx +++ b/website/src/content/docs/actors/quickstart/react.mdx @@ -52,7 +52,7 @@ export const registry = setup({ use: { counter }, }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -113,7 +113,7 @@ export const registry = setup({ use: { counter }, }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` diff --git a/website/src/content/docs/actors/request-handler.mdx b/website/src/content/docs/actors/request-handler.mdx index 04a6336eb0..a4ef981b6c 100644 --- a/website/src/content/docs/actors/request-handler.mdx +++ b/website/src/content/docs/actors/request-handler.mdx @@ -109,7 +109,7 @@ export const counter = actor({ }); export const registry = setup({ use: { counter } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```typescript client.ts @@ -145,7 +145,7 @@ export const counter = actor({ }); export const registry = setup({ use: { counter } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```typescript client.ts diff --git a/website/src/content/docs/actors/sandbox.mdx b/website/src/content/docs/actors/sandbox.mdx index 20cdb50e7f..b63ec76dc1 100644 --- a/website/src/content/docs/actors/sandbox.mdx +++ b/website/src/content/docs/actors/sandbox.mdx @@ -15,7 +15,7 @@ The legacy TypeScript sandbox actor and provider exports were removed from ## What to use instead -- For actor hosting, use `Registry.startEnvoy()` and the native `rivetkit-core` +- For actor hosting, use `Registry.start()` and the native `rivetkit-core` path. - If you still need sandbox orchestration immediately, integrate `sandbox-agent` directly in your own application code instead of relying on a diff --git a/website/src/content/docs/actors/sqlite-drizzle.mdx b/website/src/content/docs/actors/sqlite-drizzle.mdx index 40a68df097..3538ea1d54 100644 --- a/website/src/content/docs/actors/sqlite-drizzle.mdx +++ b/website/src/content/docs/actors/sqlite-drizzle.mdx @@ -166,7 +166,7 @@ import { setup } from "rivetkit"; import { todoList } from "./todo-list/index.ts"; export const registry = setup({ use: { todoList } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts client.ts @nocheck diff --git a/website/src/content/docs/actors/sqlite.mdx b/website/src/content/docs/actors/sqlite.mdx index 7a1317db26..71d7ecfe18 100644 --- a/website/src/content/docs/actors/sqlite.mdx +++ b/website/src/content/docs/actors/sqlite.mdx @@ -95,7 +95,7 @@ export const todoList = actor({ }); export const registry = setup({ use: { todoList } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts client.ts @@ -225,7 +225,7 @@ export const todoList = actor({ }); export const registry = setup({ use: { todoList } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts client.ts diff --git a/website/src/content/docs/actors/troubleshooting.mdx b/website/src/content/docs/actors/troubleshooting.mdx index ed684346eb..93a33441c9 100644 --- a/website/src/content/docs/actors/troubleshooting.mdx +++ b/website/src/content/docs/actors/troubleshooting.mdx @@ -51,7 +51,7 @@ No server was available to run your actor. The cause depends on your [runtime mo **Runners**: - You don't have enough runners online. Check your runner list in the dashboard to verify runners are visible and connected. -- Your runners are full. Each runner has a limited number of actor slots (configured via `RIVET_TOTAL_SLOTS`, default: 100,000). Check the dashboard to see if your runners have available capacity and scale up if needed. +- Your runners are full. Each runner has a limited number of actor slots (managed by Rivet Engine). Check the dashboard to see if your runners have available capacity and scale up if needed. ### `runner_no_response` @@ -89,7 +89,7 @@ The connection to your serverless endpoint was terminated before the actor finis Rivet received an unexpected response from your serverless endpoint. This typically means something is intercepting or modifying the request before it reaches your RivetKit handler. Check that: -- Your server routes requests to `registry.start()`, `registry.serve()`, or `registry.handler()` correctly. +- Your server routes requests to `registry.fetchHandler({ path })` or `registry.listen({ port, path })` correctly. - No middleware is modifying the request or response body. ### `internal_error` diff --git a/website/src/content/docs/actors/versions.mdx b/website/src/content/docs/actors/versions.mdx index 5126abc9b6..7b20362613 100644 --- a/website/src/content/docs/actors/versions.mdx +++ b/website/src/content/docs/actors/versions.mdx @@ -6,18 +6,18 @@ skill: true ## How Versions Work -Each runner has a **version number**. When you deploy new code with a new version, Rivet handles the transition automatically: +Each envoy has a **version number**. When you deploy new code with a new version, Rivet handles the transition automatically: -- **New actors go to the newest version**: When allocating actors, Rivet always prefers runners with the highest version number +- **New actors go to the newest version**: When allocating actors, Rivet always prefers envoys with the highest version number - **Multiple versions can coexist**: Old actors continue running on old versions while new actors are created on the new version -- **Drain old actors**: When enabled, a runner connecting with a newer version number will gracefully stop old actors to be rescheduled to the new version +- **Drain old actors**: When enabled, an envoy connecting with a newer version number will gracefully stop old actors to be rescheduled to the new version -Versions are not configured by default. See [Registry Configuration](/docs/connect/registry-configuration) to learn how to configure the runner version. +Versions are not configured by default. See [Registry Configuration](/docs/connect/registry-configuration) to learn how to configure the runtime version. -`RIVET_ENVOY_VERSION` is only needed when self-hosting or using a custom runner. Rivet Compute handles versioning automatically. +`RIVET_VERSION` is only needed when self-hosting or using a custom envoy. Rivet Compute handles versioning automatically. ### Example Scenario @@ -26,12 +26,12 @@ Versions are not configured by default. See [Registry Configuration](/docs/conne -When a new version is deployed, existing actors are immediately drained from the old runner and live migrated to the new version. +When a new version is deployed, existing actors are immediately drained from the old envoy and live migrated to the new version. ```mermaid sequenceDiagram - participant R1 as Runner v1 - participant R2 as Runner v2 + participant R1 as Envoy v1 + participant R2 as Envoy v2 Note over R1: Currently running Note over R2: Deployed @@ -48,8 +48,8 @@ When a new version is deployed, both versions coexist. New actors are created on ```mermaid sequenceDiagram - participant R1 as Runner v1 - participant R2 as Runner v2 + participant R1 as Envoy v1 + participant R2 as Envoy v2 Note over R1: Currently running Note over R2: Deployed @@ -65,12 +65,12 @@ sequenceDiagram ### Setting the Version -Configure the runner version using an environment variable or programmatically: +Configure the runtime version using an environment variable or programmatically: ```bash {{"title": "Environment Variable"}} -RIVET_ENVOY_VERSION=2 +RIVET_VERSION=2 ``` ```typescript {{"title": "Programmatic"}} @@ -80,9 +80,7 @@ const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor }, - envoy: { - version: 2, - }, + version: 2, }); ``` @@ -103,13 +101,13 @@ We recommend injecting a build-time value that increments with every deployment. Generate the version at build time and bake it into the image as an environment variable: ```bash @nocheck -docker build --build-arg RIVET_ENVOY_VERSION=$(date +%s) . +docker build --build-arg RIVET_VERSION=$(date +%s) . ``` ```dockerfile @nocheck FROM node:20-slim -ARG RIVET_ENVOY_VERSION -ENV RIVET_ENVOY_VERSION=$RIVET_ENVOY_VERSION +ARG RIVET_VERSION +ENV RIVET_VERSION=$RIVET_VERSION WORKDIR /app COPY . . RUN npm install && npm run build @@ -129,7 +127,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { env: { - RIVET_ENVOY_VERSION: String(Math.floor(Date.now() / 1000)), + RIVET_VERSION: String(Math.floor(Date.now() / 1000)), }, }; @@ -147,7 +145,7 @@ import { defineConfig } from "vite"; export default defineConfig({ define: { - "process.env.RIVET_ENVOY_VERSION": JSON.stringify( + "process.env.RIVET_VERSION": JSON.stringify( String(Math.floor(Date.now() / 1000)) ), }, @@ -163,17 +161,17 @@ Set the version from your CI pipeline: ```yaml @nocheck # GitHub Actions env: - RIVET_ENVOY_VERSION: ${{ github.run_number }} + RIVET_VERSION: ${{ github.run_number }} ``` ```bash @nocheck # Railway / Render / generic CI -export RIVET_ENVOY_VERSION=$(date +%s) +export RIVET_VERSION=$(date +%s) ``` ```bash @nocheck # Git commit count -export RIVET_ENVOY_VERSION=$(git rev-list --count HEAD) +export RIVET_VERSION=$(git rev-list --count HEAD) ``` @@ -198,9 +196,7 @@ const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor }, - envoy: { - version: BUILD_VERSION, - }, + version: BUILD_VERSION, }); ``` @@ -210,11 +206,11 @@ const registry = setup({ ### Drain on Version Upgrade -The `drainOnVersionUpgrade` option controls whether old actors are stopped when a new version is deployed. This is configured in the Rivet dashboard under your runner configuration. +The `drainOnVersionUpgrade` option controls whether old actors are stopped when a new version is deployed. This is configured in the Rivet dashboard under your envoy configuration. | Value | Behavior | |-------|----------| -| `false` (default in [runner mode](/docs/general/runtime-modes)) | Old actors continue running. New actors go to new version. Versions coexist. | +| `false` (default in [envoy mode](/docs/general/runtime-modes)) | Old actors continue running. New actors go to new version. Versions coexist. | | `true` (default in [serverless mode](/docs/general/runtime-modes)) | Old actors receive stop signal and have 30m to finish gracefully. | ## Upgrading Actor State @@ -268,7 +264,7 @@ const todoList = actor({ }); const registry = setup({ use: { todoList } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ### In-memory state (`c.state`) @@ -294,7 +290,7 @@ const myActor = actor({ }); const registry = setup({ use: { myActor } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` **Zod schema coercion** @@ -323,7 +319,7 @@ const myActor = actor({ }); const registry2 = setup({ use: { myActor } }); -registry2.start(); +registry2.listen({ port: 3000, path: "/api/rivet" }); ``` For anything beyond simple defaults, consider moving to [SQLite](/docs/actors/sqlite) where you get proper migration tooling. @@ -334,20 +330,20 @@ For anything beyond simple defaults, consider moving to [SQLite](/docs/actors/sq When `drainOnVersionUpgrade` is enabled, Rivet uses two mechanisms to detect version changes: -- **New runner connection**: When a runner connects with a newer version number, the engine immediately drains all older runners with the same name. This is the primary mechanism for [runner mode](/docs/general/runtime-modes) deployments. -- **Metadata polling** (serverless only): In [serverless mode](/docs/general/runtime-modes), runners periodically poll the engine to check for newer versions and self-drain if one is found. This ensures old runners drain even if no new requests trigger a runner connection. +- **New envoy connection**: When an envoy connects with a newer version number, the engine immediately drains all older envoys with the same name. This is the primary mechanism for [envoy mode](/docs/general/runtime-modes) deployments. +- **Metadata polling** (serverless only): In [serverless mode](/docs/general/runtime-modes), envoys periodically poll the engine to check for newer versions and self-drain if one is found. This ensures old envoys drain even if no new requests trigger an envoy connection. ### SIGTERM Handling -When a runner process receives SIGTERM, it gracefully stops all actors before exiting: +When a envoy process receives SIGTERM, it gracefully stops all actors before exiting: - Each actor's `onSleep` hook is called, giving it time to save state -- Actors are rescheduled to other available runners -- The runner waits up to **30 minutes** for all actors to finish stopping +- Actors are rescheduled to other available envoys +- The envoy waits up to **30 minutes** for all actors to finish stopping - If the process is force-killed before actors finish (e.g. SIGKILL), actors are rescheduled with a crash backoff penalty instead of a clean handoff -Ensure your platform's shutdown grace period is at least **35 minutes** so actors have 30 minutes to stop cleanly plus buffer for runner shutdown overhead. +Ensure your platform's shutdown grace period is at least **35 minutes** so actors have 30 minutes to stop cleanly plus buffer for envoy shutdown overhead. ### Shutdown Timeouts @@ -358,11 +354,11 @@ Several timeouts control how long each part of the shutdown process can take: |---------|---------|-------------|---------------| | `actor_stop_threshold` | 30m | Engine-side limit on how long each actor has to stop before being marked lost | [Engine config](/docs/self-hosting/configuration) (`pegboard.actor_stop_threshold`) | | `sleepGracePeriod` | 15s | Total graceful sleep budget for `onSleep`, `waitUntil`, `keepAwake`, and async raw WebSocket handlers | [Actor options](/docs/actors/lifecycle#options) | -| `runner_lost_threshold` | 15s | Fallback detection if the runner dies without graceful shutdown | [Engine config](/docs/self-hosting/configuration) (`pegboard.runner_lost_threshold`) | +| `runner_lost_threshold` | 15s | Fallback detection if the envoy dies without graceful shutdown | [Engine config](/docs/self-hosting/configuration) (`pegboard.runner_lost_threshold`) | Rivet has a max shutdown grace period of 30 minutes that cannot be configured. ## Related -- [Runtime Modes](/docs/general/runtime-modes): Serverless vs runner deployment modes +- [Runtime Modes](/docs/general/runtime-modes): Serverless vs envoy deployment modes - [Lifecycle](/docs/actors/lifecycle): Actor lifecycle hooks including `onSleep` diff --git a/website/src/content/docs/actors/websocket-handler.mdx b/website/src/content/docs/actors/websocket-handler.mdx index 1e3e13305d..1a801cb0c0 100644 --- a/website/src/content/docs/actors/websocket-handler.mdx +++ b/website/src/content/docs/actors/websocket-handler.mdx @@ -62,7 +62,7 @@ export const chat = actor({ }); export const registry = setup({ use: { chat } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```typescript client.ts @nocheck @@ -108,7 +108,7 @@ export const chat = actor({ }); export const registry = setup({ use: { chat } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```typescript client.ts @nocheck diff --git a/website/src/content/docs/agent-os/agent-to-agent.mdx b/website/src/content/docs/agent-os/agent-to-agent.mdx index 66e3b9b20a..b9aac95e51 100644 --- a/website/src/content/docs/agent-os/agent-to-agent.mdx +++ b/website/src/content/docs/agent-os/agent-to-agent.mdx @@ -64,7 +64,7 @@ const reviewer = agentOs({ }); export const registry = setup({ use: { writer, reviewer } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts diff --git a/website/src/content/docs/agent-os/agents/pi.mdx b/website/src/content/docs/agent-os/agents/pi.mdx index 45656b39d5..c75a6c0592 100644 --- a/website/src/content/docs/agent-os/agents/pi.mdx +++ b/website/src/content/docs/agent-os/agents/pi.mdx @@ -18,7 +18,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts diff --git a/website/src/content/docs/agent-os/authentication.mdx b/website/src/content/docs/agent-os/authentication.mdx index 5daeaed459..b0699eaad9 100644 --- a/website/src/content/docs/agent-os/authentication.mdx +++ b/website/src/content/docs/agent-os/authentication.mdx @@ -29,7 +29,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ## `createConnState` @@ -59,7 +59,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ## Client usage diff --git a/website/src/content/docs/agent-os/configuration.mdx b/website/src/content/docs/agent-os/configuration.mdx index 59716654ce..a77282c66e 100644 --- a/website/src/content/docs/agent-os/configuration.mdx +++ b/website/src/content/docs/agent-os/configuration.mdx @@ -48,7 +48,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ## Session options diff --git a/website/src/content/docs/agent-os/cron.mdx b/website/src/content/docs/agent-os/cron.mdx index be41d596a6..305437cdaa 100644 --- a/website/src/content/docs/agent-os/cron.mdx +++ b/website/src/content/docs/agent-os/cron.mdx @@ -44,7 +44,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -83,7 +83,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -128,7 +128,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -165,7 +165,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -200,7 +200,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` diff --git a/website/src/content/docs/agent-os/events.mdx b/website/src/content/docs/agent-os/events.mdx index 6261992596..65645cede1 100644 --- a/website/src/content/docs/agent-os/events.mdx +++ b/website/src/content/docs/agent-os/events.mdx @@ -148,7 +148,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -191,6 +191,6 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` diff --git a/website/src/content/docs/agent-os/filesystem.mdx b/website/src/content/docs/agent-os/filesystem.mdx index db94eebac2..d686e05e1e 100644 --- a/website/src/content/docs/agent-os/filesystem.mdx +++ b/website/src/content/docs/agent-os/filesystem.mdx @@ -36,7 +36,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ### Host directory @@ -58,7 +58,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ### S3 @@ -128,7 +128,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ### Google Drive @@ -161,7 +161,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ## Filesystem operations diff --git a/website/src/content/docs/agent-os/index.mdx b/website/src/content/docs/agent-os/index.mdx index 39fa75efdb..9d0eb48b88 100644 --- a/website/src/content/docs/agent-os/index.mdx +++ b/website/src/content/docs/agent-os/index.mdx @@ -78,7 +78,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -135,7 +135,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -161,7 +161,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts @@ -248,7 +248,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -291,7 +291,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -330,7 +330,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -376,7 +376,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -401,7 +401,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` [Documentation](/docs/agent-os/sandbox) @@ -443,7 +443,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -468,7 +468,7 @@ const reviewer = agentOs({ }); export const registry = setup({ use: { coder, reviewer } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts @@ -551,7 +551,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { automator, vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` [Documentation](/docs/agent-os/workflows) diff --git a/website/src/content/docs/agent-os/llm-credentials.mdx b/website/src/content/docs/agent-os/llm-credentials.mdx index 154397b74f..b9cb9f94c0 100644 --- a/website/src/content/docs/agent-os/llm-credentials.mdx +++ b/website/src/content/docs/agent-os/llm-credentials.mdx @@ -44,7 +44,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` Then use the connection state when creating sessions: diff --git a/website/src/content/docs/agent-os/multiplayer.mdx b/website/src/content/docs/agent-os/multiplayer.mdx index 29f00a44d5..5bbb2124f6 100644 --- a/website/src/content/docs/agent-os/multiplayer.mdx +++ b/website/src/content/docs/agent-os/multiplayer.mdx @@ -53,7 +53,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -93,7 +93,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -117,7 +117,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts @@ -183,7 +183,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` diff --git a/website/src/content/docs/agent-os/networking.mdx b/website/src/content/docs/agent-os/networking.mdx index 7e5d465afb..a3ae84552e 100644 --- a/website/src/content/docs/agent-os/networking.mdx +++ b/website/src/content/docs/agent-os/networking.mdx @@ -45,7 +45,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -83,7 +83,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -107,7 +107,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts @@ -161,7 +161,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` diff --git a/website/src/content/docs/agent-os/permissions.mdx b/website/src/content/docs/agent-os/permissions.mdx index 17b0275df6..8b92c573bc 100644 --- a/website/src/content/docs/agent-os/permissions.mdx +++ b/website/src/content/docs/agent-os/permissions.mdx @@ -50,7 +50,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -82,7 +82,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts @@ -124,7 +124,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts diff --git a/website/src/content/docs/agent-os/persistence.mdx b/website/src/content/docs/agent-os/persistence.mdx index 4c4200c6b7..0b5c02bc3b 100644 --- a/website/src/content/docs/agent-os/persistence.mdx +++ b/website/src/content/docs/agent-os/persistence.mdx @@ -89,7 +89,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -139,7 +139,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` diff --git a/website/src/content/docs/agent-os/processes.mdx b/website/src/content/docs/agent-os/processes.mdx index 2d29160770..7f56bc4cc7 100644 --- a/website/src/content/docs/agent-os/processes.mdx +++ b/website/src/content/docs/agent-os/processes.mdx @@ -39,7 +39,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -81,7 +81,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -121,7 +121,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -163,7 +163,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -203,7 +203,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -249,7 +249,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` diff --git a/website/src/content/docs/agent-os/queues.mdx b/website/src/content/docs/agent-os/queues.mdx index 69aa00c267..6462b4fcd5 100644 --- a/website/src/content/docs/agent-os/queues.mdx +++ b/website/src/content/docs/agent-os/queues.mdx @@ -43,7 +43,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { taskRunner, vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts @@ -103,7 +103,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { reviewer, vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts @@ -168,7 +168,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { issueWorker, vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts diff --git a/website/src/content/docs/agent-os/quickstart.mdx b/website/src/content/docs/agent-os/quickstart.mdx index 5a6cad1b25..2361b197a2 100644 --- a/website/src/content/docs/agent-os/quickstart.mdx +++ b/website/src/content/docs/agent-os/quickstart.mdx @@ -35,7 +35,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts diff --git a/website/src/content/docs/agent-os/security.mdx b/website/src/content/docs/agent-os/security.mdx index fc82452b15..7a24534cfc 100644 --- a/website/src/content/docs/agent-os/security.mdx +++ b/website/src/content/docs/agent-os/security.mdx @@ -25,7 +25,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ## Network control @@ -45,7 +45,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ## Custom authentication @@ -69,7 +69,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); async function verifyToken(token: string): Promise { // Your authentication logic diff --git a/website/src/content/docs/agent-os/sessions.mdx b/website/src/content/docs/agent-os/sessions.mdx index 7d14edea28..0e33ab1b5e 100644 --- a/website/src/content/docs/agent-os/sessions.mdx +++ b/website/src/content/docs/agent-os/sessions.mdx @@ -45,7 +45,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -166,7 +166,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -204,7 +204,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -250,7 +250,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -289,7 +289,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -337,7 +337,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -380,7 +380,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -420,7 +420,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -466,7 +466,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` diff --git a/website/src/content/docs/agent-os/sqlite.mdx b/website/src/content/docs/agent-os/sqlite.mdx index aa5eb3ccee..536bdb266b 100644 --- a/website/src/content/docs/agent-os/sqlite.mdx +++ b/website/src/content/docs/agent-os/sqlite.mdx @@ -44,7 +44,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` The agent calls it as a CLI command: diff --git a/website/src/content/docs/agent-os/tools.mdx b/website/src/content/docs/agent-os/tools.mdx index ade91f9228..1e30f00b97 100644 --- a/website/src/content/docs/agent-os/tools.mdx +++ b/website/src/content/docs/agent-os/tools.mdx @@ -52,7 +52,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts diff --git a/website/src/content/docs/agent-os/webhooks.mdx b/website/src/content/docs/agent-os/webhooks.mdx index f597dec703..e14ac44cb6 100644 --- a/website/src/content/docs/agent-os/webhooks.mdx +++ b/website/src/content/docs/agent-os/webhooks.mdx @@ -55,7 +55,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { slackWorker, vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); // Hono server to receive Slack webhooks const app = new Hono(); diff --git a/website/src/content/docs/agent-os/workflows.mdx b/website/src/content/docs/agent-os/workflows.mdx index d3c2881071..5eee01d8d8 100644 --- a/website/src/content/docs/agent-os/workflows.mdx +++ b/website/src/content/docs/agent-os/workflows.mdx @@ -64,7 +64,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { automator, vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts @@ -141,7 +141,7 @@ const vm = agentOs({ }); export const registry = setup({ use: { pipeline, vm } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```ts @nocheck client.ts diff --git a/website/src/content/docs/clients/index.mdx b/website/src/content/docs/clients/index.mdx index 4e111df487..a07952578d 100644 --- a/website/src/content/docs/clients/index.mdx +++ b/website/src/content/docs/clients/index.mdx @@ -21,4 +21,4 @@ import { faNodeJs, faReact, faSwift } from "@rivet-gg/icons"; -The JavaScript, React, Swift, and SwiftUI clients automatically read `RIVET_ENDPOINT`, `RIVET_NAMESPACE`, `RIVET_TOKEN`, and `RIVET_RUNNER` for configuration. See each client page for defaults and examples. +The JavaScript, React, Swift, and SwiftUI clients automatically read `RIVET_ENDPOINT`, `RIVET_NAMESPACE`, `RIVET_TOKEN`, and `RIVET_POOL` for configuration. See each client page for defaults and examples. diff --git a/website/src/content/docs/clients/javascript.mdx b/website/src/content/docs/clients/javascript.mdx index fb2247c2a2..0c04764b51 100644 --- a/website/src/content/docs/clients/javascript.mdx +++ b/website/src/content/docs/clients/javascript.mdx @@ -41,7 +41,7 @@ export const registry = setup({ use: { counter }, }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -226,7 +226,7 @@ export const registry = setup({ use: { chatRoom }, }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -239,7 +239,7 @@ Don't build keys with string interpolation like `"org:${userId}"` when `userId` - `RIVET_ENDPOINT` (endpoint) - `RIVET_NAMESPACE` - `RIVET_TOKEN` -- `RIVET_RUNNER` +- `RIVET_POOL` Defaults to `http://localhost:6420` when unset. RivetKit runs on port 6420 by default. diff --git a/website/src/content/docs/clients/react.mdx b/website/src/content/docs/clients/react.mdx index d23c9d031c..e0f801423d 100644 --- a/website/src/content/docs/clients/react.mdx +++ b/website/src/content/docs/clients/react.mdx @@ -51,7 +51,7 @@ export const registry = setup({ use: { counter }, }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -243,7 +243,7 @@ export const registry = setup({ use: { chatRoom }, }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` @@ -256,7 +256,7 @@ Don't build keys with string interpolation like `"org:${userId}"` when `userId` - `RIVET_ENDPOINT` - `RIVET_NAMESPACE` - `RIVET_TOKEN` -- `RIVET_RUNNER` +- `RIVET_POOL` Defaults to `http://localhost:6420` when unset. RivetKit runs on port 6420 by default. diff --git a/website/src/content/docs/clients/swift.mdx b/website/src/content/docs/clients/swift.mdx index 5f83d0127c..4fd684b3a9 100644 --- a/website/src/content/docs/clients/swift.mdx +++ b/website/src/content/docs/clients/swift.mdx @@ -410,7 +410,7 @@ Don't build keys with string interpolation like `"org:\(userId)"` when `userId` - `RIVET_NAMESPACE` - Namespace (can also be in endpoint URL) - `RIVET_TOKEN` - Authentication token (can also be in endpoint URL) -- `RIVET_RUNNER` - Runner name (defaults to `"default"`) +- `RIVET_POOL` - Runner name (defaults to `"default"`) The `endpoint` parameter is always required. There is no default endpoint. diff --git a/website/src/content/docs/clients/swiftui.mdx b/website/src/content/docs/clients/swiftui.mdx index a12241c542..152a3b60a7 100644 --- a/website/src/content/docs/clients/swiftui.mdx +++ b/website/src/content/docs/clients/swiftui.mdx @@ -318,7 +318,7 @@ When using `.rivetKit(endpoint:)`, the client is created once and cached per end - `RIVET_NAMESPACE` - Namespace (can also be in endpoint URL) - `RIVET_TOKEN` - Authentication token (can also be in endpoint URL) -- `RIVET_RUNNER` - Runner name (defaults to `"default"`) +- `RIVET_POOL` - Runner name (defaults to `"default"`) The endpoint is always required. There is no default endpoint. diff --git a/website/src/content/docs/connect/cloudflare.mdx b/website/src/content/docs/connect/cloudflare.mdx index a27a3c7bc8..ce3e9f4bc3 100644 --- a/website/src/content/docs/connect/cloudflare.mdx +++ b/website/src/content/docs/connect/cloudflare.mdx @@ -94,10 +94,12 @@ const counter = actor({ }); const use = { counter }; -let registry: { handler(request: Request): Promise } | undefined; +let handler: ((request: Request) => Promise) | undefined; -function getRegistry(env: Env) { - registry ??= setup({ +function getHandler(env: Env) { + if (handler) return handler; + + const registry = setup({ runtime: "wasm", sqlite: "remote", wasm: { @@ -105,23 +107,24 @@ function getRegistry(env: Env) { initInput: wasmModule, }, use, - endpoint: env.RIVET_ENDPOINT, + engine: { + endpoint: env.RIVET_ENDPOINT, + }, namespace: env.RIVET_NAMESPACE, token: env.RIVET_TOKEN, - envoy: { - poolName: env.RIVET_POOL, - }, - serverless: { - publicEndpoint: env.RIVET_PUBLIC_ENDPOINT, - }, + pool: env.RIVET_POOL, }); - return registry; + handler = registry.fetchHandler({ + path: "/api/rivet", + publicEndpoint: env.RIVET_PUBLIC_ENDPOINT, + }); + return handler; } export default { async fetch(request: Request, env: Env): Promise { - return await getRegistry(env).handler(request); + return await getHandler(env)(request); }, }; ``` diff --git a/website/src/content/docs/connect/freestyle.mdx b/website/src/content/docs/connect/freestyle.mdx index 6db6b40bff..fc896315c7 100644 --- a/website/src/content/docs/connect/freestyle.mdx +++ b/website/src/content/docs/connect/freestyle.mdx @@ -46,15 +46,16 @@ export const counter = actor({ export const registry = setup({ use: { counter }, }); -registry.start(); ``` ```typescript server.ts @nocheck import { registry } from "./index"; +const handler = registry.fetchHandler({ path: "/api/rivet" }); + // Freestyle uses Deno under the hood for web deployments // @ts-ignore Deno is a Freestyle runtime global -Deno.serve((request: Request) => registry.handler(request)); +Deno.serve(handler); ``` @@ -77,7 +78,6 @@ declare const buildDir: string; const res = await freestyle.deployWeb(buildDir, { envVars: { FREESTYLE_ENDPOINT: `https://${FREESTYLE_DOMAIN}`, - RIVET_RUNNER_KIND: "serverless", // For self-hosted instances: // RIVET_ENDPOINT: "http://127.0.0.1:6420", RIVET_ENDPOINT: "api.rivet.dev", diff --git a/website/src/content/docs/connect/supabase.mdx b/website/src/content/docs/connect/supabase.mdx index 23a33b3399..6d499eaab6 100644 --- a/website/src/content/docs/connect/supabase.mdx +++ b/website/src/content/docs/connect/supabase.mdx @@ -82,21 +82,20 @@ const registry = setup({ initInput: wasmModule, }, use: { counter }, - endpoint: Deno.env.get("RIVET_ENDPOINT"), + engine: { + endpoint: Deno.env.get("RIVET_ENDPOINT"), + }, namespace: Deno.env.get("RIVET_NAMESPACE"), token: Deno.env.get("RIVET_TOKEN"), - envoy: { - poolName: Deno.env.get("RIVET_POOL") ?? "supabase-functions", - }, - serverless: { - basePath: "/rivet/api/rivet", - publicEndpoint: Deno.env.get("RIVET_PUBLIC_ENDPOINT"), - }, + pool: Deno.env.get("RIVET_POOL") ?? "supabase-functions", }); -Deno.serve(async (request) => { - return await registry.handler(request); +const handler = registry.fetchHandler({ + path: "/rivet/api/rivet", + publicEndpoint: Deno.env.get("RIVET_PUBLIC_ENDPOINT"), }); + +Deno.serve(handler); ``` diff --git a/website/src/content/docs/connect/vercel.mdx b/website/src/content/docs/connect/vercel.mdx index 404a180219..585b67d102 100644 --- a/website/src/content/docs/connect/vercel.mdx +++ b/website/src/content/docs/connect/vercel.mdx @@ -52,7 +52,8 @@ import { Hono } from "hono"; import { registry } from "./actors.ts"; const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; ``` diff --git a/website/src/content/docs/general/endpoints.mdx b/website/src/content/docs/general/endpoints.mdx index fee03b6a4e..d0124022a2 100644 --- a/website/src/content/docs/general/endpoints.mdx +++ b/website/src/content/docs/general/endpoints.mdx @@ -44,7 +44,9 @@ const myActor = actor({ const registry = setup({ use: { myActor }, - endpoint: "https://my-namespace:sk_xxxxx@api.rivet.dev", + engine: { + endpoint: "https://my-namespace:sk_xxxxx@api.rivet.dev", + }, }); ``` @@ -58,7 +60,7 @@ The public endpoint tells clients where to connect to reach your actors. This endpoint and token will be exposed to the internet. Use a public token (`pk_`), not your secret token (`sk_`). -The public endpoint is only required if using the [serverless runtime mode](/docs/general/runtime-modes#runners) and if you have a frontend using RivetKit. +The public endpoint is only required if using the [serverless runtime mode](/docs/general/runtime-modes#serverless-mode) and if you have a frontend using RivetKit. @@ -82,9 +84,11 @@ const myActor = actor({ const registry = setup({ use: { myActor }, - serverless: { - publicEndpoint: "https://my-namespace:pk_xxxxx@api.rivet.dev", - }, +}); + +const handler = registry.fetchHandler({ + path: "/api/rivet", + publicEndpoint: "https://my-namespace:pk_xxxxx@api.rivet.dev", }); ``` @@ -116,7 +120,7 @@ In serverless mode, the private endpoint is used to validate that requests to `G ### How Clients Connect -This flow applies to [serverless runtime mode](/docs/general/runtime-modes#serverless). For [runner runtime mode](/docs/general/runtime-modes#runners) or [clients configured to connect directly to Rivet](/docs/clients/javascript), clients connect directly to Rivet and this metadata flow is not needed. +This flow applies to [serverless runtime mode](/docs/general/runtime-modes#serverless-mode). For [envoy mode](/docs/general/runtime-modes#envoy-mode) or [clients configured to connect directly to Rivet](/docs/clients/javascript), clients connect directly to Rivet and this metadata flow is not needed. When a client connects to your serverless application, it follows this flow: @@ -138,8 +142,8 @@ This indirection exists because Rivet acts as a gateway between clients and your | Environment Variable | Config Option | Description | |---------------------|---------------|-------------| -| `RIVET_ENDPOINT` | `endpoint` | Rivet Engine URL for your backend | +| `RIVET_ENDPOINT` | `engine.endpoint` | Rivet Engine URL for your backend | | `RIVET_NAMESPACE` | `namespace` | Namespace for actor isolation | | `RIVET_TOKEN` | `token` | Authentication token for engine connection | -| `RIVET_PUBLIC_ENDPOINT` | `serverless.publicEndpoint` | Client-facing endpoint | -| `RIVET_PUBLIC_TOKEN` | `serverless.publicToken` | Client-facing token | +| `RIVET_PUBLIC_ENDPOINT` | `fetchHandler({ publicEndpoint })` | Client-facing endpoint | +| `RIVET_PUBLIC_TOKEN` | `fetchHandler({ publicToken })` | Client-facing token | diff --git a/website/src/content/docs/general/environment-variables.mdx b/website/src/content/docs/general/environment-variables.mdx index 39a210b500..a8017c3fb2 100644 --- a/website/src/content/docs/general/environment-variables.mdx +++ b/website/src/content/docs/general/environment-variables.mdx @@ -4,71 +4,76 @@ description: "This page documents all environment variables that configure Rivet skill: true --- +## Runtime + +| Environment Variable | Description | +| --- | --- | +| `RIVET_POOL` | Pool name. Defaults to `"default"`. | +| `RIVET_VERSION` | Runtime version for deploy drain behavior. Required when `NODE_ENV === "production"`. | + +Do not set the same value in both `setup()` and the environment. RivetKit throws instead of choosing precedence. + ## Connection | Environment Variable | Description | -|---------------------|-------------| +| --- | --- | | `RIVET_ENDPOINT` | Endpoint URL to connect to Rivet Engine. Supports [URL auth syntax](/docs/general/endpoints#url-auth-syntax). | -| `RIVET_TOKEN` | Authentication token for Rivet Engine | -| `RIVET_NAMESPACE` | Namespace to use (default: "default") | +| `RIVET_TOKEN` | Authentication token for Rivet Engine. | +| `RIVET_NAMESPACE` | Namespace to use. Defaults to `"default"`. | + +Prefer `RIVET_ENDPOINT` over hardcoding `engine.endpoint` in source code. ## Public Endpoint These variables configure how clients connect to your actors. | Environment Variable | Description | -|---------------------|-------------| +| --- | --- | | `RIVET_PUBLIC_ENDPOINT` | Public endpoint for client connections. Supports [URL auth syntax](/docs/general/endpoints#url-auth-syntax). | -| `RIVET_PUBLIC_TOKEN` | Public token for client authentication | - -## Runner Configuration - -| Environment Variable | Description | -|---------------------|-------------| -| `RIVET_RUNNER` | Runner name (default: "default") | -| `RIVET_RUNNER_VERSION` | Version number for the runner. See [Versions & Upgrades](/docs/actors/versions). | -| `RIVET_RUNNER_KIND` | Type of runner | -| `RIVET_TOTAL_SLOTS` | Total actor slots available (default: 100000) | +| `RIVET_PUBLIC_TOKEN` | Public token for client authentication. | ## Engine | Environment Variable | Description | -|---------------------|-------------| -| `RIVET_RUN_ENGINE` | Set to `1` to spawn the engine process | -| `RIVET_RUN_ENGINE_VERSION` | Version of engine to download | +| --- | --- | +| `RIVET_ENGINE_BINARY_PATH` | Advanced native-runtime engine binary override. | ## Inspector | Environment Variable | Description | -|---------------------|-------------| -| `RIVET_INSPECTOR_DISABLE` | Set to `1` to disable the inspector | +| --- | --- | +| `RIVET_INSPECTOR_DISABLE` | Set to `1` to disable the inspector. | ## Metrics | Environment Variable | Description | -|---------------------|-------------| +| --- | --- | | `_RIVET_METRICS_TOKEN` | Bearer token that gates the per-actor Prometheus `/metrics` endpoint. When unset, `/metrics` is disabled. | ## Experimental | Environment Variable | Description | -|---------------------|-------------| -| `RIVET_EXPERIMENTAL_OTEL` | Set to `1` to enable experimental OTel tracing in Rivet Actors | +| --- | --- | +| `RIVET_EXPERIMENTAL_OTEL` | Set to `1` to enable experimental OTel tracing in Rivet Actors. | ## Storage | Environment Variable | Description | -|---------------------|-------------| +| --- | --- | | `RIVETKIT_RUNTIME` | Runtime binding to use for RivetKit core: `auto`, `native`, or `wasm`. Defaults to `auto`. | | `RIVETKIT_STORAGE_PATH` | Overrides the default file-system storage path used by RivetKit when using the default driver. | ## Logging | Environment Variable | Description | -|---------------------|-------------| -| `RIVET_LOG_LEVEL` | Log level: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `silent` | -| `RIVET_LOG_TARGET` | Set to `1` to include log target | -| `RIVET_LOG_TIMESTAMP` | Set to `1` to include timestamps | -| `RIVET_LOG_MESSAGE` | Set to `1` to include message formatting | -| `RIVET_LOG_ERROR_STACK` | Set to `1` to include error stack traces | -| `RIVET_LOG_HEADERS` | Set to `1` to log request headers | +| --- | --- | +| `RIVET_LOG_LEVEL` | Log level: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `silent`. | +| `RIVET_LOG_TARGET` | Set to `1` to include log target. | +| `RIVET_LOG_TIMESTAMP` | Set to `1` to include timestamps. | +| `RIVET_LOG_MESSAGE` | Set to `1` to include message formatting. | +| `RIVET_LOG_ERROR_STACK` | Set to `1` to include error stack traces. | +| `RIVET_LOG_HEADERS` | Set to `1` to log request headers. | + +## Removed Variables + +These variables now throw at startup: `RIVET_RUNNER`, `RIVET_RUNNER_*`, `RIVET_ENVOY_VERSION`, `RIVET_ENVOY_KIND`, `RIVET_POOL_NAME`, `RIVET_RUN_ENGINE`, `RIVET_RUN_ENGINE_VERSION`, and `RIVET_TOTAL_SLOTS`. diff --git a/website/src/content/docs/general/http-server.mdx b/website/src/content/docs/general/http-server.mdx index e7b53a7f83..54cedcf369 100644 --- a/website/src/content/docs/general/http-server.mdx +++ b/website/src/content/docs/general/http-server.mdx @@ -6,9 +6,9 @@ skill: true ## Methods of Running Your Server -### registry.start() +### registry.listen() -The simplest way to run your server. Starts a local RivetKit server, serves static files from a `public` directory, and starts the actor runner: +The simplest way to run your server. Starts a local RivetKit server, serves static files from a `public` directory, and starts the actor runtime: ```ts index.ts import { actor, setup } from "rivetkit"; @@ -16,7 +16,7 @@ import { actor, setup } from "rivetkit"; const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` Run with `npx tsx --watch index.ts` (Node.js), `bun --watch index.ts` (Bun), or `deno run --allow-net --allow-read --allow-env --watch index.ts` (Deno). Clients connect to the Rivet Engine on `http://localhost:6420`. @@ -25,18 +25,19 @@ Run with `npx tsx --watch index.ts` (Node.js), `bun --watch index.ts` (Bun), or A [fetch handler](https://wintercg.org/) is a function that takes a `Request` and returns a `Response`. This is the standard pattern used by Cloudflare Workers, Deno Deploy, Bun, and other modern runtimes. -Use `registry.serve()` to get a fetch handler: +Use `registry.fetchHandler()` to get a fetch handler: ```ts server.ts import { actor, setup } from "rivetkit"; const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); +const handler = registry.fetchHandler({ path: "/api/rivet" }); -export default registry.serve(); +export default { fetch: handler }; ``` -To integrate with a router like [Hono](https://hono.dev/) or [Elysia](https://elysiajs.com/), use `registry.handler()`: +To integrate with a router like [Hono](https://hono.dev/) or [Elysia](https://elysiajs.com/), call the returned fetch handler: @@ -51,7 +52,8 @@ const registry = setup({ use: { myActor } }); const app = new Hono(); app.get("/health", (c) => c.text("OK")); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; ``` @@ -68,8 +70,9 @@ const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); const app = new Elysia() - .get("/health", () => "OK") - .all("/api/rivet/*", ({ request }) => registry.handler(request)); + .get("/health", () => "OK"); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", ({ request }) => handler(request)); export default app; ``` @@ -115,7 +118,8 @@ const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); serve({ fetch: app.fetch, port: 3000 }); ``` @@ -134,8 +138,9 @@ import { createServerAdapter } from "@whatwg-node/server"; const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); +const fetch = registry.fetchHandler({ path: "/api/rivet" }); -const handler = createServerAdapter(registry.serve().fetch); +const handler = createServerAdapter(fetch); const server = createServer(handler); server.listen(3000); ``` @@ -151,10 +156,11 @@ import { actor, setup } from "rivetkit"; const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); +const handler = registry.fetchHandler({ path: "/api/rivet" }); Bun.serve({ port: 3000, - fetch: (request: Request) => registry.handler(request), + fetch: handler, }); ``` @@ -169,8 +175,9 @@ import { actor, setup } from "rivetkit"; const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); +const handler = registry.fetchHandler({ path: "/api/rivet" }); -Deno.serve({ port: 3000 }, (request: Request) => registry.handler(request)); +Deno.serve({ port: 3000 }, handler); ``` diff --git a/website/src/content/docs/general/production-checklist.mdx b/website/src/content/docs/general/production-checklist.mdx index 171e519c75..03eaa538ea 100644 --- a/website/src/content/docs/general/production-checklist.mdx +++ b/website/src/content/docs/general/production-checklist.mdx @@ -21,7 +21,7 @@ We recommend passing this page to your coding agent to verify your configuration ### Serverless - **Check platform timeouts.** Rivet handles migration between invocations automatically, but shorter timeouts increase migration frequency. See [Timeouts](/docs/general/runtime-modes#timeouts). -- **Verify `/api/rivet/start` body size limits.** Serverless actor starts carry actor config and preloaded KV or SQLite startup data in the request body. Keep `serverless.maxStartPayloadBytes` and your platform or proxy body limit at **16 MiB or higher**, or lower the preload budget if your platform cannot accept that size. See [Limits](/docs/actors/limits#kv-preloading). +- **Verify `/api/rivet/start` body size limits.** Serverless actor starts carry actor config and preloaded KV or SQLite startup data in the request body. Keep `fetchHandler({ maxStartPayloadBytes })` and your platform or proxy body limit at **16 MiB or higher**, or lower the preload budget if your platform cannot accept that size. See [Limits](/docs/actors/limits#kv-preloading). - **Configure max runners.** Go to Settings > Providers > Edit Provider > Max Runners to set the limit. The default is 100,000 runners. This is effectively your max actor count. - **Verify your platform rate limit accommodates your actor create and wake frequency.** Actor start requests are sent from Rivet's servers, so they all originate from the same IP. Per-IP rate limits will throttle the engine well before they would throttle real end-user traffic. Size your platform's rate limit to your peak actor create and wake rate, not your end-user request rate. - **Configure platform max concurrency if available.** Some platforms (e.g. GCP Cloud Run, AWS Lambda) let you cap the number of concurrent instances. Set this to match your expected concurrent actor count so the platform admits enough instances to host your actors. diff --git a/website/src/content/docs/general/registry-configuration.mdx b/website/src/content/docs/general/registry-configuration.mdx index 63e560d7f9..1737a4ad7d 100644 --- a/website/src/content/docs/general/registry-configuration.mdx +++ b/website/src/content/docs/general/registry-configuration.mdx @@ -45,9 +45,9 @@ const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor }, - endpoint: "https://api.rivet.dev", - token: process.env.RIVET_TOKEN, - namespace: "production", + engine: { + endpoint: "https://api.rivet.dev", + }, }); ``` @@ -59,13 +59,13 @@ After configuring your registry, start it: -```typescript registry.start() +```typescript Native-server import { actor, setup } from "rivetkit"; const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); -registry.start(); +registry.listen({ port: 3000, path: "/api/rivet" }); ``` ```typescript Serverless @@ -73,8 +73,11 @@ import { actor, setup } from "rivetkit"; const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); +const handler = registry.fetchHandler({ + path: "/api/rivet", +}); -export default registry.serve(); +export default { fetch: handler }; ``` ```typescript Serverless-with-Router @@ -85,7 +88,8 @@ const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ path: "/api/rivet" }); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; ``` @@ -96,7 +100,7 @@ import { actor, setup } from "rivetkit"; const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); -registry.startEnvoy(); +registry.start(); ``` diff --git a/website/src/content/docs/general/runtime-modes.mdx b/website/src/content/docs/general/runtime-modes.mdx index 88ffb5ef19..dd4cf10417 100644 --- a/website/src/content/docs/general/runtime-modes.mdx +++ b/website/src/content/docs/general/runtime-modes.mdx @@ -1,147 +1,210 @@ --- title: "Runtime Modes" -description: "RivetKit supports two runtime modes for running your actors:" +description: "Choose between serverless and envoy runtime modes based on how you deploy." skill: true --- -import imgServerless from "./runtime-modes/serverless.png"; -import imgRunners from "./runtime-modes/runners.png"; +Pick whichever runtime mode best matches your architecture. Actor code is identical between the two. +| | **Serverless** | **Envoy** | +| --- | --- | --- | +| **Deploy to** | Vercel, Cloudflare Workers, AWS Lambda, Netlify | Kubernetes, Fly.io, Render, AWS ECS, Railway | +| **Connection** | Rivet calls your app (`POST /api/rivet/start`) | Your app opens a WebSocket to Rivet | +| **Public endpoint** | Required. Rivet must reach your HTTP endpoint. | Not required. Your app connects to Rivet. | +| **Scaling** | Handled by your serverless platform. Scales to zero. | CPU/memory autoscaler (e.g. Kubernetes HPA). | +## Serverless Mode -- **Serverless**: Default mode. Responds to HTTP requests and scales automatically. -- **Runners**: Background processes without HTTP endpoints. Only needed for advanced scenarios. +In serverless mode, the Rivet engine calls your app whenever an actor needs to run. Your platform scales the function automatically. -## Serverless +```mermaid +sequenceDiagram + participant Client + participant Rivet as Rivet Engine + participant App as Your App + participant Actor as counter actor -Serverless is the default and recommended mode. Rivet sends HTTP requests to your backend to run actor logic, allowing your infrastructure to scale automatically. - -### Benefits - -- **Platform support**: Works with serverless platforms (Vercel, Cloudflare Workers, etc.) -- **Scale to zero**: No cost when idle -- **Edge deployments**: Easier to deploy to edge locations -- **Preview deployments**: Integrates with preview deployments on platforms like Vercel and Railway -- **Efficient autoscaling**: Request-based autoscaling can be faster and more efficient than CPU-based autoscaling depending on the platform + Client->>Rivet: create actor + Rivet->>App: POST /api/rivet/start + App->>Actor: spawn +``` -### Example +### Setup ```typescript Direct import { actor, setup } from "rivetkit"; -const myActor = actor({ state: {}, actions: {} }); -const registry = setup({ use: { myActor } }); +const counter = actor({ state: {}, actions: {} }); +const registry = setup({ use: { counter } }); +const handler = registry.fetchHandler({ + path: "/api/rivet", +}); -export default registry.serve(); +export default { fetch: handler }; ``` ```typescript With-Router import { Hono } from "hono"; import { actor, setup } from "rivetkit"; -const myActor = actor({ state: {}, actions: {} }); -const registry = setup({ use: { myActor } }); +const counter = actor({ state: {}, actions: {} }); +const registry = setup({ use: { counter } }); const app = new Hono(); -app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +const handler = registry.fetchHandler({ + path: "/api/rivet", +}); +app.all("/api/rivet/*", (c) => handler(c.req.raw)); export default app; ``` -See [Server Setup](/docs/general/http-server/) for more configuration options. +See [Server Setup](/docs/general/http-server/) for additional configuration options. -### Architecture +Do not call `registry.start()` on the same registry when using `registry.fetchHandler(...)`. `fetchHandler` is the serverless entrypoint, and RivetKit throws if a registry tries to use more than one entrypoint. -When a client creates an actor, it sends a request to the Rivet Engine. The engine then calls `GET /api/rivet/start` on your serverless backend to run the actor. +### Local Development -Serverless architecture diagram +When running serverless mode locally, point RivetKit at your local serverless endpoint with `dev`. RivetKit starts (or reuses) a local Rivet engine and configures it to call that URL when `NODE_ENV=development`: -### Advanced +```typescript +import { actor, setup } from "rivetkit"; -#### Endpoints +const counter = actor({ state: {}, actions: {} }); -Rivet exposes the following endpoints: +const registry = setup({ use: { counter } }); +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: "http://127.0.0.1:3000/api/rivet", +}); -- `GET /api/rivet/metadata`: Validates configuration -- `GET /api/rivet/start`: Runs an actor +export default { fetch: handler }; +``` -You should never call these endpoints yourself, this is included purely for comprehension of how Rivet works under the hood. +Use `dev: { startEngine: false }` when something else (e.g. a separate engine instance) is already configured to call your endpoint: -#### Timeouts +```typescript +import { actor, setup } from "rivetkit"; -Serverless platforms like Vercel have function timeouts. Rivet handles this automatically by migrating actors between function invocations, preserving state through `ctx.state`. Write your code as if it runs forever, Rivet handles the rest. +const counter = actor({ state: {}, actions: {} }); -Read more about [how we handle timeouts](/blog/2025-10-20-how-we-built-websocket-servers-for-vercel-functions/#timeouts-and-failover). +const registry = setup({ use: { counter } }); +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: { + url: "http://127.0.0.1:3000/api/rivet", + startEngine: false, + }, +}); +``` -#### Shutdown Sequence +### Endpoints -Each serverless request has a configurable lifespan (`requestLifespan`, default: 60 minutes). Set this to match your platform's function timeout (e.g. `requestLifespan: 3600` for Vercel Pro). +Rivet exposes the following endpoints on your serverless app: -When the request nears its lifespan, the engine reserves a grace period (`serverless_drain_grace_period`, default: 10 seconds) at the end to gracefully stop actors. For example, with a 3600-second lifespan, actors begin stopping at 3590 seconds. After the full lifespan elapses, the connection is forcibly closed and any remaining actors are rescheduled. +- `GET /api/rivet/metadata` — Validates configuration. +- `POST /api/rivet/start` — Runs an actor. -See [Limits](/docs/actors/limits#serverless-shutdown) for configuration details. +You should never call these endpoints yourself. They are documented purely so you understand how Rivet works under the hood. + +### Timeouts -## Runners +Serverless platforms like Vercel have function timeouts. Rivet handles this automatically by migrating actors between function invocations, preserving state through `ctx.state`. Write your code as if it runs forever; Rivet handles the rest. -Runners run actors as long-running background processes without exposing an HTTP endpoint. +Read more about [how we handle timeouts](/blog/2025-10-20-how-we-built-websocket-servers-for-vercel-functions/#timeouts-and-failover). -### When to Use Runners +### Shutdown Sequence -- **No HTTP server**: Your app does not or cannot expose an HTTP server -- **No load balancer**: You don't have a load balancer to distribute HTTP requests across your servers -- **Custom scaling**: You have custom scaling requirements +Each serverless request has a configurable lifespan (`requestLifespan`, default: 60 minutes). Set this to match your platform's function timeout (for example, `requestLifespan: 3600` for Vercel Pro). -### Example +When the request nears its lifespan, the engine reserves a grace period (`serverless_drain_grace_period`, default: 10 seconds) at the end to gracefully stop actors. With a 3600-second lifespan, actors begin stopping at 3590 seconds. After the full lifespan elapses, the connection is forcibly closed and any remaining actors are rescheduled. -```typescript runner.ts -import { actor, setup } from "rivetkit"; +See [Limits](/docs/actors/limits#serverless-shutdown) for configuration details. + +## Envoy Mode + +In envoy mode, your app opens a persistent WebSocket out to the Rivet engine and stays connected. Rivet routes actor work over that socket. Run as many envoy replicas as you need behind an autoscaler (e.g. Kubernetes HPA); each replica registers itself on startup, so adding or removing replicas is transparent to clients. -const myActor = actor({ state: {}, actions: {} }); -const registry = setup({ use: { myActor } }); +```mermaid +sequenceDiagram + participant Client + participant Rivet as Rivet Engine + participant App as Your App + participant Actor as counter actor -registry.startEnvoy(); + Note over App: registry.start() + App->>Rivet: WebSocket connect + Note over App,Rivet: Persistent connection + + Client->>Rivet: create actor + Rivet->>App: start actor (over WebSocket) + App->>Actor: spawn ``` -The runner runs in the background, ready to run actors. +### Setup -### Architecture +```typescript +import { actor, setup } from "rivetkit"; -On startup, your backend calls `registry.startEnvoy()` which opens a persistent connection to the Rivet Engine. When a client creates an actor, the engine sends a command through this connection to start the actor on your backend. +const counter = actor({ state: {}, actions: {} }); -Runners architecture diagram +const registry = setup({ + use: { counter }, +}); -### Configuration +registry.start(); +``` -#### Runner Pool +Configure the engine endpoint in the environment: -Use `RIVET_RUNNER` to assign runners to a pool. This lets you control which runners handle specific actors. +```bash +RIVET_ENDPOINT=https://... +RIVET_TOKEN=... +RIVET_NAMESPACE=... +RIVET_POOL=default +RIVET_VERSION=123 +``` + +### Pool Configuration + +Use `RIVET_POOL` (or the `pool` config field) to assign envoys to a pool. Pools let you route specific actors to specific envoy groups: ```bash -RIVET_RUNNER=gpu-workers +RIVET_POOL=workers ``` ```typescript import { actor, setup } from "rivetkit"; -const myActor = actor({ state: {}, actions: {} }); +const counter = actor({ state: {}, actions: {} }); const registry = setup({ - use: { myActor }, - envoy: { - poolName: "gpu-workers", - }, + pool: "workers", + use: { counter }, }); + +registry.start(); ``` -## Comparison +## Defaults + +`setup({})` only defines actors and registry-wide config. The method you call chooses how the app runs: + +- `registry.start()` starts a persistent outbound envoy. +- `registry.fetchHandler({ path })` returns a serverless fetch function. +- `registry.listen({ port, path })` starts the native HTTP server and serves `./public` by default. + +## Entrypoint Reference + +| Entrypoint | Mode | Purpose | +| --- | --- | --- | +| `registry.start()` | Envoy | Start a persistent outbound envoy. | +| `registry.fetchHandler({ path })` | Serverless | Return a fetch function for Workers, Vercel, Hono, Deno, Bun, and similar runtimes. | +| `registry.listen({ port, path })` | Native server | Start RivetKit's native HTTP server and static file server. | -| Mode | Method | Use Case | -|------|--------|----------| -| Auto | `registry.start()` | Simplest setup. Starts server, serves static files, and runs actors. | -| Serverless | `registry.serve()` | Fetch handler for serverless platforms | -| Serverless | `registry.handler()` | Integrating with existing routers (Hono, Elysia, etc.) | -| Runner | `registry.startEnvoy()` | Long-running processes without HTTP endpoints | +Only call one entrypoint per registry. The entrypoint method selects the mode. +RivetKit throws if you call another entrypoint on the same registry, for example `registry.start()` after `registry.fetchHandler(...)`. diff --git a/website/src/content/posts/2026-05-06-rivet-2-3-0/page.mdx b/website/src/content/posts/2026-05-06-rivet-2-3-0/page.mdx new file mode 100644 index 0000000000..7c3c181b5e --- /dev/null +++ b/website/src/content/posts/2026-05-06-rivet-2-3-0/page.mdx @@ -0,0 +1,89 @@ +--- +author: nicholas-kissel +published: "2026-05-06" +category: changelog +keywords: ["rivetkit", "migration", "runtime-modes"] +title: "Rivet 2.3.0" +description: "Migration notes for the RivetKit registry entrypoint cleanup." +unpublished: true +--- + +This draft only covers the RivetKit registry entrypoint migration for Rivet 2.3.0. + +## Environment Variables + +- Replace `RIVET_ENVOY_VERSION` with `RIVET_VERSION`. +- Remove `RIVET_ENVOY_KIND`. Runtime mode is selected by the registry entrypoint method. +- Replace `RIVET_POOL_NAME` with `RIVET_POOL`. +- Remove `RIVET_RUN_ENGINE` and `RIVET_RUN_ENGINE_VERSION`. Use `dev.startEngine` on `registry.fetchHandler(...)` or `registry.listen(...)` for manual local engine control. +- Remove `RIVET_TOTAL_SLOTS`. +- Replace `RIVET_RUNNER` and `RIVET_RUNNER_*` with `RIVET_POOL` and `RIVET_VERSION`. + +RivetKit now throws if a value is set both in the environment and in `setup()`. + +## Setup Config + +- Replace `envoy.poolName` with top-level `pool`. +- Replace `envoy.version` with top-level `version`. +- Remove `envoy.envoyKey` and `RIVET_ENVOY_KEY`. Envoy keys are managed by RivetKit. +- Move `configurePool` or `devServerless` behavior to `dev` on `registry.fetchHandler(...)` or `registry.listen(...)`. +- Remove `mode`, `startEngine`, `staticDir`, `serverless`, and `envoy` from `setup(...)`. + +## Entrypoints + +- Replace `registry.startEnvoy()` and `registry.start()` envoy usage with `registry.start()`. +- Replace `registry.handler(request)` with a reusable `const handler = registry.fetchHandler({ path }); handler(request)`. +- Replace `registry.serve()` with `const handler = registry.fetchHandler({ path }); export default { fetch: handler }`. +- Use `registry.listen({ port, path, static, dev })` when you want RivetKit to start the native HTTP server. +- Only call one entrypoint per registry. Do not combine `registry.start()` with `registry.fetchHandler(...)` or `registry.listen(...)`; RivetKit now throws when it detects mixed entrypoints. + +## Before And After + +### Local Default + +```typescript +const registry = setup({ use: { counter } }); +registry.start(); +``` + +### Production Serverless + +```typescript +const registry = setup({ + use: { counter }, +}); + +const handler = registry.fetchHandler({ + path: "/api/rivet", +}); + +export default { fetch: handler }; +``` + +### Explicit Envoy + +```bash +RIVET_ENDPOINT=https://... +RIVET_POOL=default +RIVET_VERSION=123 +``` + +```typescript +const registry = setup({ use: { counter } }); +registry.start(); +``` + +### Local Dev Serverless + +```typescript +const registry = setup({ + use: { counter }, +}); + +const handler = registry.fetchHandler({ + path: "/api/rivet", + dev: "http://127.0.0.1:3000/api/rivet", +}); + +export default { fetch: handler }; +```