diff --git a/engine/sdks/typescript/runner-protocol/src/index.ts b/engine/sdks/typescript/runner-protocol/src/index.ts
index 343802861a..c6405665cb 100644
--- a/engine/sdks/typescript/runner-protocol/src/index.ts
+++ b/engine/sdks/typescript/runner-protocol/src/index.ts
@@ -1,4 +1,4 @@
-
+import assert from "node:assert"
import * as bare from "@bare-ts/lib"
const DEFAULT_CONFIG = /* @__PURE__ */ bare.Config({})
@@ -1906,9 +1906,3 @@ export function decodeToServerlessServer(bytes: Uint8Array): ToServerlessServer
}
return result
}
-
-
-function assert(condition: boolean, message?: string): asserts condition {
- if (!condition) throw new Error(message ?? "Assertion failed")
-}
-
diff --git a/examples/cloudflare-workers-inline-client/README.md b/examples/cloudflare-workers-inline-client/README.md
new file mode 100644
index 0000000000..6b1ecf9c3e
--- /dev/null
+++ b/examples/cloudflare-workers-inline-client/README.md
@@ -0,0 +1,27 @@
+# Cloudflare Workers Inline Client Example
+
+Simple example demonstrating accessing Rivet Actors via Cloudflare Workers without exposing a public API. This uses the `createInlineClient` function to connect directly to your Durable Object.
+
+## Getting Started
+
+Install dependencies:
+
+```sh
+pnpm install
+```
+
+Start the development server:
+
+```sh
+pnpm run dev
+```
+
+In a separate terminal, test the endpoint:
+
+```sh
+pnpm run client
+```
+
+## License
+
+Apache 2.0
diff --git a/examples/cloudflare-workers-inline-client/package.json b/examples/cloudflare-workers-inline-client/package.json
new file mode 100644
index 0000000000..1cc3163a45
--- /dev/null
+++ b/examples/cloudflare-workers-inline-client/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "example-cloudflare-workers-inline-client",
+ "version": "2.0.21",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "wrangler dev",
+ "deploy": "wrangler deploy",
+ "check-types": "tsc --noEmit",
+ "client": "tsx scripts/client.ts"
+ },
+ "devDependencies": {
+ "@cloudflare/workers-types": "^4.20250129.0",
+ "@types/node": "^22.13.9",
+ "tsx": "^3.12.7",
+ "typescript": "^5.5.2",
+ "wrangler": "^4.22.0"
+ },
+ "dependencies": {
+ "rivetkit": "workspace:*",
+ "@rivetkit/cloudflare-workers": "workspace:*"
+ },
+ "stableVersion": "0.8.0"
+}
diff --git a/examples/cloudflare-workers-inline-client/scripts/client.ts b/examples/cloudflare-workers-inline-client/scripts/client.ts
new file mode 100644
index 0000000000..560891aa83
--- /dev/null
+++ b/examples/cloudflare-workers-inline-client/scripts/client.ts
@@ -0,0 +1,38 @@
+const baseUrl = process.env.BASE_URL ?? "http://localhost:8787";
+
+async function main() {
+ console.log("🚀 Cloudflare Workers Client Demo");
+
+ try {
+ // Increment counter 'demo'
+ console.log("Incrementing counter 'demo'...");
+ const response1 = await fetch(`${baseUrl}/increment/demo`, {
+ method: "POST",
+ });
+ const result1 = await response1.text();
+ console.log(result1);
+
+ // Increment counter 'demo' again
+ console.log("Incrementing counter 'demo' again...");
+ const response2 = await fetch(`${baseUrl}/increment/demo`, {
+ method: "POST",
+ });
+ const result2 = await response2.text();
+ console.log(result2);
+
+ // Increment counter 'another'
+ console.log("Incrementing counter 'another'...");
+ const response3 = await fetch(`${baseUrl}/increment/another`, {
+ method: "POST",
+ });
+ const result3 = await response3.text();
+ console.log(result3);
+
+ console.log("✅ Demo completed!");
+ } catch (error) {
+ console.error("❌ Error:", error);
+ process.exit(1);
+ }
+}
+
+main().catch(console.error);
diff --git a/examples/cloudflare-workers-inline-client/src/index.ts b/examples/cloudflare-workers-inline-client/src/index.ts
new file mode 100644
index 0000000000..a2f225baf6
--- /dev/null
+++ b/examples/cloudflare-workers-inline-client/src/index.ts
@@ -0,0 +1,29 @@
+import { createInlineClient } from "@rivetkit/cloudflare-workers";
+import { registry } from "./registry";
+
+const { client, ActorHandler } = createInlineClient(registry);
+
+// IMPORTANT: Your Durable Object must be exported here
+export { ActorHandler };
+
+export default {
+ fetch: async (request) => {
+ const url = new URL(request.url);
+
+ if (
+ request.method === "POST" &&
+ url.pathname.startsWith("/increment/")
+ ) {
+ const name = url.pathname.slice("/increment/".length);
+
+ const counter = client.counter.getOrCreate(name);
+ const newCount = await counter.increment(1);
+
+ return new Response(`New Count: ${newCount}`, {
+ headers: { "Content-Type": "text/plain" },
+ });
+ }
+
+ return new Response("Not Found", { status: 404 });
+ },
+} satisfies ExportedHandler;
diff --git a/examples/cloudflare-workers-inline-client/src/registry.ts b/examples/cloudflare-workers-inline-client/src/registry.ts
new file mode 100644
index 0000000000..4afe732a3c
--- /dev/null
+++ b/examples/cloudflare-workers-inline-client/src/registry.ts
@@ -0,0 +1,16 @@
+import { actor, setup } from "rivetkit";
+
+export const counter = actor({
+ state: { count: 0 },
+ actions: {
+ increment: (c, x: number) => {
+ c.state.count += x;
+ c.broadcast("newCount", c.state.count);
+ return c.state.count;
+ },
+ },
+});
+
+export const registry = setup({
+ use: { counter },
+});
diff --git a/examples/cloudflare-workers-inline-client/tsconfig.json b/examples/cloudflare-workers-inline-client/tsconfig.json
new file mode 100644
index 0000000000..f4bdc4cddf
--- /dev/null
+++ b/examples/cloudflare-workers-inline-client/tsconfig.json
@@ -0,0 +1,43 @@
+{
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig.json to read more about this file */
+
+ /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ "target": "esnext",
+ /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ "lib": ["esnext"],
+ /* Specify what JSX code is generated. */
+ "jsx": "react-jsx",
+
+ /* Specify what module code is generated. */
+ "module": "esnext",
+ /* Specify how TypeScript looks up a file from a given module specifier. */
+ "moduleResolution": "bundler",
+ /* Specify type package names to be included without being referenced in a source file. */
+ "types": ["@cloudflare/workers-types"],
+ /* Enable importing .json files */
+ "resolveJsonModule": true,
+
+ /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
+ "allowJs": true,
+ /* Enable error reporting in type-checked JavaScript files. */
+ "checkJs": false,
+
+ /* Disable emitting files from a compilation. */
+ "noEmit": true,
+
+ /* Ensure that each file can be safely transpiled without relying on other imports. */
+ "isolatedModules": true,
+ /* Allow 'import x from y' when a module doesn't have a default export. */
+ "allowSyntheticDefaultImports": true,
+ /* Ensure that casing is correct in imports. */
+ "forceConsistentCasingInFileNames": true,
+
+ /* Enable all strict type-checking options. */
+ "strict": true,
+
+ /* Skip type checking all .d.ts files. */
+ "skipLibCheck": true
+ },
+ "include": ["src/**/*"]
+}
diff --git a/examples/cloudflare-workers-inline-client/turbo.json b/examples/cloudflare-workers-inline-client/turbo.json
new file mode 100644
index 0000000000..29d4cb2625
--- /dev/null
+++ b/examples/cloudflare-workers-inline-client/turbo.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://turbo.build/schema.json",
+ "extends": ["//"]
+}
diff --git a/examples/cloudflare-workers-inline-client/wrangler.json b/examples/cloudflare-workers-inline-client/wrangler.json
new file mode 100644
index 0000000000..f5b84c4ef6
--- /dev/null
+++ b/examples/cloudflare-workers-inline-client/wrangler.json
@@ -0,0 +1,30 @@
+{
+ "name": "rivetkit-cloudflare-workers-example",
+ "main": "src/index.ts",
+ "compatibility_date": "2025-01-20",
+ "compatibility_flags": ["nodejs_compat"],
+ "migrations": [
+ {
+ "tag": "v1",
+ "new_sqlite_classes": ["ActorHandler"]
+ }
+ ],
+ "durable_objects": {
+ "bindings": [
+ {
+ "name": "ACTOR_DO",
+ "class_name": "ActorHandler"
+ }
+ ]
+ },
+ "kv_namespaces": [
+ {
+ "binding": "ACTOR_KV",
+ "id": "example_namespace",
+ "preview_id": "example_namespace_preview"
+ }
+ ],
+ "observability": {
+ "enabled": true
+ }
+}
diff --git a/examples/cloudflare-workers/src/registry.ts b/examples/cloudflare-workers/src/registry.ts
index 24277ebeb8..4afe732a3c 100644
--- a/examples/cloudflare-workers/src/registry.ts
+++ b/examples/cloudflare-workers/src/registry.ts
@@ -1,7 +1,7 @@
import { actor, setup } from "rivetkit";
export const counter = actor({
- state: { count: 0, connectionCount: 0, messageCount: 0 },
+ state: { count: 0 },
actions: {
increment: (c, x: number) => {
c.state.count += x;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 04665f8cac..2700ec6581 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -46,6 +46,12 @@ importers:
specifier: ^8.8.5
version: 8.8.5
+ engine:
+ dependencies:
+ '@vbare/compiler':
+ specifier: ^0.0.3
+ version: 0.0.3(@bare-ts/lib@0.4.0)
+
engine/docker/template:
dependencies:
'@types/js-yaml':
@@ -493,6 +499,31 @@ importers:
specifier: ^4.22.0
version: 4.44.0(@cloudflare/workers-types@4.20251014.0)
+ examples/cloudflare-workers-inline-client:
+ dependencies:
+ '@rivetkit/cloudflare-workers':
+ specifier: workspace:*
+ version: link:../../rivetkit-typescript/packages/cloudflare-workers
+ rivetkit:
+ specifier: workspace:*
+ version: link:../../rivetkit-typescript/packages/rivetkit
+ devDependencies:
+ '@cloudflare/workers-types':
+ specifier: ^4.20250129.0
+ version: 4.20251014.0
+ '@types/node':
+ specifier: ^22.13.9
+ version: 22.18.1
+ tsx:
+ specifier: ^3.12.7
+ version: 3.14.0
+ typescript:
+ specifier: ^5.5.2
+ version: 5.9.2
+ wrangler:
+ specifier: ^4.22.0
+ version: 4.44.0(@cloudflare/workers-types@4.20251014.0)
+
examples/counter:
devDependencies:
'@types/node':
@@ -2331,6 +2362,9 @@ importers:
'@bare-ts/tools':
specifier: ^0.13.0
version: 0.13.0(@bare-ts/lib@0.3.0)
+ '@biomejs/biome':
+ specifier: ^2.2.3
+ version: 2.2.3
'@hono/node-server':
specifier: ^1.18.2
version: 1.19.1(hono@4.9.8)
@@ -3250,6 +3284,13 @@ packages:
peerDependencies:
'@bare-ts/lib': '>=0.3.0 <=0.4.0'
+ '@bare-ts/tools@0.16.1':
+ resolution: {integrity: sha512-eKXTnVqzuKDxr1ozKsFSZfM1wcN4g/iMRnG9GB2fA8oyUcHxwokJC50CANfuSLe6rLnjhZ8Ave1Y2TnZqUqGcQ==}
+ engines: {node: '>=20.0.0'}
+ hasBin: true
+ peerDependencies:
+ '@bare-ts/lib': '>=0.3.0 <=0.4.0'
+
'@base-org/account@2.0.1':
resolution: {integrity: sha512-tySVNx+vd6XEynZL0uvB10uKiwnAfThr8AbKTwILVG86mPbLAhEOInQIk+uDnvpTvfdUhC1Bi5T/46JvFoLZQQ==}
@@ -7021,6 +7062,11 @@ packages:
peerDependencies:
'@urql/core': ^5.0.0
+ '@vbare/compiler@0.0.3':
+ resolution: {integrity: sha512-Dhz0iwYjIhyGAPsNpiqDmDqgwLXfEonjFJLVQ0m/s4Tt9CsTjY0WV3KiQtJi5BdPt9481HR+0uwExH36FuuR2A==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
'@vitejs/plugin-react@4.7.0':
resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -14436,6 +14482,10 @@ snapshots:
'@bare-ts/lib': 0.4.0
commander: 11.1.0
+ '@bare-ts/tools@0.16.1(@bare-ts/lib@0.4.0)':
+ dependencies:
+ '@bare-ts/lib': 0.4.0
+
'@base-org/account@2.0.1(@types/react@19.2.2)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.5.0(react@19.1.1))(zod@3.25.76)':
dependencies:
'@noble/hashes': 1.4.0
@@ -18687,6 +18737,13 @@ snapshots:
'@urql/core': 5.2.0
wonka: 6.3.5
+ '@vbare/compiler@0.0.3(@bare-ts/lib@0.4.0)':
+ dependencies:
+ '@bare-ts/tools': 0.16.1(@bare-ts/lib@0.4.0)
+ commander: 11.1.0
+ transitivePeerDependencies:
+ - '@bare-ts/lib'
+
'@vitejs/plugin-react@4.7.0(vite@5.4.20(@types/node@20.19.13)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0))':
dependencies:
'@babel/core': 7.28.4
diff --git a/rivetkit-openapi/openapi.json b/rivetkit-openapi/openapi.json
index 90803b4757..4ab454fa07 100644
--- a/rivetkit-openapi/openapi.json
+++ b/rivetkit-openapi/openapi.json
@@ -113,6 +113,7 @@
},
"put": {
"requestBody": {
+ "required": true,
"content": {
"application/json": {
"schema": {
@@ -225,6 +226,7 @@
},
"post": {
"requestBody": {
+ "required": true,
"content": {
"application/json": {
"schema": {
@@ -385,283 +387,6 @@
}
}
}
- },
- "/gateway/{actorId}/health": {
- "get": {
- "parameters": [
- {
- "name": "actorId",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The ID of the actor to target"
- }
- ],
- "responses": {
- "200": {
- "description": "Health check",
- "content": {
- "text/plain": {
- "schema": {
- "type": "string"
- }
- }
- }
- }
- }
- }
- },
- "/gateway/{actorId}/action/{action}": {
- "post": {
- "parameters": [
- {
- "name": "actorId",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The ID of the actor to target"
- },
- {
- "name": "action",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The name of the action to execute"
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "type": "object",
- "properties": {
- "args": {}
- },
- "additionalProperties": false
- }
- }
- }
- },
- "responses": {
- "200": {
- "description": "Action executed successfully",
- "content": {
- "application/json": {
- "schema": {
- "type": "object",
- "properties": {
- "output": {}
- },
- "additionalProperties": false
- }
- }
- }
- },
- "400": {
- "description": "Invalid action"
- },
- "500": {
- "description": "Internal error"
- }
- }
- }
- },
- "/gateway/{actorId}/request/{path}": {
- "get": {
- "parameters": [
- {
- "name": "actorId",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The ID of the actor to target"
- },
- {
- "name": "path",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The HTTP path to forward to the actor"
- }
- ],
- "responses": {
- "200": {
- "description": "Response from actor's raw HTTP handler"
- }
- }
- },
- "post": {
- "parameters": [
- {
- "name": "actorId",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The ID of the actor to target"
- },
- {
- "name": "path",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The HTTP path to forward to the actor"
- }
- ],
- "responses": {
- "200": {
- "description": "Response from actor's raw HTTP handler"
- }
- }
- },
- "put": {
- "parameters": [
- {
- "name": "actorId",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The ID of the actor to target"
- },
- {
- "name": "path",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The HTTP path to forward to the actor"
- }
- ],
- "responses": {
- "200": {
- "description": "Response from actor's raw HTTP handler"
- }
- }
- },
- "delete": {
- "parameters": [
- {
- "name": "actorId",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The ID of the actor to target"
- },
- {
- "name": "path",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The HTTP path to forward to the actor"
- }
- ],
- "responses": {
- "200": {
- "description": "Response from actor's raw HTTP handler"
- }
- }
- },
- "patch": {
- "parameters": [
- {
- "name": "actorId",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The ID of the actor to target"
- },
- {
- "name": "path",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The HTTP path to forward to the actor"
- }
- ],
- "responses": {
- "200": {
- "description": "Response from actor's raw HTTP handler"
- }
- }
- },
- "head": {
- "parameters": [
- {
- "name": "actorId",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The ID of the actor to target"
- },
- {
- "name": "path",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The HTTP path to forward to the actor"
- }
- ],
- "responses": {
- "200": {
- "description": "Response from actor's raw HTTP handler"
- }
- }
- },
- "options": {
- "parameters": [
- {
- "name": "actorId",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The ID of the actor to target"
- },
- {
- "name": "path",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string"
- },
- "description": "The HTTP path to forward to the actor"
- }
- ],
- "responses": {
- "200": {
- "description": "Response from actor's raw HTTP handler"
- }
- }
- }
}
}
}
\ No newline at end of file
diff --git a/rivetkit-typescript/packages/cloudflare-workers/src/handler.ts b/rivetkit-typescript/packages/cloudflare-workers/src/handler.ts
index 2d186564ce..3b87959152 100644
--- a/rivetkit-typescript/packages/cloudflare-workers/src/handler.ts
+++ b/rivetkit-typescript/packages/cloudflare-workers/src/handler.ts
@@ -1,11 +1,11 @@
import { env } from "cloudflare:workers";
-import type { Registry, RunConfig } from "rivetkit";
+import type { Client, Registry, RunConfig } from "rivetkit";
import {
type ActorHandlerInterface,
createActorDurableObject,
type DurableObjectConstructor,
} from "./actor-handler-do";
-import { ConfigSchema, type InputConfig } from "./config";
+import { type Config, ConfigSchema, type InputConfig } from "./config";
import { CloudflareActorsManagerDriver } from "./manager-driver";
import { upgradeWebSocket } from "./websocket";
@@ -24,15 +24,35 @@ export function getCloudflareAmbientEnv(): Bindings {
return env as unknown as Bindings;
}
-interface Handler {
+export interface InlineOutput> {
+ /** Client to communicate with the actors. */
+ client: Client;
+
+ /** Fetch handler to manually route requests to the Rivet manager API. */
+ fetch: (request: Request, ...args: any) => Response | Promise;
+
+ config: Config;
+
+ ActorHandler: DurableObjectConstructor;
+}
+
+export interface HandlerOutput {
handler: ExportedHandler;
ActorHandler: DurableObjectConstructor;
}
-export function createHandler>(
+/**
+ * Creates an inline client for accessing Rivet Actors privately without a public manager API.
+ *
+ * If you want to expose a public manager API, either:
+ *
+ * - Use `createHandler` to expose the Rivet API on `/rivet`
+ * - Forward Rivet API requests to `InlineOutput::fetch`
+ */
+export function createInlineClient>(
registry: R,
inputConfig?: InputConfig,
-): Handler {
+): InlineOutput {
// HACK: Cloudflare does not support using `crypto.randomUUID()` before start, so we pass a default value
//
// Runner key is not used on Cloudflare
@@ -57,16 +77,34 @@ export function createHandler>(
const ActorHandler = createActorDurableObject(registry, runConfig);
// Create server
- const serverOutputPromise = registry.start(runConfig);
+ const { client, fetch } = registry.start(runConfig);
+
+ return { client, fetch, config, ActorHandler };
+}
+
+/**
+ * Creates a handler to be exported from a Cloudflare Worker.
+ *
+ * This will automatically expose the Rivet manager API on `/rivet`.
+ *
+ * This includes a `fetch` handler and `ActorHandler` Durable Object.
+ */
+export function createHandler>(
+ registry: R,
+ inputConfig?: InputConfig,
+): HandlerOutput {
+ const { client, fetch, config, ActorHandler } = createInlineClient(
+ registry,
+ inputConfig,
+ );
// Create Cloudflare handler
const handler = {
fetch: async (request, cfEnv, ctx) => {
- const serverOutput = await serverOutputPromise;
const url = new URL(request.url);
// Inject Rivet env
- const env = Object.assign({ RIVET: serverOutput.client }, cfEnv);
+ const env = Object.assign({ RIVET: client }, cfEnv);
// Mount Rivet manager API
if (url.pathname.startsWith(config.managerPath)) {
@@ -75,7 +113,7 @@ export function createHandler>(
);
url.pathname = strippedPath;
const modifiedRequest = new Request(url.toString(), request);
- return serverOutput.fetch(modifiedRequest, env, ctx);
+ return fetch(modifiedRequest, env, ctx);
}
if (config.fetch) {
diff --git a/rivetkit-typescript/packages/cloudflare-workers/src/mod.ts b/rivetkit-typescript/packages/cloudflare-workers/src/mod.ts
index 12ad512c14..40129e55fe 100644
--- a/rivetkit-typescript/packages/cloudflare-workers/src/mod.ts
+++ b/rivetkit-typescript/packages/cloudflare-workers/src/mod.ts
@@ -1,4 +1,11 @@
export type { Client } from "rivetkit";
export type { DriverContext } from "./actor-driver";
+export { createActorDurableObject } from "./actor-handler-do";
export type { InputConfig as Config } from "./config";
-export { type Bindings, createHandler } from "./handler";
+export {
+ type Bindings,
+ createHandler,
+ createInlineClient,
+ HandlerOutput,
+ InlineOutput,
+} from "./handler";