diff --git a/docs/workers/quickstart.mdx b/docs/workers/quickstart.mdx index e91268eb6..04be06d7c 100644 --- a/docs/workers/quickstart.mdx +++ b/docs/workers/quickstart.mdx @@ -42,14 +42,18 @@ export const registry = setup({ import { registry } from "./registry"; import { Hono } from "hono"; import { serve } from "@hono/node-server"; +import { createNodeWebSocket } from '@hono/node-ws' + +// Setup server +const app = new Hono(); // Start RivetKit // // State is stored in memory, this can be configured later -const { client, hono } = registry.run(); - -// Setup server -const app = new Hono(); +const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app }) // TODO: do this before app +const { client, hono } = registry.run({ + getUpgradeWebSocket: () => upgradeWebSocket, +}); // Expose RivetKit to the frontend (optional) app.route("/registry", hono); @@ -67,6 +71,7 @@ app.post("/increment/:name", async (c) => { serve({ fetch: app.fetch, port: 8080 }, (x) => console.log("Listening at http://localhost:8080"), ); +injectWebSocket(server) ``` ```ts Express.js @@ -77,6 +82,37 @@ TODO TODO ``` +```ts Hono +import { registry } from "./registry"; +import { Hono } from "hono"; + +// Start RivetKit +// +// State is stored in memory, this can be configured later +const { client, serve } = registry.server(); + +// Setup server +const app = new Hono(); + +// Example endpoint +app.post("/increment/:name", async (c) => { + const name = c.req.param("name"); + + // Communicate with actor + const counter = client.counter.getOrCreate(name); + const newCount = await counter.increment(1); + + return c.text(`New Count: ${newCount}`); +}); + +// Start server +serve(app); +``` + +TODO: How to serve without registry helper + +TODO: Why we need to use our own custom serve fn + diff --git a/examples/better-auth/package.json b/examples/better-auth/package.json index fcde06536..600b9b8b4 100644 --- a/examples/better-auth/package.json +++ b/examples/better-auth/package.json @@ -25,17 +25,11 @@ }, "dependencies": { "@hono/node-server": "^1.14.4", - "@rivetkit/memory": "workspace:0.9.0-rc.1", "@rivetkit/react": "workspace:0.9.0-rc.1", "better-auth": "^1.0.1", "hono": "^4.7.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, - "example": { - "platforms": [ - "*" - ] - }, "stableVersion": "0.8.0" } diff --git a/examples/better-auth/src/backend/auth.ts b/examples/better-auth/src/backend/auth.ts index c735ba539..141cff093 100644 --- a/examples/better-auth/src/backend/auth.ts +++ b/examples/better-auth/src/backend/auth.ts @@ -1,20 +1,20 @@ -import { betterAuth } from "better-auth"; -import { sqliteAdapter } from "@better-auth/sqlite"; -import Database from "better-sqlite3"; - -const db = new Database("./auth.db"); - -export const auth = betterAuth({ - database: sqliteAdapter(db), - emailAndPassword: { - enabled: true, - }, - session: { - expiresIn: 60 * 60 * 24 * 7, // 7 days - updateAge: 60 * 60 * 24, // 1 day (every day the session expiry is updated) - }, - plugins: [], -}); - -export type Session = typeof auth.$Infer.Session; -export type User = typeof auth.$Infer.User; \ No newline at end of file +// import { betterAuth } from "better-auth"; +// import { sqliteAdapter } from "@better-auth/sqlite"; +// import Database from "better-sqlite3"; +// +// const db = new Database("./auth.db"); +// +// export const auth = betterAuth({ +// database: sqliteAdapter(db), +// emailAndPassword: { +// enabled: true, +// }, +// session: { +// expiresIn: 60 * 60 * 24 * 7, // 7 days +// updateAge: 60 * 60 * 24, // 1 day (every day the session expiry is updated) +// }, +// plugins: [], +// }); +// +// export type Session = typeof auth.$Infer.Session; +// export type User = typeof auth.$Infer.User; diff --git a/examples/better-auth/src/backend/registry.ts b/examples/better-auth/src/backend/registry.ts index bca18a372..daceb8bbd 100644 --- a/examples/better-auth/src/backend/registry.ts +++ b/examples/better-auth/src/backend/registry.ts @@ -1,48 +1,48 @@ -import { worker, setup } from "@rivetkit/worker"; -import { auth, type Session, type User } from "./auth"; - -export const chatRoom = worker({ - onAuth: async (c) => { - const authResult = await auth.api.getSession({ - headers: c.req.headers, - }); - - if (!authResult?.session || !authResult?.user) { - throw new Error("Unauthorized"); - } - - return { - userId: authResult.user.id, - user: authResult.user, - session: authResult.session, - }; - }, - state: { - messages: [] as Array<{ id: string; userId: string; username: string; message: string; timestamp: number }> - }, - actions: { - sendMessage: (c, message: string) => { - const newMessage = { - id: crypto.randomUUID(), - userId: c.auth.userId, - username: c.auth.user.email, - message, - timestamp: Date.now(), - }; - - c.state.messages.push(newMessage); - c.broadcast("newMessage", newMessage); - - return newMessage; - }, - getMessages: (c) => { - return c.state.messages; - }, - }, -}); - -export const registry = setup({ - workers: { chatRoom }, -}); - -export type Registry = typeof registry; +// import { worker, setup } from "@rivetkit/worker"; +// import { auth, type Session, type User } from "./auth"; +// +// export const chatRoom = worker({ +// onAuth: async (c) => { +// const authResult = await auth.api.getSession({ +// headers: c.req.headers, +// }); +// +// if (!authResult?.session || !authResult?.user) { +// throw new Error("Unauthorized"); +// } +// +// return { +// userId: authResult.user.id, +// user: authResult.user, +// session: authResult.session, +// }; +// }, +// state: { +// messages: [] as Array<{ id: string; userId: string; username: string; message: string; timestamp: number }> +// }, +// actions: { +// sendMessage: (c, message: string) => { +// const newMessage = { +// id: crypto.randomUUID(), +// userId: c.auth.userId, +// username: c.auth.user.email, +// message, +// timestamp: Date.now(), +// }; +// +// c.state.messages.push(newMessage); +// c.broadcast("newMessage", newMessage); +// +// return newMessage; +// }, +// getMessages: (c) => { +// return c.state.messages; +// }, +// }, +// }); +// +// export const registry = setup({ +// workers: { chatRoom }, +// }); +// +// export type Registry = typeof registry; diff --git a/examples/better-auth/src/backend/server.ts b/examples/better-auth/src/backend/server.ts index 241bf8472..de9d06abe 100644 --- a/examples/better-auth/src/backend/server.ts +++ b/examples/better-auth/src/backend/server.ts @@ -1,55 +1,54 @@ -import { registry } from "./registry"; -import { auth } from "./auth"; -import { Hono } from "hono"; -import { serve } from "@hono/node-server"; -import { createMemoryDriver } from "@rivetkit/memory"; - -// Setup router -const app = new Hono(); - -// Start RivetKit -const { client, hono } = registry.run({ - driver: createMemoryDriver(), - cors: { - // IMPORTANT: Configure origins in production - origin: "*", - }, -}); - -// Mount Better Auth routes -app.on(["GET", "POST"], "/api/auth/**", (c) => auth.handler(c.req.raw)); - -// Expose RivetKit to the frontend -app.route("/registry", hono); - -// Example HTTP endpoint to join chat room -app.post("/api/join-room/:roomId", async (c) => { - const roomId = c.req.param("roomId"); - - // Verify authentication - const authResult = await auth.api.getSession({ - headers: c.req.header(), - }); - - if (!authResult?.session || !authResult?.user) { - return c.json({ error: "Unauthorized" }, 401); - } - - try { - const room = client.chatRoom.getOrCreate(roomId); - const messages = await room.getMessages(); - - return c.json({ - success: true, - roomId, - messages, - user: authResult.user - }); - } catch (error) { - return c.json({ error: "Failed to join room" }, 500); - } -}); - -serve({ fetch: app.fetch, port: 6420 }, () => - console.log("Listening at http://localhost:6420"), -); \ No newline at end of file +// import { registry } from "./registry"; +// import { auth } from "./auth"; +// import { Hono } from "hono"; +// import { serve } from "@hono/node-server"; +// +// // Setup router +// const app = new Hono(); +// +// // Start RivetKit +// const { client, hono } = registry.run({ +// driver: createMemoryDriver(), +// cors: { +// // IMPORTANT: Configure origins in production +// origin: "*", +// }, +// }); +// +// // Mount Better Auth routes +// app.on(["GET", "POST"], "/api/auth/**", (c) => auth.handler(c.req.raw)); +// +// // Expose RivetKit to the frontend +// app.route("/registry", hono); +// +// // Example HTTP endpoint to join chat room +// app.post("/api/join-room/:roomId", async (c) => { +// const roomId = c.req.param("roomId"); +// +// // Verify authentication +// const authResult = await auth.api.getSession({ +// headers: c.req.header(), +// }); +// +// if (!authResult?.session || !authResult?.user) { +// return c.json({ error: "Unauthorized" }, 401); +// } +// +// try { +// const room = client.chatRoom.getOrCreate(roomId); +// const messages = await room.getMessages(); +// +// return c.json({ +// success: true, +// roomId, +// messages, +// user: authResult.user +// }); +// } catch (error) { +// return c.json({ error: "Failed to join room" }, 500); +// } +// }); +// +// serve({ fetch: app.fetch, port: 6420 }, () => +// console.log("Listening at http://localhost:6420"), +// ); diff --git a/examples/better-auth/src/frontend/App.tsx b/examples/better-auth/src/frontend/App.tsx index 30549a0e3..059b6e758 100644 --- a/examples/better-auth/src/frontend/App.tsx +++ b/examples/better-auth/src/frontend/App.tsx @@ -1,73 +1,73 @@ -import { useState, useEffect } from "react"; -import { authClient } from "./auth-client"; -import { AuthForm } from "./components/AuthForm"; -import { ChatRoom } from "./components/ChatRoom"; - -function App() { - const [user, setUser] = useState<{ id: string; email: string } | null>(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - // Check if user is already authenticated - const checkAuth = async () => { - try { - const session = await authClient.getSession(); - if (session.data?.user) { - setUser(session.data.user); - } - } catch (error) { - console.error("Auth check failed:", error); - } finally { - setLoading(false); - } - }; - - checkAuth(); - }, []); - - const handleAuthSuccess = async () => { - try { - const session = await authClient.getSession(); - if (session.data?.user) { - setUser(session.data.user); - } - } catch (error) { - console.error("Failed to get user after auth:", error); - } - }; - - const handleSignOut = () => { - setUser(null); - }; - - if (loading) { - return ( -
- Loading... -
- ); - } - - return ( -
-
-

- RivetKit with Better Auth -

- - {user ? ( - - ) : ( - - )} -
-
- ); -} - -export default App; \ No newline at end of file +// import { useState, useEffect } from "react"; +// import { authClient } from "./auth-client"; +// import { AuthForm } from "./components/AuthForm"; +// import { ChatRoom } from "./components/ChatRoom"; +// +// function App() { +// const [user, setUser] = useState<{ id: string; email: string } | null>(null); +// const [loading, setLoading] = useState(true); +// +// useEffect(() => { +// // Check if user is already authenticated +// const checkAuth = async () => { +// try { +// const session = await authClient.getSession(); +// if (session.data?.user) { +// setUser(session.data.user); +// } +// } catch (error) { +// console.error("Auth check failed:", error); +// } finally { +// setLoading(false); +// } +// }; +// +// checkAuth(); +// }, []); +// +// const handleAuthSuccess = async () => { +// try { +// const session = await authClient.getSession(); +// if (session.data?.user) { +// setUser(session.data.user); +// } +// } catch (error) { +// console.error("Failed to get user after auth:", error); +// } +// }; +// +// const handleSignOut = () => { +// setUser(null); +// }; +// +// if (loading) { +// return ( +//
+// Loading... +//
+// ); +// } +// +// return ( +//
+//
+//

+// RivetKit with Better Auth +//

+// +// {user ? ( +// +// ) : ( +// +// )} +//
+//
+// ); +// } +// +// export default App; diff --git a/examples/better-auth/src/frontend/auth-client.ts b/examples/better-auth/src/frontend/auth-client.ts index 793a44e51..c3ead35b8 100644 --- a/examples/better-auth/src/frontend/auth-client.ts +++ b/examples/better-auth/src/frontend/auth-client.ts @@ -1,5 +1,5 @@ -import { createAuthClient } from "better-auth/react"; - -export const authClient = createAuthClient({ - baseURL: "http://localhost:6420", -}); \ No newline at end of file +// import { createAuthClient } from "better-auth/react"; +// +// export const authClient = createAuthClient({ +// baseURL: "http://localhost:6420", +// }); diff --git a/examples/better-auth/src/frontend/main.tsx b/examples/better-auth/src/frontend/main.tsx index f5871ecc7..cd443c7ac 100644 --- a/examples/better-auth/src/frontend/main.tsx +++ b/examples/better-auth/src/frontend/main.tsx @@ -1,9 +1,9 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import App from "./App"; - -ReactDOM.createRoot(document.getElementById("root")!).render( - - - , -); \ No newline at end of file +// import React from "react"; +// import ReactDOM from "react-dom/client"; +// import App from "./App"; +// +// ReactDOM.createRoot(document.getElementById("root")!).render( +// +// +// , +// ); diff --git a/examples/chat-room/package.json b/examples/chat-room/package.json index 556d37b79..1924156ff 100644 --- a/examples/chat-room/package.json +++ b/examples/chat-room/package.json @@ -17,13 +17,5 @@ "typescript": "^5.5.2", "vitest": "^3.1.1" }, - "dependencies": { - "@rivetkit/nodejs": "workspace:*" - }, - "example": { - "platforms": [ - "*" - ] - }, "stableVersion": "0.8.0" } diff --git a/examples/chat-room/src/server.ts b/examples/chat-room/src/server.ts index 4bf6ba53b..d3235e055 100644 --- a/examples/chat-room/src/server.ts +++ b/examples/chat-room/src/server.ts @@ -1,4 +1,4 @@ -import { serve } from "@rivetkit/nodejs"; -import { registry } from "./workers/registry"; - -serve(registry); +// import { serve } from "@rivetkit/nodejs"; +// import { registry } from "./workers/registry"; +// +// serve(registry); diff --git a/examples/cloudflare-workers/package.json b/examples/cloudflare-workers/package.json index 1bb44fa84..0ab187a3c 100644 --- a/examples/cloudflare-workers/package.json +++ b/examples/cloudflare-workers/package.json @@ -20,10 +20,5 @@ "dependencies": { "@rivetkit/cloudflare-workers": "workspace:*" }, - "example": { - "platforms": [ - "cloudflare-workers" - ] - }, "stableVersion": "0.8.0" } diff --git a/examples/counter/package.json b/examples/counter/package.json index 9bd64035a..d1b6bf3f2 100644 --- a/examples/counter/package.json +++ b/examples/counter/package.json @@ -15,13 +15,5 @@ "typescript": "^5.7.3", "vitest": "^3.1.1" }, - "dependencies": { - "@rivetkit/nodejs": "workspace:*" - }, - "example": { - "platforms": [ - "*" - ] - }, "stableVersion": "0.8.0" } diff --git a/examples/counter/src/server.ts b/examples/counter/src/server.ts index 4bf6ba53b..d3235e055 100644 --- a/examples/counter/src/server.ts +++ b/examples/counter/src/server.ts @@ -1,4 +1,4 @@ -import { serve } from "@rivetkit/nodejs"; -import { registry } from "./workers/registry"; - -serve(registry); +// import { serve } from "@rivetkit/nodejs"; +// import { registry } from "./workers/registry"; +// +// serve(registry); diff --git a/examples/elysia/package.json b/examples/elysia/package.json index b8899a345..4d96c884f 100644 --- a/examples/elysia/package.json +++ b/examples/elysia/package.json @@ -13,14 +13,10 @@ "typescript": "^5.5.2" }, "dependencies": { - "@rivetkit/memory": "workspace:0.9.0-rc.1", "@rivetkit/react": "workspace:0.9.0-rc.1", "elysia": "^1.3.5", "react": "^18.2.0", "react-dom": "^18.2.0" }, - "example": { - "platforms": ["*"] - }, "stableVersion": "0.8.0" } diff --git a/examples/elysia/src/server.ts b/examples/elysia/src/server.ts index e3eb13286..d51532385 100644 --- a/examples/elysia/src/server.ts +++ b/examples/elysia/src/server.ts @@ -1,25 +1,25 @@ -import { registry } from "./registry"; -import { Elysia } from "elysia"; -import { createMemoryDriver } from "@rivetkit/memory"; - -// Start RivetKit -const { client, handler } = registry.run({ - driver: createMemoryDriver(), -}); - -// Setup router -const app = new Elysia() - // Expose RivetKit to the frontend (optional) - .mount("/registry", handler) - // Example HTTP endpoint - .post("/increment/:name", async ({ params }) => { - const name = params.name; - - const counter = client.counter.getOrCreate(name); - const newCount = await counter.increment(1); - - return `New Count: ${newCount}`; - }) - .listen(6420); - -console.log("Listening at http://localhost:6420"); +// import { registry } from "./registry"; +// import { Elysia } from "elysia"; +// import { createMemoryDriver } from "@rivetkit/memory"; +// +// // Start RivetKit +// const { client, handler } = registry.run({ +// driver: createMemoryDriver(), +// }); +// +// // Setup router +// const app = new Elysia() +// // Expose RivetKit to the frontend (optional) +// .mount("/registry", handler) +// // Example HTTP endpoint +// .post("/increment/:name", async ({ params }) => { +// const name = params.name; +// +// const counter = client.counter.getOrCreate(name); +// const newCount = await counter.increment(1); +// +// return `New Count: ${newCount}`; +// }) +// .listen(6420); +// +// console.log("Listening at http://localhost:6420"); diff --git a/examples/express/package.json b/examples/express/package.json index 1c0601f20..ae98f4809 100644 --- a/examples/express/package.json +++ b/examples/express/package.json @@ -15,14 +15,10 @@ "typescript": "^5.5.2" }, "dependencies": { - "@rivetkit/memory": "workspace:0.9.0-rc.1", "@rivetkit/react": "workspace:0.9.0-rc.1", "express": "^5.1.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, - "example": { - "platforms": ["*"] - }, "stableVersion": "0.8.0" } diff --git a/examples/express/src/server.ts b/examples/express/src/server.ts index 8e2a79c1f..c0c5ebcee 100644 --- a/examples/express/src/server.ts +++ b/examples/express/src/server.ts @@ -1,11 +1,8 @@ import { registry } from "./registry"; import express from "express"; -import { createMemoryDriver } from "@rivetkit/memory"; // Start RivetKit -const { client, handler } = registry.run({ - driver: createMemoryDriver(), -}); +const { client, handler } = registry.run(); // Setup router const app = express(); diff --git a/examples/hono-react/package.json b/examples/hono-react/package.json index 8a0857752..aaa832e77 100644 --- a/examples/hono-react/package.json +++ b/examples/hono-react/package.json @@ -25,16 +25,10 @@ }, "dependencies": { "@hono/node-server": "^1.14.4", - "@rivetkit/memory": "workspace:0.9.0-rc.1", "@rivetkit/react": "workspace:0.9.0-rc.1", "hono": "^4.7.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, - "example": { - "platforms": [ - "*" - ] - }, "stableVersion": "0.8.0" } diff --git a/examples/hono-react/src/backend/server.ts b/examples/hono-react/src/backend/server.ts index 1a0dc675c..66d3e6b97 100644 --- a/examples/hono-react/src/backend/server.ts +++ b/examples/hono-react/src/backend/server.ts @@ -1,33 +1,33 @@ -import { registry } from "./registry"; -import { Hono } from "hono"; -import { serve } from "@hono/node-server"; -import { createMemoryDriver } from "@rivetkit/memory"; - -// Setup router -const app = new Hono(); - -// Start RivetKit -const { client, hono } = registry.run({ - driver: createMemoryDriver(), - cors: { - // IMPORTANT: Configure origins in production - origin: "*", - }, -}); - -// Expose RivetKit to the frontend -app.route("/registry", hono); - -// Example HTTP endpoint -app.post("/increment/:name", async (c) => { - const name = c.req.param("name"); - - const counter = client.counter.getOrCreate(name); - const newCount = await counter.increment(1); - - return c.text(`New Count: ${newCount}`); -}); - -serve({ fetch: app.fetch, port: 6420 }, () => - console.log("Listening at http://localhost:6420"), -); +// import { registry } from "./registry"; +// import { Hono } from "hono"; +// import { serve } from "@hono/node-server"; +// import { createMemoryDriver } from "@rivetkit/memory"; +// +// // Setup router +// const app = new Hono(); +// +// // Start RivetKit +// const { client, hono } = registry.run({ +// driver: createMemoryDriver(), +// cors: { +// // IMPORTANT: Configure origins in production +// origin: "*", +// }, +// }); +// +// // Expose RivetKit to the frontend +// app.route("/registry", hono); +// +// // Example HTTP endpoint +// app.post("/increment/:name", async (c) => { +// const name = c.req.param("name"); +// +// const counter = client.counter.getOrCreate(name); +// const newCount = await counter.increment(1); +// +// return c.text(`New Count: ${newCount}`); +// }); +// +// serve({ fetch: app.fetch, port: 6420 }, () => +// console.log("Listening at http://localhost:6420"), +// ); diff --git a/examples/hono-react/src/frontend/App.tsx b/examples/hono-react/src/frontend/App.tsx index 91152baa2..d5aebce6a 100644 --- a/examples/hono-react/src/frontend/App.tsx +++ b/examples/hono-react/src/frontend/App.tsx @@ -1,39 +1,39 @@ -import { useState } from "react"; -import { createClient, createRivetKit } from "@rivetkit/react"; -import type { Registry } from "../backend/registry"; - -const client = createClient("http://localhost:6420/registry", { - transport: "sse", -}); -const { useWorker } = createRivetKit(client); - -function App() { - const [count, setCount] = useState(0); - const [counterName, setCounterName] = useState("test-counter"); - - const counter = useWorker({ - name: "counter", - key: [counterName], - }); - - counter.useEvent("newCount", (x: number) => setCount(x)); - - const increment = async () => { - await counter.connection?.increment(1); - }; - - return ( -
-

Counter: {count}

- setCounterName(e.target.value)} - placeholder="Counter name" - /> - -
- ); -} - -export default App; +// import { useState } from "react"; +// import { createClient, createRivetKit } from "@rivetkit/react"; +// import type { Registry } from "../backend/registry"; +// +// const client = createClient("http://localhost:6420/registry", { +// transport: "sse", +// }); +// const { useWorker } = createRivetKit(client); +// +// function App() { +// const [count, setCount] = useState(0); +// const [counterName, setCounterName] = useState("test-counter"); +// +// const counter = useWorker({ +// name: "counter", +// key: [counterName], +// }); +// +// counter.useEvent("newCount", (x: number) => setCount(x)); +// +// const increment = async () => { +// await counter.connection?.increment(1); +// }; +// +// return ( +//
+//

Counter: {count}

+// setCounterName(e.target.value)} +// placeholder="Counter name" +// /> +// +//
+// ); +// } +// +// export default App; diff --git a/examples/hono-react/src/frontend/main.tsx b/examples/hono-react/src/frontend/main.tsx index 6d0ba7949..cd443c7ac 100644 --- a/examples/hono-react/src/frontend/main.tsx +++ b/examples/hono-react/src/frontend/main.tsx @@ -1,9 +1,9 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import App from "./App"; - -ReactDOM.createRoot(document.getElementById("root")!).render( - - - , -); +// import React from "react"; +// import ReactDOM from "react-dom/client"; +// import App from "./App"; +// +// ReactDOM.createRoot(document.getElementById("root")!).render( +// +// +// , +// ); diff --git a/examples/hono/package.json b/examples/hono/package.json index 791801226..63c7d873a 100644 --- a/examples/hono/package.json +++ b/examples/hono/package.json @@ -15,11 +15,7 @@ }, "dependencies": { "@hono/node-server": "^1.14.4", - "@rivetkit/memory": "workspace:0.9.0-rc.1", "hono": "^4.7.0" }, - "example": { - "platforms": ["*"] - }, "stableVersion": "0.8.0" } diff --git a/examples/hono/src/server.ts b/examples/hono/src/server.ts index 220e49df2..ccca193e1 100644 --- a/examples/hono/src/server.ts +++ b/examples/hono/src/server.ts @@ -1,29 +1,29 @@ -import { registry } from "./registry"; -import { Hono } from "hono"; -import { serve } from "@hono/node-server"; -import { createMemoryDriver } from "@rivetkit/memory"; - -// Start RivetKit -const { client, hono } = registry.run({ - driver: createMemoryDriver(), -}); - -// Setup router -const app = new Hono(); - -// Expose RivetKit to the frontend (optinoal) -app.route("/registry", hono); - -// Example HTTP endpoint -app.post("/increment/:name", async (c) => { - const name = c.req.param("name"); - - const counter = client.counter.getOrCreate(name); - const newCount = await counter.increment(1); - - return c.text(`New Count: ${newCount}`); -}); - -serve({ fetch: app.fetch, port: 6420 }, (x) => - console.log("Listening at http://localhost:6420"), -); +// import { registry } from "./registry"; +// import { Hono } from "hono"; +// import { serve } from "@hono/node-server"; +// import { createMemoryDriver } from "@rivetkit/memory"; +// +// // Start RivetKit +// const { client, hono } = registry.server({ +// driver: createMemoryDriver(), +// }); +// +// // Setup router +// const app = new Hono(); +// +// // Expose RivetKit to the frontend (optinoal) +// app.route("/registry", hono); +// +// // Example HTTP endpoint +// app.post("/increment/:name", async (c) => { +// const name = c.req.param("name"); +// +// const counter = client.counter.getOrCreate(name); +// const newCount = await counter.increment(1); +// +// return c.text(`New Count: ${newCount}`); +// }); +// +// serve({ fetch: app.fetch, port: 6420 }, (x) => +// console.log("Listening at http://localhost:6420"), +// ); diff --git a/examples/nodejs/README.md b/examples/nodejs/README.md deleted file mode 100644 index bb41842a6..000000000 --- a/examples/nodejs/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Hono Integration for RivetKit - -Example project demonstrating Hono web framework integration with [RivetKit](https://rivetkit.org). - -[Learn More →](https://github.com/rivet-gg/rivetkit) - -[Discord](https://rivet.gg/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-gg/rivetkit/issues) - -## Getting Started - -### Prerequisites - -- Node.js - -### Installation - -```sh -git clone https://github.com/rivet-gg/rivetkit -cd rivetkit/examples/hono -npm install -``` - -### Development - -```sh -npm run dev -``` - -Open your browser to http://localhost:3000 to see the Hono server with RivetKit integration. - -## License - -Apache 2.0 \ No newline at end of file diff --git a/examples/nodejs/package.json b/examples/nodejs/package.json deleted file mode 100644 index 406f36689..000000000 --- a/examples/nodejs/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "example-nodejs", - "version": "0.9.0-rc.1", - "private": true, - "type": "module", - "scripts": { - "dev": "tsx --watch src/server.ts", - "check-types": "tsc --noEmit" - }, - "devDependencies": { - "@types/node": "^22.13.9", - "@rivetkit/worker": "workspace:*", - "tsx": "^3.12.7", - "typescript": "^5.5.2" - }, - "dependencies": { - "@rivetkit/memory": "workspace:0.9.0-rc.1", - "@rivetkit/nodejs": "workspace:0.9.0-rc.1" - }, - "example": { - "platforms": ["*"] - }, - "stableVersion": "0.8.0" -} diff --git a/examples/nodejs/src/registry.ts b/examples/nodejs/src/registry.ts deleted file mode 100644 index 84f7d059b..000000000 --- a/examples/nodejs/src/registry.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { worker, setup } from "@rivetkit/worker"; - -export const counter = worker({ - onAuth: () => { - // Configure auth here - }, - state: { count: 0 }, - actions: { - increment: (c, x: number) => { - c.state.count += x; - return c.state.count; - }, - }, -}); - -export const registry = setup({ - workers: { counter }, -}); - -export type Registry = typeof registry; diff --git a/examples/nodejs/src/server.ts b/examples/nodejs/src/server.ts deleted file mode 100644 index 9d26449e9..000000000 --- a/examples/nodejs/src/server.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { registry } from "./registry"; -import { createMemoryDriver } from "@rivetkit/memory"; -import { serve } from "@rivetkit/nodejs"; - -serve(registry, { - driver: createMemoryDriver(), -}); diff --git a/examples/nodejs/tsconfig.json b/examples/nodejs/tsconfig.json deleted file mode 100644 index 40a9fd759..000000000 --- a/examples/nodejs/tsconfig.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "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": ["node"], - /* 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/react/package.json b/examples/react/package.json index b1296e965..a37a14a74 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -24,14 +24,9 @@ "vitest": "^3.1.1" }, "dependencies": { - "@rivetkit/memory": "workspace:0.9.0-rc.1", "@rivetkit/react": "workspace:0.9.0-rc.1", - "@rivetkit/nodejs": "workspace:0.9.0-rc.1", "react": "^18.2.0", "react-dom": "^18.2.0" }, - "example": { - "platforms": ["*"] - }, "stableVersion": "0.8.0" } diff --git a/examples/react/src/backend/server.ts b/examples/react/src/backend/server.ts index ef8556a69..40bd8bbc3 100644 --- a/examples/react/src/backend/server.ts +++ b/examples/react/src/backend/server.ts @@ -1,11 +1,9 @@ -import { registry } from "./registry"; -import { createMemoryDriver } from "@rivetkit/memory"; -import { serve } from "@rivetkit/nodejs"; - -serve(registry, { - driver: createMemoryDriver(), - cors: { - // IMPORTANT: Configure origins in production - origin: "*", - }, -}); +// import { registry } from "./registry"; +// import { serve } from "@rivetkit/nodejs"; +// +// serve(registry, { +// cors: { +// // IMPORTANT: Configure origins in production +// origin: "*", +// }, +// }); diff --git a/examples/react/src/frontend/App.tsx b/examples/react/src/frontend/App.tsx index 44dedfd69..a65a3cabf 100644 --- a/examples/react/src/frontend/App.tsx +++ b/examples/react/src/frontend/App.tsx @@ -1,37 +1,37 @@ -import { useState } from "react"; -import { createClient, createRivetKit } from "@rivetkit/react"; -import type { Registry } from "../backend/registry"; - -const client = createClient(`http://localhost:6420/registry`); -const { useWorker } = createRivetKit(client); - -function App() { - const [count, setCount] = useState(0); - const [counterName, setCounterName] = useState("test-counter"); - - const counter = useWorker({ - name: "counter", - key: [counterName], - }); - - counter.useEvent("newCount", (x: number) => setCount(x)); - - const increment = async () => { - await counter.connection?.increment(1); - }; - - return ( -
-

Counter: {count}

- setCounterName(e.target.value)} - placeholder="Counter name" - /> - -
- ); -} - -export default App; +// import { useState } from "react"; +// import { createClient, createRivetKit } from "@rivetkit/react"; +// import type { Registry } from "../backend/registry"; +// +// const client = createClient(`http://localhost:6420/registry`); +// const { useWorker } = createRivetKit(client); +// +// function App() { +// const [count, setCount] = useState(0); +// const [counterName, setCounterName] = useState("test-counter"); +// +// const counter = useWorker({ +// name: "counter", +// key: [counterName], +// }); +// +// counter.useEvent("newCount", (x: number) => setCount(x)); +// +// const increment = async () => { +// await counter.connection?.increment(1); +// }; +// +// return ( +//
+//

Counter: {count}

+// setCounterName(e.target.value)} +// placeholder="Counter name" +// /> +// +//
+// ); +// } +// +// export default App; diff --git a/examples/react/src/frontend/main.tsx b/examples/react/src/frontend/main.tsx index 6d0ba7949..cd443c7ac 100644 --- a/examples/react/src/frontend/main.tsx +++ b/examples/react/src/frontend/main.tsx @@ -1,9 +1,9 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import App from "./App"; - -ReactDOM.createRoot(document.getElementById("root")!).render( - - - , -); +// import React from "react"; +// import ReactDOM from "react-dom/client"; +// import App from "./App"; +// +// ReactDOM.createRoot(document.getElementById("root")!).render( +// +// +// , +// ); diff --git a/examples/rivet/.dockerignore b/examples/rivet/.dockerignore deleted file mode 100644 index de4d1f007..000000000 --- a/examples/rivet/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -dist -node_modules diff --git a/examples/rivet/Dockerfile b/examples/rivet/Dockerfile deleted file mode 100644 index 643968a74..000000000 --- a/examples/rivet/Dockerfile +++ /dev/null @@ -1,39 +0,0 @@ -FROM node:22-alpine AS builder - -RUN npm i -g corepack && corepack enable - -WORKDIR /app - -COPY package.json ./ - -RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ - pnpm install - -COPY . . - -RUN pnpm build - -COPY . . - -# === - -FROM node:22-alpine AS runtime - -RUN npm i -g corepack && corepack enable - -RUN addgroup -g 1001 -S rivet && \ - adduser -S rivet -u 1001 -G rivet - -WORKDIR /app - -COPY --from=builder --chown=rivet:rivet /app/package.json ./ - -RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ - pnpm install --prod - -COPY --from=builder --chown=rivet:rivet /app/dist ./dist - -USER rivet - -CMD ["node", "dist/server.js"] - diff --git a/examples/rivet/package.json b/examples/rivet/package.json index 808e79f42..9999a8d03 100644 --- a/examples/rivet/package.json +++ b/examples/rivet/package.json @@ -14,13 +14,7 @@ "typescript": "^5.5.2" }, "dependencies": { - "@rivetkit/worker": "https://pkg.pr.new/rivet-gg/rivetkit@65c3659", - "@rivetkit/rivet": "https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/rivet@65c3659" - }, - "example": { - "platforms": [ - "*" - ] + "@rivetkit/worker": "https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/worker@38d8fca" }, "stableVersion": "0.8.0" } diff --git a/examples/rivet/src/registry.ts b/examples/rivet/src/registry.ts index 84f7d059b..bcb929459 100644 --- a/examples/rivet/src/registry.ts +++ b/examples/rivet/src/registry.ts @@ -17,4 +17,3 @@ export const registry = setup({ workers: { counter }, }); -export type Registry = typeof registry; diff --git a/examples/rivet/src/server.ts b/examples/rivet/src/server.ts index a2cbb7f78..11163905a 100644 --- a/examples/rivet/src/server.ts +++ b/examples/rivet/src/server.ts @@ -1,4 +1,3 @@ -import { startManager } from "@rivetkit/rivet/manager"; import { registry } from "./registry"; -startManager(registry); +registry.runServer(); diff --git a/examples/trpc/package.json b/examples/trpc/package.json index 6da22c94d..7a360d443 100644 --- a/examples/trpc/package.json +++ b/examples/trpc/package.json @@ -15,13 +15,9 @@ "typescript": "^5.5.2" }, "dependencies": { - "@rivetkit/memory": "workspace:0.9.0-rc.1", "@trpc/client": "^11.3.1", "@trpc/server": "^11.4.2", "zod": "^3.24.1" }, - "example": { - "platforms": ["*"] - }, "stableVersion": "0.8.0" } diff --git a/examples/trpc/src/server.ts b/examples/trpc/src/server.ts index 5d3dfb1a5..8f90f67ba 100644 --- a/examples/trpc/src/server.ts +++ b/examples/trpc/src/server.ts @@ -1,37 +1,34 @@ -import { registry } from "./registry.js"; -import { initTRPC } from "@trpc/server"; -import { createHTTPServer } from "@trpc/server/adapters/standalone"; -import { z } from "zod"; -import { createMemoryDriver } from "@rivetkit/memory"; - -// Start RivetKit -const { client } = registry.run({ - driver: createMemoryDriver(), -}); - -// Initialize tRPC -const t = initTRPC.create(); - -// Create tRPC router with RivetKit integration -const appRouter = t.router({ - // Increment a named counter - increment: t.procedure - .input(z.object({ name: z.string() })) - .mutation(async ({ input }) => { - const counter = client.counter.getOrCreate(input.name); - const newCount = await counter.increment(1); - return newCount; - }), -}); - -// Export type for client -export type AppRouter = typeof appRouter; - -// Create HTTP server -const server = createHTTPServer({ - router: appRouter, -}); - -server.listen(3001); - -console.log("tRPC server listening at http://localhost:3001"); +// import { registry } from "./registry.js"; +// import { initTRPC } from "@trpc/server"; +// import { createHTTPServer } from "@trpc/server/adapters/standalone"; +// import { z } from "zod"; +// +// // Start RivetKit +// const { client } = registry.run(); +// +// // Initialize tRPC +// const t = initTRPC.create(); +// +// // Create tRPC router with RivetKit integration +// const appRouter = t.router({ +// // Increment a named counter +// increment: t.procedure +// .input(z.object({ name: z.string() })) +// .mutation(async ({ input }) => { +// const counter = client.counter.getOrCreate(input.name); +// const newCount = await counter.increment(1); +// return newCount; +// }), +// }); +// +// // Export type for client +// export type AppRouter = typeof appRouter; +// +// // Create HTTP server +// const server = createHTTPServer({ +// router: appRouter, +// }); +// +// server.listen(3001); +// +// console.log("tRPC server listening at http://localhost:3001"); diff --git a/packages/core/README.md b/packages/core/README.md deleted file mode 100644 index 3c1319e0f..000000000 --- a/packages/core/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# RivetKit - -_Lightweight Libraries for Backends_ - -[Learn More →](https://github.com/rivet-gg/rivetkit) - -[Discord](https://rivet.gg/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-gg/rivetkit/issues) - -## License - -Apache 2.0 \ No newline at end of file diff --git a/packages/core/fixtures/driver-test-suite/action-inputs.ts b/packages/core/fixtures/driver-test-suite/action-inputs.ts deleted file mode 100644 index 0936bf7d9..000000000 --- a/packages/core/fixtures/driver-test-suite/action-inputs.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { worker } from "@rivetkit/core"; - -export interface State { - initialInput?: unknown; - onCreateInput?: unknown; -} - -// Test worker that can capture input during creation -export const inputWorker = worker({ - onAuth: () => {}, - createState: (c, { input }): State => { - return { - initialInput: input, - onCreateInput: undefined, - }; - }, - - onCreate: (c, { input }) => { - c.state.onCreateInput = input; - }, - - actions: { - getInputs: (c) => { - return { - initialInput: c.state.initialInput, - onCreateInput: c.state.onCreateInput, - }; - }, - }, -}); - diff --git a/packages/core/fixtures/driver-test-suite/action-timeout.ts b/packages/core/fixtures/driver-test-suite/action-timeout.ts deleted file mode 100644 index c0a5988b4..000000000 --- a/packages/core/fixtures/driver-test-suite/action-timeout.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { worker } from "@rivetkit/core"; - -// Short timeout worker -export const shortTimeoutWorker = worker({ - onAuth: () => {}, - state: { value: 0 }, - options: { - action: { - timeout: 50, // 50ms timeout - }, - }, - actions: { - quickAction: async (c) => { - return "quick response"; - }, - slowAction: async (c) => { - // This action should timeout - await new Promise((resolve) => setTimeout(resolve, 100)); - return "slow response"; - }, - }, -}); - -// Long timeout worker -export const longTimeoutWorker = worker({ - onAuth: () => {}, - state: { value: 0 }, - options: { - action: { - timeout: 200, // 200ms timeout - }, - }, - actions: { - delayedAction: async (c) => { - // This action should complete within timeout - await new Promise((resolve) => setTimeout(resolve, 100)); - return "delayed response"; - }, - }, -}); - -// Default timeout worker -export const defaultTimeoutWorker = worker({ - onAuth: () => {}, - state: { value: 0 }, - actions: { - normalAction: async (c) => { - await new Promise((resolve) => setTimeout(resolve, 50)); - return "normal response"; - }, - }, -}); - -// Sync worker (timeout shouldn't apply) -export const syncTimeoutWorker = worker({ - onAuth: () => {}, - state: { value: 0 }, - options: { - action: { - timeout: 50, // 50ms timeout - }, - }, - actions: { - syncAction: (c) => { - return "sync response"; - }, - }, -}); - - diff --git a/packages/core/fixtures/driver-test-suite/action-types.ts b/packages/core/fixtures/driver-test-suite/action-types.ts deleted file mode 100644 index 342b8b171..000000000 --- a/packages/core/fixtures/driver-test-suite/action-types.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { worker, UserError } from "@rivetkit/core"; - -// Worker with synchronous actions -export const syncActionWorker = worker({ - onAuth: () => {}, - state: { value: 0 }, - actions: { - // Simple synchronous action that returns a value directly - increment: (c, amount: number = 1) => { - c.state.value += amount; - return c.state.value; - }, - // Synchronous action that returns an object - getInfo: (c) => { - return { - currentValue: c.state.value, - timestamp: Date.now(), - }; - }, - // Synchronous action with no return value (void) - reset: (c) => { - c.state.value = 0; - }, - }, -}); - -// Worker with asynchronous actions -export const asyncActionWorker = worker({ - onAuth: () => {}, - state: { value: 0, data: null as any }, - actions: { - // Async action with a delay - delayedIncrement: async (c, amount: number = 1) => { - await Promise.resolve(); - c.state.value += amount; - return c.state.value; - }, - // Async action that simulates an API call - fetchData: async (c, id: string) => { - await Promise.resolve(); - - // Simulate response data - const data = { id, timestamp: Date.now() }; - c.state.data = data; - return data; - }, - // Async action with error handling - asyncWithError: async (c, shouldError: boolean) => { - await Promise.resolve(); - - if (shouldError) { - throw new UserError("Intentional error"); - } - - return "Success"; - }, - }, -}); - -// Worker with promise actions -export const promiseWorker = worker({ - onAuth: () => {}, - state: { results: [] as string[] }, - actions: { - // Action that returns a resolved promise - resolvedPromise: (c) => { - return Promise.resolve("resolved value"); - }, - // Action that returns a promise that resolves after a delay - delayedPromise: (c): Promise => { - return new Promise((resolve) => { - c.state.results.push("delayed"); - resolve("delayed value"); - }); - }, - // Action that returns a rejected promise - rejectedPromise: (c) => { - return Promise.reject(new UserError("promised rejection")); - }, - // Action to check the collected results - getResults: (c) => { - return c.state.results; - }, - }, -}); - diff --git a/packages/core/fixtures/driver-test-suite/auth.ts b/packages/core/fixtures/driver-test-suite/auth.ts deleted file mode 100644 index e558d9e83..000000000 --- a/packages/core/fixtures/driver-test-suite/auth.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { worker, UserError } from "@rivetkit/core"; - -// Basic auth worker - requires API key -export const authWorker = worker({ - state: { requests: 0 }, - onAuth: (opts) => { - const { req, intents, params } = opts; - const apiKey = (params as any)?.apiKey; - if (!apiKey) { - throw new UserError("API key required", { code: "missing_auth" }); - } - - if (apiKey !== "valid-api-key") { - throw new UserError("Invalid API key", { code: "invalid_auth" }); - } - - return { userId: "user123", token: apiKey }; - }, - actions: { - getRequests: (c) => { - c.state.requests++; - return c.state.requests; - }, - getUserAuth: (c) => c.conn.auth, - }, -}); - -// Intent-specific auth worker - checks different permissions for different intents -export const intentAuthWorker = worker({ - state: { value: 0 }, - onAuth: (opts) => { - const { req, intents, params } = opts; - console.log('intents', intents, params); - const role = (params as any)?.role; - - if (intents.has("create") && role !== "admin") { - throw new UserError("Admin role required for create operations", { code: "insufficient_permissions" }); - } - - if (intents.has("action") && !["admin", "user"].includes(role || "")) { - throw new UserError("User or admin role required for actions", { code: "insufficient_permissions" }); - } - - return { role, timestamp: Date.now() }; - }, - actions: { - getValue: (c) => c.state.value, - setValue: (c, value: number) => { - c.state.value = value; - return value; - }, - getAuth: (c) => c.conn.auth, - }, -}); - -// Public worker - empty onAuth to allow public access -export const publicWorker = worker({ - state: { visitors: 0 }, - onAuth: () => { - return null; // Allow public access - }, - actions: { - visit: (c) => { - c.state.visitors++; - return c.state.visitors; - }, - }, -}); - -// No auth worker - should fail when accessed publicly (no onAuth defined) -export const noAuthWorker = worker({ - state: { value: 42 }, - actions: { - getValue: (c) => c.state.value, - }, -}); - -// Async auth worker - tests promise-based authentication -export const asyncAuthWorker = worker({ - state: { count: 0 }, - onAuth: async (opts) => { - const { req, intents, params } = opts; - // Simulate async auth check (e.g., database lookup) - await new Promise(resolve => setTimeout(resolve, 10)); - - const token = (params as any)?.token; - if (!token) { - throw new UserError("Token required", { code: "missing_token" }); - } - - // Simulate token validation - if (token === "invalid") { - throw new UserError("Token is invalid", { code: "invalid_token" }); - } - - return { userId: `user-${token}`, validated: true }; - }, - actions: { - increment: (c) => { - c.state.count++; - return c.state.count; - }, - getAuthData: (c) => c.conn.auth, - }, -}); diff --git a/packages/core/fixtures/driver-test-suite/conn-params.ts b/packages/core/fixtures/driver-test-suite/conn-params.ts deleted file mode 100644 index db0876576..000000000 --- a/packages/core/fixtures/driver-test-suite/conn-params.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { worker } from "@rivetkit/core"; - -export const counterWithParams = worker({ - onAuth: () => {}, - state: { count: 0, initializers: [] as string[] }, - createConnState: (c, { params }: { params: { name?: string } }) => { - return { - name: params?.name || "anonymous", - }; - }, - onConnect: (c, conn) => { - // Record connection name - c.state.initializers.push(conn.state.name); - }, - actions: { - increment: (c, x: number) => { - c.state.count += x; - c.broadcast("newCount", { - count: c.state.count, - by: c.conn.state.name, - }); - return c.state.count; - }, - getInitializers: (c) => { - return c.state.initializers; - }, - }, -}); - diff --git a/packages/core/fixtures/driver-test-suite/conn-state.ts b/packages/core/fixtures/driver-test-suite/conn-state.ts deleted file mode 100644 index 3151905a9..000000000 --- a/packages/core/fixtures/driver-test-suite/conn-state.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { worker } from "@rivetkit/core"; - -export type ConnState = { - username: string; - role: string; - counter: number; - createdAt: number; -}; - -export const connStateWorker = worker({ - onAuth: () => {}, - state: { - sharedCounter: 0, - disconnectionCount: 0, - }, - // Define connection state - createConnState: ( - c, - { params }: { params?: { username?: string; role?: string } }, - ): ConnState => { - return { - username: params?.username || "anonymous", - role: params?.role || "user", - counter: 0, - createdAt: Date.now(), - }; - }, - // Lifecycle hook when a connection is established - onConnect: (c, conn) => { - // Broadcast event about the new connection - c.broadcast("userConnected", { - id: conn.id, - username: "anonymous", - role: "user", - }); - }, - // Lifecycle hook when a connection is closed - onDisconnect: (c, conn) => { - c.state.disconnectionCount += 1; - c.broadcast("userDisconnected", { - id: conn.id, - }); - }, - actions: { - // Action to increment the connection's counter - incrementConnCounter: (c, amount: number = 1) => { - c.conn.state.counter += amount; - }, - - // Action to increment the shared counter - incrementSharedCounter: (c, amount: number = 1) => { - c.state.sharedCounter += amount; - return c.state.sharedCounter; - }, - - // Get the connection state - getConnectionState: (c) => { - return { id: c.conn.id, ...c.conn.state }; - }, - - // Check all active connections - getConnectionIds: (c) => { - return c.conns.keys().toArray(); - }, - - // Get disconnection count - getDisconnectionCount: (c) => { - return c.state.disconnectionCount; - }, - - // Get all active connection states - getAllConnectionStates: (c) => { - return c.conns.entries().map(([id, conn]) => ({ id, ...conn.state })).toArray(); - }, - - // Send message to a specific connection with matching ID - sendToConnection: (c, targetId: string, message: string) => { - if (c.conns.has(targetId)) { - c.conns.get(targetId)!.send("directMessage", { from: c.conn.id, message }); - return true; - } else { - return false; - } - }, - - // Update connection state (simulated for tests) - updateConnection: ( - c, - updates: Partial<{ username: string; role: string }>, - ) => { - if (updates.username) c.conn.state.username = updates.username; - if (updates.role) c.conn.state.role = updates.role; - return c.conn.state; - }, - }, -}); - diff --git a/packages/core/fixtures/driver-test-suite/counter.ts b/packages/core/fixtures/driver-test-suite/counter.ts deleted file mode 100644 index 5936b93b0..000000000 --- a/packages/core/fixtures/driver-test-suite/counter.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { worker } from "@rivetkit/core"; - -export const counter = worker({ - onAuth: () => {}, - state: { count: 0 }, - actions: { - increment: (c, x: number) => { - c.state.count += x; - c.broadcast("newCount", c.state.count); - return c.state.count; - }, - getCount: (c) => { - return c.state.count; - }, - }, -}); diff --git a/packages/core/fixtures/driver-test-suite/error-handling.ts b/packages/core/fixtures/driver-test-suite/error-handling.ts deleted file mode 100644 index a6e743c98..000000000 --- a/packages/core/fixtures/driver-test-suite/error-handling.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { worker, UserError } from "@rivetkit/core"; - -export const errorHandlingWorker = worker({ - onAuth: () => {}, - state: { - errorLog: [] as string[], - }, - actions: { - // Action that throws a UserError with just a message - throwSimpleError: () => { - throw new UserError("Simple error message"); - }, - - // Action that throws a UserError with code and metadata - throwDetailedError: () => { - throw new UserError("Detailed error message", { - code: "detailed_error", - metadata: { - reason: "test", - timestamp: Date.now(), - }, - }); - }, - - // Action that throws an internal error - throwInternalError: () => { - throw new Error("This is an internal error"); - }, - - // Action that returns successfully - successfulAction: () => { - return "success"; - }, - - // Action that times out (simulated with a long delay) - timeoutAction: async (c) => { - // This action should time out if the timeout is configured - return new Promise((resolve) => { - setTimeout(() => { - resolve("This should not be reached if timeout works"); - }, 10000); // 10 seconds - }); - }, - - // Action with configurable delay to test timeout edge cases - delayedAction: async (c, delayMs: number) => { - return new Promise((resolve) => { - setTimeout(() => { - resolve(`Completed after ${delayMs}ms`); - }, delayMs); - }); - }, - - // Log an error for inspection - logError: (c, error: string) => { - c.state.errorLog.push(error); - return c.state.errorLog; - }, - - // Get the error log - getErrorLog: (c) => { - return c.state.errorLog; - }, - - // Clear the error log - clearErrorLog: (c) => { - c.state.errorLog = []; - return true; - }, - }, - options: { - // Set a short timeout for this worker's actions - action: { - timeout: 500, // 500ms timeout for actions - }, - }, -}); - -// Worker with custom timeout -export const customTimeoutWorker = worker({ - state: {}, - actions: { - quickAction: async () => { - await new Promise((resolve) => setTimeout(resolve, 50)); - return "Quick action completed"; - }, - slowAction: async () => { - await new Promise((resolve) => setTimeout(resolve, 300)); - return "Slow action completed"; - }, - }, - options: { - action: { - timeout: 200, // 200ms timeout - }, - }, -}); - diff --git a/packages/core/fixtures/driver-test-suite/lifecycle.ts b/packages/core/fixtures/driver-test-suite/lifecycle.ts deleted file mode 100644 index 950e3ac8d..000000000 --- a/packages/core/fixtures/driver-test-suite/lifecycle.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { worker } from "@rivetkit/core"; - -export const counterWithLifecycle = worker({ - onAuth: () => {}, - state: { - count: 0, - events: [] as string[], - }, - createConnState: ( - c, - opts: { params: { trackLifecycle?: boolean } | undefined }, - ) => ({ - joinTime: Date.now(), - }), - onStart: (c) => { - c.state.events.push("onStart"); - }, - onBeforeConnect: (c, conn) => { - if (conn.params?.trackLifecycle) c.state.events.push("onBeforeConnect"); - }, - onConnect: (c, conn) => { - if (conn.params?.trackLifecycle) c.state.events.push("onConnect"); - }, - onDisconnect: (c, conn) => { - if (conn.params?.trackLifecycle) c.state.events.push("onDisconnect"); - }, - actions: { - getEvents: (c) => { - return c.state.events; - }, - increment: (c, x: number) => { - c.state.count += x; - return c.state.count; - }, - }, -}); - diff --git a/packages/core/fixtures/driver-test-suite/metadata.ts b/packages/core/fixtures/driver-test-suite/metadata.ts deleted file mode 100644 index c1e94b3ea..000000000 --- a/packages/core/fixtures/driver-test-suite/metadata.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { worker } from "@rivetkit/core"; - -// Note: For testing only - metadata API will need to be mocked -// in tests since this is implementation-specific -export const metadataWorker = worker({ - onAuth: () => {}, - state: { - lastMetadata: null as any, - workerName: "", - // Store tags and region in state for testing since they may not be - // available in the context in all environments - storedTags: {} as Record, - storedRegion: null as string | null, - }, - onStart: (c) => { - // Store the worker name during initialization - c.state.workerName = c.name; - }, - actions: { - // Set up test tags - this will be called by tests to simulate tags - setupTestTags: (c, tags: Record) => { - c.state.storedTags = tags; - return tags; - }, - - // Set up test region - this will be called by tests to simulate region - setupTestRegion: (c, region: string) => { - c.state.storedRegion = region; - return region; - }, - - // Get all available metadata - getMetadata: (c) => { - // Create metadata object from stored values - const metadata = { - name: c.name, - tags: c.state.storedTags, - region: c.state.storedRegion, - }; - - // Store for later inspection - c.state.lastMetadata = metadata; - return metadata; - }, - - // Get the worker name - getWorkerName: (c) => { - return c.name; - }, - - // Get a specific tag by key - getTag: (c, key: string) => { - return c.state.storedTags[key] || null; - }, - - // Get all tags - getTags: (c) => { - return c.state.storedTags; - }, - - // Get the region - getRegion: (c) => { - return c.state.storedRegion; - }, - - // Get the stored worker name (from onStart) - getStoredWorkerName: (c) => { - return c.state.workerName; - }, - - // Get last retrieved metadata - getLastMetadata: (c) => { - return c.state.lastMetadata; - }, - }, -}); - - diff --git a/packages/core/fixtures/driver-test-suite/registry.ts b/packages/core/fixtures/driver-test-suite/registry.ts deleted file mode 100644 index 696ddfddc..000000000 --- a/packages/core/fixtures/driver-test-suite/registry.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { setup } from "@rivetkit/core"; - -// Import workers from individual files -import { counter } from "./counter"; -import { counterWithLifecycle } from "./lifecycle"; -import { scheduled } from "./scheduled"; -import { errorHandlingWorker, customTimeoutWorker } from "./error-handling"; -import { inputWorker } from "./action-inputs"; -import { - shortTimeoutWorker, - longTimeoutWorker, - defaultTimeoutWorker, - syncTimeoutWorker, -} from "./action-timeout"; -import { - syncActionWorker, - asyncActionWorker, - promiseWorker, -} from "./action-types"; -import { counterWithParams } from "./conn-params"; -import { connStateWorker } from "./conn-state"; -import { metadataWorker } from "./metadata"; -import { - staticVarWorker, - nestedVarWorker, - dynamicVarWorker, - uniqueVarWorker, - driverCtxWorker, -} from "./vars"; -import { - authWorker, - intentAuthWorker, - publicWorker, - noAuthWorker, - asyncAuthWorker, -} from "./auth"; - -// Consolidated setup with all workers -export const registry = setup({ - workers: { - // From counter.ts - counter, - // From lifecycle.ts - counterWithLifecycle, - // From scheduled.ts - scheduled, - // From error-handling.ts - errorHandlingWorker, - customTimeoutWorker, - // From action-inputs.ts - inputWorker, - // From action-timeout.ts - shortTimeoutWorker, - longTimeoutWorker, - defaultTimeoutWorker, - syncTimeoutWorker, - // From action-types.ts - syncActionWorker, - asyncActionWorker, - promiseWorker, - // From conn-params.ts - counterWithParams, - // From conn-state.ts - connStateWorker, - // From metadata.ts - metadataWorker, - // From vars.ts - staticVarWorker, - nestedVarWorker, - dynamicVarWorker, - uniqueVarWorker, - driverCtxWorker, - // From auth.ts - authWorker, - intentAuthWorker, - publicWorker, - noAuthWorker, - asyncAuthWorker, - }, -}); - -export type Registry = typeof registry; diff --git a/packages/core/fixtures/driver-test-suite/scheduled.ts b/packages/core/fixtures/driver-test-suite/scheduled.ts deleted file mode 100644 index ee2bc284c..000000000 --- a/packages/core/fixtures/driver-test-suite/scheduled.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { worker } from "@rivetkit/core"; - -export const scheduled = worker({ - onAuth: () => {}, - state: { - lastRun: 0, - scheduledCount: 0, - taskHistory: [] as string[], - }, - actions: { - // Schedule using 'at' with specific timestamp - scheduleTaskAt: (c, timestamp: number) => { - c.schedule.at(timestamp, "onScheduledTask"); - return timestamp; - }, - - // Schedule using 'after' with delay - scheduleTaskAfter: (c, delayMs: number) => { - c.schedule.after(delayMs, "onScheduledTask"); - return Date.now() + delayMs; - }, - - // Schedule with a task ID for ordering tests - scheduleTaskAfterWithId: (c, taskId: string, delayMs: number) => { - c.schedule.after(delayMs, "onScheduledTaskWithId", taskId); - return { taskId, scheduledFor: Date.now() + delayMs }; - }, - - // Original method for backward compatibility - scheduleTask: (c, delayMs: number) => { - const timestamp = Date.now() + delayMs; - c.schedule.at(timestamp, "onScheduledTask"); - return timestamp; - }, - - // Getters for state - getLastRun: (c) => { - return c.state.lastRun; - }, - - getScheduledCount: (c) => { - return c.state.scheduledCount; - }, - - getTaskHistory: (c) => { - return c.state.taskHistory; - }, - - clearHistory: (c) => { - c.state.taskHistory = []; - c.state.scheduledCount = 0; - c.state.lastRun = 0; - return true; - }, - - // Scheduled task handlers - onScheduledTask: (c) => { - c.state.lastRun = Date.now(); - c.state.scheduledCount++; - c.broadcast("scheduled", { - time: c.state.lastRun, - count: c.state.scheduledCount, - }); - }, - - onScheduledTaskWithId: (c, taskId: string) => { - c.state.lastRun = Date.now(); - c.state.scheduledCount++; - c.state.taskHistory.push(taskId); - c.broadcast("scheduledWithId", { - taskId, - time: c.state.lastRun, - count: c.state.scheduledCount, - }); - }, - }, -}); - - diff --git a/packages/core/fixtures/driver-test-suite/vars.ts b/packages/core/fixtures/driver-test-suite/vars.ts deleted file mode 100644 index ee24df1bd..000000000 --- a/packages/core/fixtures/driver-test-suite/vars.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { worker } from "@rivetkit/core"; - -// Worker with static vars -export const staticVarWorker = worker({ - onAuth: () => {}, - state: { value: 0 }, - connState: { hello: "world" }, - vars: { counter: 42, name: "test-worker" }, - actions: { - getVars: (c) => { - return c.vars; - }, - getName: (c) => { - return c.vars.name; - }, - }, -}); - -// Worker with nested vars -export const nestedVarWorker = worker({ - onAuth: () => {}, - state: { value: 0 }, - connState: { hello: "world" }, - vars: { - counter: 42, - nested: { - value: "original", - array: [1, 2, 3], - obj: { key: "value" }, - }, - }, - actions: { - getVars: (c) => { - return c.vars; - }, - modifyNested: (c) => { - // Attempt to modify the nested object - c.vars.nested.value = "modified"; - c.vars.nested.array.push(4); - c.vars.nested.obj.key = "new-value"; - return c.vars; - }, - }, -}); - -// Worker with dynamic vars -export const dynamicVarWorker = worker({ - onAuth: () => {}, - state: { value: 0 }, - connState: { hello: "world" }, - createVars: () => { - return { - random: Math.random(), - computed: `Worker-${Math.floor(Math.random() * 1000)}`, - }; - }, - actions: { - getVars: (c) => { - return c.vars; - }, - }, -}); - -// Worker with unique vars per instance -export const uniqueVarWorker = worker({ - onAuth: () => {}, - state: { value: 0 }, - connState: { hello: "world" }, - createVars: () => { - return { - id: Math.floor(Math.random() * 1000000), - }; - }, - actions: { - getVars: (c) => { - return c.vars; - }, - }, -}); - -// Worker that uses driver context -export const driverCtxWorker = worker({ - onAuth: () => {}, - state: { value: 0 }, - connState: { hello: "world" }, - createVars: (c, driverCtx: any) => { - return { - hasDriverCtx: Boolean(driverCtx?.isTest), - }; - }, - actions: { - getVars: (c) => { - return c.vars; - }, - }, -}); - - diff --git a/packages/core/package.json b/packages/core/package.json index 14aa6a822..e90a30375 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -66,6 +66,16 @@ "default": "./dist/utils.cjs" } }, + "./drivers/rivet": { + "import": { + "types": "./dist/drivers/rivet/mod.d.ts", + "default": "./dist/drivers/rivet/mod.js" + }, + "require": { + "types": "./dist/drivers/rivet/mod.d.cts", + "default": "./dist/drivers/rivet/mod.cjs" + } + }, "./driver-helpers": { "import": { "types": "./dist/driver-helpers/mod.d.ts", @@ -133,7 +143,7 @@ "sideEffects": false, "scripts": { "dev": "pnpm build --watch", - "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/common/websocket.ts src/worker/errors.ts src/topologies/coordinate/mod.ts src/topologies/partition/mod.ts src/utils.ts src/driver-helpers/mod.ts src/driver-test-suite/mod.ts src/test/mod.ts", + "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/common/websocket.ts src/worker/errors.ts src/topologies/coordinate/mod.ts src/topologies/partition/mod.ts src/utils.ts src/drivers/rivet/mod.ts src/driver-helpers/mod.ts src/driver-test-suite/mod.ts src/test/mod.ts", "check-types": "tsc --noEmit", "boop": "tsc --outDir dist/test -d", "test": "vitest run", @@ -152,6 +162,7 @@ "devDependencies": { "@hono/node-server": "^1.14.0", "@hono/node-ws": "^1.1.1", + "@rivet-gg/actor-core": "^25.1.0", "@types/invariant": "^2", "@types/node": "^22.13.1", "@types/ws": "^8", @@ -165,10 +176,10 @@ "ws": "^8.18.1" }, "peerDependencies": { - "eventsource": "^3.0.5", - "ws": "^8.0.0", "@hono/node-server": "^1.14.0", - "@hono/node-ws": "^1.1.1" + "@hono/node-ws": "^1.1.1", + "eventsource": "^3.0.5", + "ws": "^8.0.0" }, "peerDependenciesMeta": { "eventsource": { diff --git a/packages/core/scripts/dump-openapi.ts b/packages/core/scripts/dump-openapi.ts index b9670e193..d77872eb9 100644 --- a/packages/core/scripts/dump-openapi.ts +++ b/packages/core/scripts/dump-openapi.ts @@ -60,7 +60,7 @@ function main() { sendHttpMessage: unimplemented, }; - const managerRouter = createManagerRouter( + const { openapi } = createManagerRouter( registryConfig, driverConfig, inlineClientDriver, @@ -71,9 +71,9 @@ function main() { }, }, }, - ) as unknown as OpenAPIHono; + ); - const openApiDoc = managerRouter.getOpenAPIDocument({ + const openApiDoc = openapi.getOpenAPIDocument({ openapi: "3.0.0", info: { version: VERSION, diff --git a/packages/core/src/client/http-client-driver.ts b/packages/core/src/client/http-client-driver.ts index 9425d7fe9..22fd532a9 100644 --- a/packages/core/src/client/http-client-driver.ts +++ b/packages/core/src/client/http-client-driver.ts @@ -63,7 +63,7 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { const responseData = await sendHttpRequest( { - url: `${managerEndpoint}/workers/actions/${encodeURIComponent(name)}`, + url: `${managerEndpoint}/registry/workers/actions/${encodeURIComponent(name)}`, method: "POST", headers: { [HEADER_ENCODING]: encoding, @@ -94,7 +94,7 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { Record, protoHttpResolve.ResolveResponse >({ - url: `${managerEndpoint}/workers/resolve`, + url: `${managerEndpoint}/registry/workers/resolve`, method: "POST", headers: { [HEADER_ENCODING]: encodingKind, @@ -132,7 +132,7 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { const endpoint = managerEndpoint .replace(/^http:/, "ws:") .replace(/^https:/, "wss:"); - const url = `${endpoint}/workers/connect/websocket`; + const url = `${endpoint}/registry/workers/connect/websocket`; // Pass sensitive data via protocol const protocol = [ @@ -172,7 +172,7 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { ): Promise => { const { EventSource } = await dynamicImports; - const url = `${managerEndpoint}/workers/connect/sse`; + const url = `${managerEndpoint}/registry/workers/connect/sse`; logger().debug("connecting to sse", { url }); const eventSource = new EventSource(url, { @@ -206,7 +206,7 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { // TODO: Implement ordered messages, this is not guaranteed order. Needs to use an index in order to ensure we can pipeline requests efficiently. // TODO: Validate that we're using HTTP/3 whenever possible for pipelining requests const messageSerialized = serializeWithEncoding(encoding, message); - const res = await fetch(`${managerEndpoint}/workers/message`, { + const res = await fetch(`${managerEndpoint}/registry/workers/message`, { method: "POST", headers: { "User-Agent": httpUserAgent(), diff --git a/packages/core/src/common/log.ts b/packages/core/src/common/log.ts index bc69f3d20..7cecb75ba 100644 --- a/packages/core/src/common/log.ts +++ b/packages/core/src/common/log.ts @@ -1,3 +1,4 @@ +import { getEnvUniversal } from "@/utils"; import { type LevelIndex, LevelNameMap, @@ -75,13 +76,7 @@ export class Logger { const loggers: Record = {}; export function getLogger(name = "default"): Logger { - let defaultLogLevelEnv: LogLevel | undefined = undefined; - if (typeof Deno !== "undefined") { - defaultLogLevelEnv = Deno.env.get("_LOG_LEVEL") as LogLevel; - } else if (typeof process !== "undefined") { - // Do this after Deno since `process` is sometimes polyfilled - defaultLogLevelEnv = process.env._LOG_LEVEL as LogLevel; - } + const defaultLogLevelEnv: LogLevel | undefined = getEnvUniversal("_LOG_LEVEL") as LogLevel | undefined; const defaultLogLevel: LogLevel = defaultLogLevelEnv ?? "INFO"; if (!loggers[name]) { diff --git a/packages/core/src/common/utils.ts b/packages/core/src/common/utils.ts index d2bb1374e..0de4becbe 100644 --- a/packages/core/src/common/utils.ts +++ b/packages/core/src/common/utils.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import type { ContentfulStatusCode } from "hono/utils/http-status"; import * as errors from "@/worker/errors"; import type { Logger } from "./log"; +import { getEnvUniversal } from "@/utils"; // Maximum size of a key component in bytes // Set to 128 bytes to allow for separators and escape characters in the full key @@ -206,7 +207,7 @@ export function stringifyError(error: unknown): string { if (error instanceof Error) { if ( typeof process !== "undefined" && - process.env._RIVETKIT_ERROR_STACK === "1" + getEnvUniversal("_RIVETKIT_ERROR_STACK") === "1" ) { return `${error.name}: ${error.message}${error.stack ? `\n${error.stack}` : ""}`; } else { diff --git a/packages/core/src/driver-helpers/mod.ts b/packages/core/src/driver-helpers/mod.ts index 0a50c71a3..1ce93fa54 100644 --- a/packages/core/src/driver-helpers/mod.ts +++ b/packages/core/src/driver-helpers/mod.ts @@ -1,5 +1,5 @@ export type { WorkerInstance, AnyWorkerInstance } from "@/worker/instance"; -export { +export type { AttemptAcquireLease, ExtendLeaseOutput, GetWorkerLeaderOutput, @@ -7,8 +7,8 @@ export { CoordinateDriver, StartWorkerAndAcquireLeaseOutput, } from "@/topologies/coordinate/driver"; -export { WorkerDriver } from "@/worker/driver"; -export { +export type { WorkerDriver } from "@/worker/driver"; +export type { ManagerDriver, CreateInput, GetForIdInput, diff --git a/packages/drivers/memory/src/global-state.ts b/packages/core/src/drivers/memory/global-state.ts similarity index 91% rename from packages/drivers/memory/src/global-state.ts rename to packages/core/src/drivers/memory/global-state.ts index deae4f642..76cbb9739 100644 --- a/packages/drivers/memory/src/global-state.ts +++ b/packages/core/src/drivers/memory/global-state.ts @@ -1,4 +1,4 @@ -import type { WorkerKey } from "@rivetkit/core"; +import type { WorkerKey } from "@/common/utils"; export interface WorkerState { id: string; @@ -51,7 +51,9 @@ export class MemoryGlobalState { } } - findWorker(filter: (worker: WorkerState) => boolean): WorkerState | undefined { + findWorker( + filter: (worker: WorkerState) => boolean, + ): WorkerState | undefined { for (const worker of this.#workers.values()) { if (filter(worker)) { return worker; diff --git a/packages/drivers/memory/src/log.ts b/packages/core/src/drivers/memory/log.ts similarity index 69% rename from packages/drivers/memory/src/log.ts rename to packages/core/src/drivers/memory/log.ts index 25bd1ef8e..e690d2e40 100644 --- a/packages/drivers/memory/src/log.ts +++ b/packages/core/src/drivers/memory/log.ts @@ -1,4 +1,4 @@ -import { getLogger } from "@rivetkit/core/log"; +import { getLogger } from "@/common/log"; export const LOGGER_NAME = "driver-memory"; diff --git a/packages/drivers/memory/src/manager.ts b/packages/core/src/drivers/memory/manager.ts similarity index 95% rename from packages/drivers/memory/src/manager.ts rename to packages/core/src/drivers/memory/manager.ts index c6b637efc..63bcb5d91 100644 --- a/packages/drivers/memory/src/manager.ts +++ b/packages/core/src/drivers/memory/manager.ts @@ -5,8 +5,8 @@ import type { GetOrCreateWithKeyInput, WorkerOutput, ManagerDriver, -} from "@rivetkit/core/driver-helpers"; -import { WorkerAlreadyExists } from "@rivetkit/core/errors"; +} from "@/driver-helpers/mod"; +import { WorkerAlreadyExists } from "@/worker/errors"; import type { MemoryGlobalState } from "./global-state"; import * as crypto from "node:crypto"; diff --git a/packages/drivers/memory/src/mod.ts b/packages/core/src/drivers/memory/mod.ts similarity index 86% rename from packages/drivers/memory/src/mod.ts rename to packages/core/src/drivers/memory/mod.ts index 3a0af05a2..0d8b70f1c 100644 --- a/packages/drivers/memory/src/mod.ts +++ b/packages/core/src/drivers/memory/mod.ts @@ -1,4 +1,4 @@ -import type { DriverConfig } from "@rivetkit/core"; +import type { DriverConfig } from "@/registry/run-config"; import { MemoryManagerDriver } from "./manager"; import { MemoryGlobalState } from "./global-state"; import { MemoryWorkerDriver } from "./worker"; diff --git a/packages/drivers/memory/src/worker.ts b/packages/core/src/drivers/memory/worker.ts similarity index 91% rename from packages/drivers/memory/src/worker.ts rename to packages/core/src/drivers/memory/worker.ts index 1d4214fcd..d22cb4d6a 100644 --- a/packages/drivers/memory/src/worker.ts +++ b/packages/core/src/drivers/memory/worker.ts @@ -1,4 +1,4 @@ -import type { WorkerDriver, AnyWorkerInstance } from "@rivetkit/core/driver-helpers"; +import type { WorkerDriver, AnyWorkerInstance } from "@/driver-helpers/mod"; import type { MemoryGlobalState } from "./global-state"; export type WorkerDriverContext = Record; diff --git a/packages/platforms/rivet/src/config.ts b/packages/core/src/drivers/rivet/config.ts similarity index 56% rename from packages/platforms/rivet/src/config.ts rename to packages/core/src/drivers/rivet/config.ts index 082a0b2db..83fd50de0 100644 --- a/packages/platforms/rivet/src/config.ts +++ b/packages/core/src/drivers/rivet/config.ts @@ -1,9 +1,9 @@ -import { RunConfigSchema } from "@rivetkit/core/driver-helpers"; +import { RunConfigSchema } from "@/driver-helpers/mod"; import { z } from "zod"; -export const ConfigSchema = RunConfigSchema.omit({ +export const ConfigSchema = RunConfigSchema.removeDefault().omit({ driver: true, getUpgradeWebSocket: true, -}).default({}); +}).default({}) export type InputConfig = z.input; export type Config = z.infer; diff --git a/packages/core/src/drivers/rivet/conn-routing-handler.ts b/packages/core/src/drivers/rivet/conn-routing-handler.ts new file mode 100644 index 000000000..17986334a --- /dev/null +++ b/packages/core/src/drivers/rivet/conn-routing-handler.ts @@ -0,0 +1,115 @@ +import { logger } from "./log"; +import { type RivetClientConfig } from "./rivet-client"; +import { getWorkerMeta } from "./worker-meta"; +import invariant from "invariant"; +import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { + HEADER_AUTH_DATA, + HEADER_CONN_PARAMS, + HEADER_ENCODING, + HEADER_EXPOSE_INTERNAL_ERROR, +} from "@/driver-helpers/mod"; +import { importWebSocket } from "@/common/websocket"; +import { createWebSocketProxy } from "./ws-proxy"; +import { proxy } from "hono/proxy"; + +export function createRivetConnRoutingHandler( + clientConfig: RivetClientConfig, +): ConnRoutingHandler { + return { + custom: { + sendRequest: async (workerId, workerRequest) => { + const meta = await getWorkerMeta(clientConfig, workerId); + invariant(meta, "worker should exist"); + + const parsedRequestUrl = new URL(workerRequest.url); + const workerUrl = `${meta.endpoint}${parsedRequestUrl.pathname}${parsedRequestUrl.search}`; + + logger().debug("proxying request to rivet worker", { + method: workerRequest.method, + url: workerUrl, + }); + + const proxyRequest = new Request(workerUrl, workerRequest); + return await fetch(proxyRequest); + }, + openWebSocket: async (workerId, encodingKind, params: unknown) => { + const WebSocket = await importWebSocket(); + + const meta = await getWorkerMeta(clientConfig, workerId); + invariant(meta, "worker should exist"); + + const wsEndpoint = meta.endpoint.replace(/^http/, "ws"); + const url = `${wsEndpoint}/connect/websocket`; + + const headers: Record = { + Upgrade: "websocket", + Connection: "Upgrade", + [HEADER_EXPOSE_INTERNAL_ERROR]: "true", + [HEADER_ENCODING]: encodingKind, + }; + if (params) { + headers[HEADER_CONN_PARAMS] = JSON.stringify(params); + } + + logger().debug("opening websocket to worker", { + workerId, + url, + }); + + return new WebSocket(url, { headers }); + }, + proxyRequest: async (c, workerRequest, workerId) => { + const meta = await getWorkerMeta(clientConfig, workerId); + invariant(meta, "worker should exist"); + + const parsedRequestUrl = new URL(workerRequest.url); + const workerUrl = `${meta.endpoint}${parsedRequestUrl.pathname}${parsedRequestUrl.search}`; + + logger().debug("proxying request to rivet worker", { + method: workerRequest.method, + url: workerUrl, + }); + + const proxyRequest = new Request(workerUrl, workerRequest); + return await proxy(proxyRequest); + }, + proxyWebSocket: async ( + c, + path, + workerId, + encoding, + connParmas, + authData, + upgradeWebSocket, + ) => { + const meta = await getWorkerMeta(clientConfig, workerId); + invariant(meta, "worker should exist"); + + const workerUrl = `${meta.endpoint}${path}`; + + logger().debug("proxying websocket to rivet worker", { + url: workerUrl, + }); + + // Build headers + const headers: Record = { + [HEADER_EXPOSE_INTERNAL_ERROR]: "true", + [HEADER_ENCODING]: encoding, + }; + if (connParmas) { + headers[HEADER_CONN_PARAMS] = JSON.stringify(connParmas); + } + if (authData) { + headers[HEADER_AUTH_DATA] = JSON.stringify(authData); + } + + const handlers = await createWebSocketProxy(workerUrl, headers); + + // upgradeWebSocket is middleware, so we need to pass fake handlers + invariant(upgradeWebSocket, "missing upgradeWebSocket"); + return upgradeWebSocket((c) => handlers)(c, async () => {}); + }, + }, + }; +} diff --git a/packages/platforms/rivet/src/log.ts b/packages/core/src/drivers/rivet/log.ts similarity index 68% rename from packages/platforms/rivet/src/log.ts rename to packages/core/src/drivers/rivet/log.ts index 1d4dc4b9a..fcc694b28 100644 --- a/packages/platforms/rivet/src/log.ts +++ b/packages/core/src/drivers/rivet/log.ts @@ -1,4 +1,4 @@ -import { getLogger } from "@rivetkit/core/log"; +import { getLogger } from "@/common/log"; export const LOGGER_NAME = "driver-rivet"; diff --git a/packages/platforms/rivet/src/manager-driver.ts b/packages/core/src/drivers/rivet/manager-driver.ts similarity index 79% rename from packages/platforms/rivet/src/manager-driver.ts rename to packages/core/src/drivers/rivet/manager-driver.ts index d18edcb75..ec3f1b70f 100644 --- a/packages/platforms/rivet/src/manager-driver.ts +++ b/packages/core/src/drivers/rivet/manager-driver.ts @@ -1,5 +1,5 @@ -import { assertUnreachable } from "@rivetkit/core/utils"; -import { WorkerAlreadyExists, InternalError } from "@rivetkit/core/errors"; +import { assertUnreachable } from "@/common/utils"; +import { WorkerAlreadyExists, InternalError } from "@/worker/errors"; import type { ManagerDriver, GetForIdInput, @@ -7,24 +7,26 @@ import type { WorkerOutput, GetOrCreateWithKeyInput, CreateInput, -} from "@rivetkit/core/driver-helpers"; +} from "@/driver-helpers/mod"; import { logger } from "./log"; import { RivetActor, type RivetClientConfig, rivetRequest, } from "./rivet-client"; +import { convertKeyToRivetTags } from "./util"; import { - serializeKeyForTag, - deserializeKeyFromTag, - convertKeyToRivetTags, -} from "./util"; -import { + flushCache, getWorkerMeta, getWorkerMetaWithKey, populateCache, } from "./worker-meta"; import invariant from "invariant"; +import { getEnvUniversal } from "@/utils"; +import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { createRivetConnRoutingHandler } from "./conn-routing-handler"; +import { Hono } from "hono"; +import { Registry, RegistryConfig } from "@/registry/mod"; export interface WorkerState { key: string[]; @@ -38,8 +40,12 @@ export interface GetWorkerMeta { export class RivetManagerDriver implements ManagerDriver { #clientConfig: RivetClientConfig; + readonly connRoutingHandler: ConnRoutingHandler; + constructor(clientConfig: RivetClientConfig) { this.#clientConfig = clientConfig; + + this.connRoutingHandler = createRivetConnRoutingHandler(clientConfig); } async getForId({ @@ -98,13 +104,8 @@ export class RivetManagerDriver implements ManagerDriver { } // Create the worker request - let workerLogLevel: string | undefined = undefined; - if (typeof Deno !== "undefined") { - workerLogLevel = Deno.env.get("_WORKER_LOG_LEVEL"); - } else if (typeof process !== "undefined") { - // Do this after Deno since `process` is sometimes polyfilled - workerLogLevel = process.env._WORKER_LOG_LEVEL; - } + let workerLogLevel: string | undefined = + getEnvUniversal("_WORKER_LOG_LEVEL"); const createRequest = { tags: convertKeyToRivetTags(name, key), @@ -124,6 +125,7 @@ export class RivetManagerDriver implements ManagerDriver { }, runtime: { environment: { + RIVETKIT_DRIVER: "rivet", RIVET_ENDPOINT: this.#clientConfig.endpoint, RIVET_SERVICE_TOKEN: this.#clientConfig.token, RIVET_PROJECT: this.#clientConfig.project, @@ -189,4 +191,14 @@ export class RivetManagerDriver implements ManagerDriver { key: meta.key, }; } + + modifyManagerRouter(registryConfig: RegistryConfig, router: Hono) { + // HACK: Expose endpoint for tests to flush cache + if (registryConfig.test.enabled) { + router.post("/.test/rivet/flush-cache", (c) => { + flushCache(); + return c.text("ok"); + }); + } + } } diff --git a/packages/core/src/drivers/rivet/manager.ts b/packages/core/src/drivers/rivet/manager.ts new file mode 100644 index 000000000..a114f8f73 --- /dev/null +++ b/packages/core/src/drivers/rivet/manager.ts @@ -0,0 +1,106 @@ +import { setupLogging } from "@/common/log"; +import { serve as honoServe } from "@hono/node-server"; +import { createNodeWebSocket, NodeWebSocket } from "@hono/node-ws"; +import { logger } from "./log"; +import { GetWorkerMeta, RivetManagerDriver } from "./manager-driver"; +import type { RivetClientConfig } from "./rivet-client"; +import { PartitionTopologyManager } from "@/topologies/partition/mod"; +import { ConfigSchema, InputConfig } from "./config"; +import type { Registry, RunConfig } from "@/registry/mod"; +import { flushCache, getWorkerMeta } from "./worker-meta"; + +export async function startManager( + registry: Registry, + inputConfig?: InputConfig, +): Promise { + setupLogging(); + + const portStr = process.env.PORT_HTTP; + if (!portStr) { + throw "Missing port"; + } + const port = Number.parseInt(portStr); + if (!Number.isFinite(port)) { + throw "Invalid port"; + } + + const endpoint = process.env.RIVET_ENDPOINT; + if (!endpoint) throw new Error("missing RIVET_ENDPOINT"); + const token = process.env.RIVET_SERVICE_TOKEN; + if (!token) throw new Error("missing RIVET_SERVICE_TOKEN"); + const project = process.env.RIVET_PROJECT; + if (!project) throw new Error("missing RIVET_PROJECT"); + const environment = process.env.RIVET_ENVIRONMENT; + if (!environment) throw new Error("missing RIVET_ENVIRONMENT"); + + const clientConfig: RivetClientConfig = { + endpoint, + token, + project, + environment, + }; + + const config = ConfigSchema.parse(inputConfig); + let injectWebSocket: NodeWebSocket["injectWebSocket"] | undefined; + const runConfig = { + driver: { + topology: "partition", + manager: new RivetManagerDriver(clientConfig), + // HACK: We can't build the worker driver until we're inside the worker + worker: undefined as any, + }, + // Setup WebSocket routing for Node + // + // Save `injectWebSocket` for after server is created + getUpgradeWebSocket: (app) => { + const webSocket = createNodeWebSocket({ app }); + injectWebSocket = webSocket.injectWebSocket; + return webSocket.upgradeWebSocket; + }, + ...config, + } satisfies RunConfig; + + //// Force disable inspector + //driverConfig.registry.config.inspector = { + // enabled: false, + //}; + + //const corsConfig = driverConfig.registry.config.cors; + // + //// Enable CORS for Rivet domains + //driverConfig.registry.config.cors = { + // ...driverConfig.registry.config.cors, + // origin: (origin, c) => { + // const isRivetOrigin = + // origin.endsWith(".rivet.gg") || origin.includes("localhost:"); + // const configOrigin = corsConfig?.origin; + // + // if (isRivetOrigin) { + // return origin; + // } + // if (typeof configOrigin === "function") { + // return configOrigin(origin, c); + // } + // if (typeof configOrigin === "string") { + // return configOrigin; + // } + // return null; + // }, + //}; + + // Create manager topology + const managerTopology = new PartitionTopologyManager( + registry.config, + runConfig, + ); + + // Start server with ambient env wrapper + logger().info("server running", { port }); + const server = honoServe({ + fetch: managerTopology.router.fetch, + hostname: "0.0.0.0", + port, + }); + if (!injectWebSocket) throw new Error("injectWebSocket not defined"); + injectWebSocket(server); +} diff --git a/packages/core/src/drivers/rivet/mod.ts b/packages/core/src/drivers/rivet/mod.ts new file mode 100644 index 000000000..4218e500c --- /dev/null +++ b/packages/core/src/drivers/rivet/mod.ts @@ -0,0 +1,15 @@ +import type { DriverConfig } from "@/registry/run-config"; +import { RivetManagerDriver } from "./manager-driver"; +import { getRivetClientConfig } from "./rivet-client"; + +export function createRivetManagerDriver(): DriverConfig { + const clientConfig = getRivetClientConfig(); + return { + topology: "partition", + manager: new RivetManagerDriver(clientConfig), + // We don't have access to `ActorContext`, so we can't construct this + worker: undefined as any, + }; +} + +export { createWorkerHandler } from "./worker"; diff --git a/packages/platforms/rivet/src/rivet-client.ts b/packages/core/src/drivers/rivet/rivet-client.ts similarity index 65% rename from packages/platforms/rivet/src/rivet-client.ts rename to packages/core/src/drivers/rivet/rivet-client.ts index 812059b37..2a19b3303 100644 --- a/packages/platforms/rivet/src/rivet-client.ts +++ b/packages/core/src/drivers/rivet/rivet-client.ts @@ -1,4 +1,4 @@ -import { httpUserAgent } from "@rivetkit/core/utils"; +import { getEnvUniversal, httpUserAgent } from "@/utils"; export interface RivetClientConfig { endpoint: string; @@ -7,6 +7,24 @@ export interface RivetClientConfig { environment?: string; } +export function getRivetClientConfig(): RivetClientConfig { + const endpoint = getEnvUniversal("RIVET_ENDPOINT"); + if (!endpoint) throw new Error("missing RIVET_ENDPOINT"); + const token = getEnvUniversal("RIVET_SERVICE_TOKEN"); + if (!token) throw new Error("missing RIVET_SERVICE_TOKEN"); + const project = getEnvUniversal("RIVET_PROJECT"); + if (!project) throw new Error("missing RIVET_PROJECT"); + const environment = getEnvUniversal("RIVET_ENVIRONMENT"); + if (!environment) throw new Error("missing RIVET_ENVIRONMENT"); + + return { + endpoint, + token, + project, + environment, + }; +} + // biome-ignore lint/suspicious/noExplicitAny: will add api types later export type RivetActor = any; // biome-ignore lint/suspicious/noExplicitAny: will add api types later diff --git a/packages/platforms/rivet/src/util.ts b/packages/core/src/drivers/rivet/util.ts similarity index 100% rename from packages/platforms/rivet/src/util.ts rename to packages/core/src/drivers/rivet/util.ts diff --git a/packages/platforms/rivet/src/worker-driver.ts b/packages/core/src/drivers/rivet/worker-driver.ts similarity index 94% rename from packages/platforms/rivet/src/worker-driver.ts rename to packages/core/src/drivers/rivet/worker-driver.ts index f6453fb56..9533f8a01 100644 --- a/packages/platforms/rivet/src/worker-driver.ts +++ b/packages/core/src/drivers/rivet/worker-driver.ts @@ -1,5 +1,5 @@ import { ActorContext } from "@rivet-gg/actor-core"; -import type { WorkerDriver, AnyWorkerInstance } from "@rivetkit/core/driver-helpers"; +import type { WorkerDriver, AnyWorkerInstance } from "@/driver-helpers/mod"; export interface WorkerDriverContext { ctx: ActorContext; diff --git a/packages/platforms/rivet/src/worker-meta.ts b/packages/core/src/drivers/rivet/worker-meta.ts similarity index 99% rename from packages/platforms/rivet/src/worker-meta.ts rename to packages/core/src/drivers/rivet/worker-meta.ts index f3d570926..f55ee17f5 100644 --- a/packages/platforms/rivet/src/worker-meta.ts +++ b/packages/core/src/drivers/rivet/worker-meta.ts @@ -1,4 +1,4 @@ -import { assertUnreachable } from "@rivetkit/core/utils"; +import { assertUnreachable } from "@/utils"; import { RivetActor, RivetClientConfig, rivetRequest } from "./rivet-client"; import { deserializeKeyFromTag, convertKeyToRivetTags } from "./util"; import invariant from "invariant"; diff --git a/packages/platforms/rivet/src/worker.ts b/packages/core/src/drivers/rivet/worker.ts similarity index 83% rename from packages/platforms/rivet/src/worker.ts rename to packages/core/src/drivers/rivet/worker.ts index 2c67c8b6e..2e2c9a256 100644 --- a/packages/platforms/rivet/src/worker.ts +++ b/packages/core/src/drivers/rivet/worker.ts @@ -1,20 +1,19 @@ -import { setupLogging } from "@rivetkit/core/log"; -import { upgradeWebSocket } from "hono/deno"; +import { setupLogging } from "@/common/log"; import { logger } from "./log"; import { deserializeKeyFromTag, type RivetHandler } from "./util"; -import { PartitionTopologyWorker } from "@rivetkit/core/topologies/partition"; +import { PartitionTopologyWorker } from "@/topologies/partition/mod"; import { RivetWorkerDriver } from "./worker-driver"; import invariant from "invariant"; import type { ActorContext } from "@rivet-gg/actor-core"; -import { Registry, RunConfig } from "@rivetkit/core"; +import { Registry, RunConfig } from "@/registry/mod"; import { type Config, ConfigSchema, type InputConfig } from "./config"; -import { stringifyError } from "@rivetkit/core/utils"; +import { stringifyError } from "@/common/utils"; import { RivetManagerDriver } from "./manager-driver"; -import { RivetClientConfig } from "./rivet-client"; +import { getRivetClientConfig, RivetClientConfig } from "./rivet-client"; export function createWorkerHandler( registry: Registry, - inputConfig?: InputConfig, + inputConfig?: InputConfig ): RivetHandler { let config: Config; try { @@ -41,6 +40,8 @@ async function startWorker( registry: Registry, config: Config, ): Promise { + const { upgradeWebSocket } = await import("hono/deno"); + setupLogging(); const portStr = Deno.env.get("PORT_HTTP"); @@ -52,21 +53,7 @@ async function startWorker( throw "Invalid port"; } - const endpoint = Deno.env.get("RIVET_ENDPOINT"); - if (!endpoint) throw new Error("missing RIVET_ENDPOINT"); - const token = Deno.env.get("RIVET_SERVICE_TOKEN"); - if (!token) throw new Error("missing RIVET_SERVICE_TOKEN"); - const project = Deno.env.get("RIVET_PROJECT"); - if (!project) throw new Error("missing RIVET_PROJECT"); - const environment = Deno.env.get("RIVET_ENVIRONMENT"); - if (!environment) throw new Error("missing RIVET_ENVIRONMENT"); - - const clientConfig: RivetClientConfig = { - endpoint, - token, - project, - environment, - }; + const clientConfig: RivetClientConfig = getRivetClientConfig(); const runConfig = { driver: { diff --git a/packages/platforms/rivet/src/ws-proxy.ts b/packages/core/src/drivers/rivet/ws-proxy.ts similarity index 97% rename from packages/platforms/rivet/src/ws-proxy.ts rename to packages/core/src/drivers/rivet/ws-proxy.ts index e14d92608..f87981eda 100644 --- a/packages/platforms/rivet/src/ws-proxy.ts +++ b/packages/core/src/drivers/rivet/ws-proxy.ts @@ -2,7 +2,7 @@ import { WSContext } from "hono/ws"; import { logger } from "./log"; import invariant from "invariant"; import type { WebSocket, CloseEvent } from "ws"; -import { importWebSocket } from "@rivetkit/core/driver-helpers/websocket"; +import { importWebSocket } from "@/common/websocket"; /** * Creates a WebSocket proxy to forward connections to a target endpoint diff --git a/packages/core/src/manager/driver.ts b/packages/core/src/manager/driver.ts index 02c8b2b7a..5d887fec0 100644 --- a/packages/core/src/manager/driver.ts +++ b/packages/core/src/manager/driver.ts @@ -1,6 +1,8 @@ import { ClientDriver } from "@/client/client"; import type { WorkerKey } from "@/common/utils"; -import type { Env, Context as HonoContext, HonoRequest } from "hono"; +import { RegistryConfig } from "@/registry/config"; +import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import type { Env, Hono, Context as HonoContext } from "hono"; export interface ManagerDriver { getForId(input: GetForIdInput): Promise; @@ -8,6 +10,10 @@ export interface ManagerDriver { getOrCreateWithKey(input: GetOrCreateWithKeyInput): Promise; createWorker(input: CreateInput): Promise; + readonly connRoutingHandler?: ConnRoutingHandler; + + modifyManagerRouter?: (registryConfig: RegistryConfig, router: Hono) => void; + // inspector?: ManagerInspector; } export interface GetForIdInput { diff --git a/packages/core/src/manager/router.ts b/packages/core/src/manager/router.ts index 7df8d6936..5a55b8427 100644 --- a/packages/core/src/manager/router.ts +++ b/packages/core/src/manager/router.ts @@ -59,8 +59,7 @@ import { ClientDriver } from "@/client/client"; import { Transport } from "@/worker/protocol/message/mod"; import { authenticateEndpoint } from "./auth"; import type { WebSocket, MessageEvent, CloseEvent } from "ws"; -import { DriverConfig, RunConfig } from "@/registry/run-config"; -import { basePath, baseRoutePath, routePath } from "hono/route"; +import { RunConfig } from "@/registry/run-config"; type ManagerRouterHandler = { // onConnectInspector?: ManagerInspectorConnHandler; @@ -118,9 +117,9 @@ export function createManagerRouter( runConfig: RunConfig, inlineClientDriver: ClientDriver, handler: ManagerRouterHandler, -) { +): { router: Hono; openapi: OpenAPIHono } { const driver = runConfig.driver.manager; - const router = new OpenAPIHono(); + const router = new OpenAPIHono({ strict: false }); const upgradeWebSocket = runConfig.getUpgradeWebSocket?.( router as unknown as Hono, @@ -562,12 +561,24 @@ export function createManagerRouter( }, }); - router.get("/foo", (c) => c.text("foo")); + driver.modifyManagerRouter?.(registryConfig, router as unknown as Hono); router.notFound(handleRouteNotFound); router.onError(handleRouteError.bind(undefined, {})); - return router as unknown as Hono; + // Mount on both / and /registry + // + // We do this because the default requests are to `/registry/*`. + // + // If using `app.fetch` directly in a non-hono router, paths + // might not be truncated so they'll come to this router as + // `/registry/*`. If mounted correctly in Hono, requests will + // come in at the root as `/*`. + const mountedRouter = new Hono(); + mountedRouter.route("/", router); + mountedRouter.route("/registry", router); + + return { router: mountedRouter, openapi: router }; } export interface TestInlineDriverCallRequest { diff --git a/packages/core/src/registry/mod.ts b/packages/core/src/registry/mod.ts index f3876cc1f..b282e4cc1 100644 --- a/packages/core/src/registry/mod.ts +++ b/packages/core/src/registry/mod.ts @@ -13,13 +13,23 @@ import { } from "./run-config"; import { StandaloneTopology } from "@/topologies/standalone/mod"; import invariant from "invariant"; -import type { Hono } from "hono"; +import { Hono } from "hono"; import { assertUnreachable } from "@/utils"; +import { PartitionTopologyManager, PartitionTopologyWorker } from "@/mod"; +import { logger } from "./log"; +import { crossPlatformServe } from "./serve"; -interface RunOutput> { +interface ServerOutput> { client: Client; hono: Hono; handler: (req: Request) => Promise; + serve: (hono?: Hono) => void; +} + +interface WorkerNodeOutput { + hono: Hono; + handler: (req: Request) => Promise; + serve: (hono?: Hono) => void; } export class Registry { @@ -33,7 +43,10 @@ export class Registry { this.#config = config; } - public run(inputConfig: RunConfigInput): RunOutput { + /** + * Runs the registry for a server. + */ + public server(inputConfig?: RunConfigInput): ServerOutput { const config = RunConfigSchema.parse(inputConfig); // Setup topology @@ -44,8 +57,9 @@ export class Registry { hono = topology.router; clientDriver = topology.clientDriver; } else if (config.driver.topology === "partition") { - // TODO: - invariant(false, "foo"); + const topology = new PartitionTopologyManager(this.#config, config); + hono = topology.router; + clientDriver = topology.clientDriver; } else if (config.driver.topology === "coordinate") { const topology = new StandaloneTopology(this.#config, config); hono = topology.router; @@ -61,12 +75,51 @@ export class Registry { client, hono, handler: async (req: Request) => await hono.fetch(req), + serve: (app) => crossPlatformServe(config, hono, app), }; } - // public runAndServe(): RunOutput { - // // TODO: - // } + /** + * Runs the registry as a standalone server. + */ + public async runServer(inputConfig?: RunConfigInput) { + const { serve } = this.server(inputConfig); + serve(); + } + + /** + * Runs the registry for a worker node. + */ + public workerNode(inputConfig?: RunConfigInput): WorkerNodeOutput { + const config = RunConfigSchema.parse(inputConfig); + + // Setup topology + let hono: Hono; + if (config.driver.topology === "standalone") { + invariant(false, "cannot run worker node for standalone topology"); + } else if (config.driver.topology === "partition") { + const topology = new PartitionTopologyWorker(this.#config, config); + hono = topology.router; + } else if (config.driver.topology === "coordinate") { + invariant(false, "cannot run worker node for coordinate topology"); + } else { + assertUnreachable(config.driver.topology); + } + + return { + hono, + handler: async (req: Request) => await hono.fetch(req), + serve: (app) => crossPlatformServe(config, hono, app), + }; + } + + /** + * Runs the standalone worker node. + */ + public async runWorkerNode(inputConfig?: RunConfigInput) { + const { serve } = this.workerNode(inputConfig); + serve(); + } } export function setup( diff --git a/packages/core/src/registry/run-config.ts b/packages/core/src/registry/run-config.ts index 37e7ba383..b86066f03 100644 --- a/packages/core/src/registry/run-config.ts +++ b/packages/core/src/registry/run-config.ts @@ -5,6 +5,7 @@ import type { ManagerDriver } from "@/manager/driver"; import type { WorkerDriver } from "@/worker/driver"; import type { UpgradeWebSocket } from "@/utils"; import type { cors } from "hono/cors"; +import { createMemoryDriver } from "@/drivers/memory/mod"; type CorsOptions = NonNullable[0]>; @@ -57,22 +58,23 @@ export const DriverConfigSchema = z.object({ export type DriverConfig = z.infer; /** Base config used for the worker config across all platforms. */ -export const RunConfigSchema = z.object({ - driver: DriverConfigSchema, +export const RunConfigSchema = z + .object({ + driver: DriverConfigSchema.optional().default(() => createMemoryDriver()), - // This is dynamic since NodeJS requires a reference to the router to initialize WebSockets - getUpgradeWebSocket: z.custom().optional(), + // This is dynamic since NodeJS requires a reference to the router to initialize WebSockets + getUpgradeWebSocket: z.custom().optional(), - /** CORS configuration for the router. Uses Hono's CORS middleware options. */ - cors: z.custom().optional(), + /** CORS configuration for the router. Uses Hono's CORS middleware options. */ + cors: z.custom().optional(), - maxIncomingMessageSize: z.number().optional().default(65_536), + maxIncomingMessageSize: z.number().optional().default(65_536), - /** Peer configuration for coordinated topology. */ - workerPeer: WorkerPeerConfigSchema.optional().default({}), + /** Peer configuration for coordinated topology. */ + workerPeer: WorkerPeerConfigSchema.optional().default({}), - // inspector: InspectorConfigSchema.optional().default({ enabled: false }), -}); + // inspector: InspectorConfigSchema.optional().default({ enabled: false }), + }).default({}); export type RunConfig = z.infer; export type RunConfigInput = z.input; diff --git a/packages/core/src/registry/serve.ts b/packages/core/src/registry/serve.ts new file mode 100644 index 000000000..4c2b48aa9 --- /dev/null +++ b/packages/core/src/registry/serve.ts @@ -0,0 +1,61 @@ +import { Hono } from "hono"; +import { logger } from "./log"; +import { RunConfig } from "./run-config"; +import { getEnvUniversal } from "@/utils"; + +export async function crossPlatformServe( + config: RunConfig, + rivetKitRouter: Hono, + userRouter: Hono | undefined, +) { + const app = userRouter ?? new Hono(); + + // Import @hono/node-server + let serve; + try { + const dep = await import("@hono/node-server"); + serve = dep.serve; + } catch (err) { + logger().error( + "failed to import @hono/node-server. please run 'npm install @hono/node-server @hono/node-ws'", + ); + process.exit(1); + } + + app.use("*", async (c, next) => { + logger().info("request", { path: c.req.path }); + await next(); + }); + + // Mount registry + app.route("/registry", rivetKitRouter); + + // Import @hono/node-ws + let createNodeWebSocket; + try { + const dep = await import("@hono/node-ws"); + createNodeWebSocket = dep.createNodeWebSocket; + } catch (err) { + logger().error( + "failed to import @hono/node-ws. please run 'npm install @hono/node-server @hono/node-ws'", + ); + process.exit(1); + } + + // Inject WS + const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ + app, + }); + + // Update config for new WS + config.getUpgradeWebSocket = () => upgradeWebSocket; + + // Start server + const port = parseInt( + getEnvUniversal("PORT") ?? getEnvUniversal("PORT_HTTP") ?? "8080", + ); + const server = serve({ fetch: app.fetch, port }, () => + logger().info(`listening on port ${port}`), + ); + injectWebSocket(server); +} diff --git a/packages/core/src/test/config.ts b/packages/core/src/test/config.ts index d74f16d8d..7c1b8444b 100644 --- a/packages/core/src/test/config.ts +++ b/packages/core/src/test/config.ts @@ -1,14 +1,17 @@ import { RunConfigSchema } from "@/registry/run-config"; import { z } from "zod"; -export const ConfigSchema = RunConfigSchema.extend({ - hostname: z - .string() - .optional() - .default(process.env.HOSTNAME ?? "127.0.0.1"), - port: z - .number() - .optional() - .default(Number.parseInt(process.env.PORT ?? "6420")), -}).partial({ driver: true }); +export const ConfigSchema = RunConfigSchema.removeDefault() + .extend({ + hostname: z + .string() + .optional() + .default(process.env.HOSTNAME ?? "127.0.0.1"), + port: z + .number() + .optional() + .default(Number.parseInt(process.env.PORT ?? "6420")), + }) + .partial({ driver: true }) + .default({}); export type InputConfig = z.input; diff --git a/packages/core/src/topologies/coordinate/topology.ts b/packages/core/src/topologies/coordinate/topology.ts index f7e931309..f0ade50dd 100644 --- a/packages/core/src/topologies/coordinate/topology.ts +++ b/packages/core/src/topologies/coordinate/topology.ts @@ -127,7 +127,7 @@ export class CoordinateTopology { this.clientDriver = createInlineClientDriver(managerDriver, routingHandler); // Build manager router - const managerRouter = createManagerRouter( + const { router: managerRouter } = createManagerRouter( registryConfig, runConfig, this.clientDriver, diff --git a/packages/core/src/topologies/partition/topology.ts b/packages/core/src/topologies/partition/topology.ts index 463f807f1..7e8b6a107 100644 --- a/packages/core/src/topologies/partition/topology.ts +++ b/packages/core/src/topologies/partition/topology.ts @@ -33,18 +33,11 @@ import type { ActionOutput, } from "@/worker/router-endpoints"; import { ClientDriver } from "@/client/client"; -import { ToServer } from "@/worker/protocol/message/to-server"; -import { WorkerQuery } from "@/manager/protocol/query"; -import { Encoding } from "@/mod"; -import { EventSource } from "eventsource"; import { createInlineClientDriver } from "@/inline-client-driver/mod"; -import { - ConnRoutingHandler, - ConnRoutingHandlerCustom, -} from "@/worker/conn-routing-handler"; +import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; import invariant from "invariant"; import type { WebSocket } from "ws"; -import type { DriverConfig, RunConfig } from "@/registry/run-config"; +import type { RunConfig } from "@/registry/run-config"; export type SendRequestHandler = ( workerRequest: Request, @@ -60,20 +53,18 @@ export class PartitionTopologyManager { clientDriver: ClientDriver; router: Hono; - constructor( - registryConfig: RegistryConfig, - runConfig: RunConfig, - customRoutingHandlers: ConnRoutingHandlerCustom, - ) { - const routingHandler: ConnRoutingHandler = { - custom: customRoutingHandlers, - }; + constructor(registryConfig: RegistryConfig, runConfig: RunConfig) { + const routingHandler = runConfig.driver.manager.connRoutingHandler; + invariant( + routingHandler, + "partition run config must provide custom routing handler", + ); const managerDriver = runConfig.driver.manager; invariant(managerDriver, "missing manager driver"); this.clientDriver = createInlineClientDriver(managerDriver, routingHandler); - this.router = createManagerRouter( + const {router}= createManagerRouter( registryConfig, runConfig, this.clientDriver, @@ -105,6 +96,7 @@ export class PartitionTopologyManager { // }, }, ); + this.router = router; } } diff --git a/packages/core/src/topologies/partition/worker-router.ts b/packages/core/src/topologies/partition/worker-router.ts index 21f3bba4c..1bde816ca 100644 --- a/packages/core/src/topologies/partition/worker-router.ts +++ b/packages/core/src/topologies/partition/worker-router.ts @@ -58,7 +58,7 @@ export function createWorkerRouter( runConfig: RunConfig, handler: WorkerRouterHandler, ): Hono { - const router = new Hono(); + const router = new Hono({ strict: false }); const upgradeWebSocket = runConfig.getUpgradeWebSocket?.(router); diff --git a/packages/core/src/topologies/standalone/conn-routing-handlers.ts b/packages/core/src/topologies/standalone/conn-routing-handlers.ts new file mode 100644 index 000000000..ff0cc87e5 --- /dev/null +++ b/packages/core/src/topologies/standalone/conn-routing-handlers.ts @@ -0,0 +1,29 @@ +import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { + type AnyConn, + generateConnId, + generateConnToken, +} from "@/worker/connection"; +import * as errors from "@/worker/errors"; +import { + CONN_DRIVER_GENERIC_HTTP, + CONN_DRIVER_GENERIC_SSE, + CONN_DRIVER_GENERIC_WEBSOCKET, + type GenericHttpDriverState, + type GenericSseDriverState, + type GenericWebSocketDriverState, +} from "../common/generic-conn-driver"; +import { ActionContext } from "@/worker/action"; +import type { + ConnectWebSocketOpts, + ConnectWebSocketOutput, + ConnectSseOpts, + ConnectSseOutput, + ConnsMessageOpts, + ActionOpts, + ActionOutput, + ConnectionHandlers, +} from "@/worker/router-endpoints"; +import { StandaloneTopology } from "@/mod"; +import { logger } from "./log"; + diff --git a/packages/core/src/topologies/standalone/topology.ts b/packages/core/src/topologies/standalone/topology.ts index bc2c67f52..cb0f5cc2b 100644 --- a/packages/core/src/topologies/standalone/topology.ts +++ b/packages/core/src/topologies/standalone/topology.ts @@ -58,6 +58,59 @@ export class StandaloneTopology { #runConfig: RunConfig; #workers = new Map(); + constructor(registryConfig: RegistryConfig, runConfig: RunConfig) { + this.#registryConfig = registryConfig; + this.#runConfig = runConfig; + + // Build router + const router = new Hono(); + + const routingHandler: ConnRoutingHandler = this.#createRoutingHandlers(); + + // Build client driver + const managerDriver = this.#runConfig.driver.manager; + invariant(managerDriver, "missing manager driver"); + this.clientDriver = createInlineClientDriver(managerDriver, routingHandler); + + // Build manager router + const { router: managerRouter } = createManagerRouter( + registryConfig, + runConfig, + this.clientDriver, + { + routingHandler, + // onConnectInspector: async () => { + // const inspector = driverConfig.drivers?.manager?.inspector; + // if (!inspector) throw new errors.Unsupported("inspector"); + // + // let conn: ManagerInspectorConnection | undefined; + // return { + // onOpen: async (ws) => { + // conn = inspector.createConnection(ws); + // }, + // onMessage: async (message) => { + // if (!conn) { + // logger().warn("`conn` does not exist"); + // return; + // } + // + // inspector.processMessage(conn, message); + // }, + // onClose: async () => { + // if (conn) { + // inspector.removeConnection(conn); + // } + // }, + // }; + // }, + }, + ); + + router.route("/", managerRouter); + + this.router = router; + } + async #getWorker( workerId: string, ): Promise<{ handler: WorkerHandler; worker: AnyWorkerInstance }> { @@ -113,17 +166,8 @@ export class StandaloneTopology { return { handler, worker }; } - constructor(registryConfig: RegistryConfig, runConfig: RunConfig) { - this.#registryConfig = registryConfig; - this.#runConfig = runConfig; - - // Build router - const router = new Hono(); - - const upgradeWebSocket = runConfig.getUpgradeWebSocket?.(router); - - // Create shared connection handlers that will be used by both manager and worker routers - const sharedConnectionHandlers: ConnectionHandlers = { + #createRoutingHandlers(): ConnRoutingHandler { + const handlers: ConnectionHandlers = { onConnectWebSocket: async ( opts: ConnectWebSocketOpts, ): Promise => { @@ -257,51 +301,6 @@ export class StandaloneTopology { }, }; - const routingHandler: ConnRoutingHandler = { - inline: { handlers: sharedConnectionHandlers }, - }; - - // Build client driver - const managerDriver = this.#runConfig.driver.manager; - invariant(managerDriver, "missing manager driver"); - this.clientDriver = createInlineClientDriver(managerDriver, routingHandler); - - // Build manager router - const managerRouter = createManagerRouter( - registryConfig, - runConfig, - this.clientDriver, - { - routingHandler, - // onConnectInspector: async () => { - // const inspector = driverConfig.drivers?.manager?.inspector; - // if (!inspector) throw new errors.Unsupported("inspector"); - // - // let conn: ManagerInspectorConnection | undefined; - // return { - // onOpen: async (ws) => { - // conn = inspector.createConnection(ws); - // }, - // onMessage: async (message) => { - // if (!conn) { - // logger().warn("`conn` does not exist"); - // return; - // } - // - // inspector.processMessage(conn, message); - // }, - // onClose: async () => { - // if (conn) { - // inspector.removeConnection(conn); - // } - // }, - // }; - // }, - }, - ); - - router.route("/", managerRouter); - - this.router = router; + return { inline: { handlers } }; } } diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 4b06e4bf7..3a2abceec 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -3,6 +3,10 @@ export { stringifyError } from "@/common/utils"; import { Context as HonoContext, Handler as HonoHandler } from "hono"; import pkgJson from "../package.json" with { type: "json" }; +import { DriverConfig, UserError } from "./mod"; +import { createMemoryDriver } from "./drivers/memory/mod"; +import { createRivetManagerDriver } from "./drivers/rivet/mod"; +import { logger } from "./worker/log"; export const VERSION = pkgJson.version; @@ -29,3 +33,29 @@ export function httpUserAgent(): string { export type UpgradeWebSocket = ( createEvents: (c: HonoContext) => any, ) => HonoHandler; + +/** + * Determines which driver to use if none is provided. + */ +export function getDefaultDriver(): DriverConfig { + const driver = getEnvUniversal("RIVETKIT_DRIVER"); + console.log("driver", driver); + if (!driver || driver === "memory") { + logger().info("using default memory driver"); + return createMemoryDriver(); + } else if (driver === "rivet") { + logger().info("using default rivet driver"); + return createRivetManagerDriver(); + } else { + throw new UserError(`Unrecognized driver: ${driver}`); + } +} + +export function getEnvUniversal(key: string): string | undefined { + if (typeof Deno !== "undefined") { + return Deno.env.get(key); + } else if (typeof process !== "undefined") { + // Do this after Deno since `process` is sometimes polyfilled + return process.env[key]; + } +} diff --git a/packages/core/tests/rivet/deployment.test.ts b/packages/core/tests/rivet/deployment.test.ts new file mode 100644 index 000000000..f6f311878 --- /dev/null +++ b/packages/core/tests/rivet/deployment.test.ts @@ -0,0 +1,40 @@ +// import { describe, test, expect, beforeAll, afterAll } from "vitest"; +// import os from "node:os"; +// import fs from "node:fs/promises"; +// import path from "node:path"; +// import { fileURLToPath } from "node:url"; +// import { deployToRivet } from "./rivet-deploy"; +// import { randomUUID } from "node:crypto"; +// +// const __dirname = path.dirname(fileURLToPath(import.meta.url)); +// +// // Simple counter worker definition to deploy +// const COUNTER_WORKER = ` +// import { worker, setup } from "@rivetkit/core"; +// +// const counter = worker({ +// state: { count: 0 }, +// actions: { +// increment: (c, amount) => { +// c.state.count += amount; +// c.broadcast("newCount", c.state.count); +// return c.state.count; +// }, +// getCount: (c) => { +// return c.state.count; +// }, +// }, +// }); +// +// export const registry = setup({ +// workers: { counter }, +// }); +// +// export type Registry = typeof registry; +// `; +// +// test("Rivet deployment tests", async () => { +// const tempFilePath = path.join(os.tmpdir(), `registry-${randomUUID()}`); +// await fs.writeFile(tempFilePath, COUNTER_WORKER); +// await deployToRivet("test-registry", tempFilePath, true); +// }); diff --git a/packages/core/tests/rivet/driver-tests.test.ts b/packages/core/tests/rivet/driver-tests.test.ts new file mode 100644 index 000000000..500933cca --- /dev/null +++ b/packages/core/tests/rivet/driver-tests.test.ts @@ -0,0 +1,57 @@ +// import { runDriverTests } from "@rivetkit/core/driver-test-suite"; +// import { deployToRivet, rivetClientConfig } from "./rivet-deploy"; +// import { RivetClientConfig, rivetRequest } from "../src/rivet-client"; +// import invariant from "invariant"; +// +// let deployProjectOnce: Promise | undefined = undefined; +// +// // IMPORTANT: Unlike other tests, Rivet tests are ran without parallelism since we reuse the same shared environment. Eventually we can create an environment per test to create isolated instances. +// runDriverTests({ +// useRealTimers: true, +// HACK_skipCleanupNet: true, +// async start(projectPath: string) { +// // Setup project +// if (!deployProjectOnce) { +// deployProjectOnce = deployToRivet(projectPath); +// } +// const endpoint = await deployProjectOnce; +// +// // Cleanup workers from previous tests +// await deleteAllWorkers(rivetClientConfig); +// +// // Flush cache since we manually updated the workers +// const res = await fetch(`${endpoint}/.test/rivet/flush-cache`, { +// method: "POST", +// }); +// invariant(res.ok, `request failed: ${res.status}`); +// +// return { +// endpoint, +// async cleanup() { +// // This takes time and slows down tests -- it's fine if we leak workers that'll be cleaned up in the next run +// // await deleteAllWorkers(rivetClientConfig); +// }, +// }; +// }, +// }); +// +// async function deleteAllWorkers(clientConfig: RivetClientConfig) { +// // TODO: This is not paginated +// +// console.log("Listing workers to delete"); +// const { actors } = await rivetRequest< +// void, +// { actors: { id: string; tags: Record }[] } +// >(clientConfig, "GET", "/actors"); +// +// for (const actor of actors) { +// if (actor.tags.role !== "worker") continue; +// +// console.log(`Deleting worker ${actor.id} (${JSON.stringify(actor.tags)})`); +// await rivetRequest( +// clientConfig, +// "DELETE", +// `/actors/${actor.id}`, +// ); +// } +// } diff --git a/packages/core/tests/rivet/key-serialization.test.ts b/packages/core/tests/rivet/key-serialization.test.ts new file mode 100644 index 000000000..7684ddaff --- /dev/null +++ b/packages/core/tests/rivet/key-serialization.test.ts @@ -0,0 +1,197 @@ +// import { describe, test, expect } from "vitest"; +// import { serializeKeyForTag, deserializeKeyFromTag, EMPTY_KEY, KEY_SEPARATOR } from "../src/util"; +// +// describe("Key serialization and deserialization", () => { +// // Test serialization +// describe("serializeKeyForTag", () => { +// test("serializes empty key array", () => { +// expect(serializeKeyForTag([])).toBe(EMPTY_KEY); +// }); +// +// test("serializes single key", () => { +// expect(serializeKeyForTag(["test"])).toBe("test"); +// }); +// +// test("serializes multiple keys", () => { +// expect(serializeKeyForTag(["a", "b", "c"])).toBe(`a${KEY_SEPARATOR}b${KEY_SEPARATOR}c`); +// }); +// +// test("escapes commas in keys", () => { +// expect(serializeKeyForTag(["a,b"])).toBe("a\\,b"); +// expect(serializeKeyForTag(["a,b", "c"])).toBe(`a\\,b${KEY_SEPARATOR}c`); +// }); +// +// test("escapes empty key marker in keys", () => { +// expect(serializeKeyForTag([EMPTY_KEY])).toBe(`\\${EMPTY_KEY}`); +// }); +// +// test("handles complex keys", () => { +// expect(serializeKeyForTag(["a,b", EMPTY_KEY, "c,d"])).toBe(`a\\,b${KEY_SEPARATOR}\\${EMPTY_KEY}${KEY_SEPARATOR}c\\,d`); +// }); +// }); +// +// // Test deserialization +// describe("deserializeKeyFromTag", () => { +// test("deserializes empty string", () => { +// expect(deserializeKeyFromTag("")).toEqual([]); +// }); +// +// test("deserializes undefined/null", () => { +// expect(deserializeKeyFromTag(undefined as unknown as string)).toEqual([]); +// expect(deserializeKeyFromTag(null as unknown as string)).toEqual([]); +// }); +// +// test("deserializes empty key marker", () => { +// expect(deserializeKeyFromTag(EMPTY_KEY)).toEqual([]); +// }); +// +// test("deserializes single key", () => { +// expect(deserializeKeyFromTag("test")).toEqual(["test"]); +// }); +// +// test("deserializes multiple keys", () => { +// expect(deserializeKeyFromTag(`a${KEY_SEPARATOR}b${KEY_SEPARATOR}c`)).toEqual(["a", "b", "c"]); +// }); +// +// test("deserializes keys with escaped commas", () => { +// expect(deserializeKeyFromTag("a\\,b")).toEqual(["a,b"]); +// expect(deserializeKeyFromTag(`a\\,b${KEY_SEPARATOR}c`)).toEqual(["a,b", "c"]); +// }); +// +// test("deserializes keys with escaped empty key marker", () => { +// expect(deserializeKeyFromTag(`\\${EMPTY_KEY}`)).toEqual([EMPTY_KEY]); +// }); +// +// test("deserializes complex keys", () => { +// expect(deserializeKeyFromTag(`a\\,b${KEY_SEPARATOR}\\${EMPTY_KEY}${KEY_SEPARATOR}c\\,d`)).toEqual(["a,b", EMPTY_KEY, "c,d"]); +// }); +// }); +// +// // Test roundtrip +// describe("roundtrip", () => { +// const testKeys = [ +// [], +// ["test"], +// ["a", "b", "c"], +// ["a,b", "c"], +// [EMPTY_KEY], +// ["a,b", EMPTY_KEY, "c,d"], +// ["special\\chars", "more:complex,keys", "final key"] +// ]; +// +// testKeys.forEach(key => { +// test(`roundtrip: ${JSON.stringify(key)}`, () => { +// const serialized = serializeKeyForTag(key); +// const deserialized = deserializeKeyFromTag(serialized); +// expect(deserialized).toEqual(key); +// }); +// }); +// +// test("handles all test cases in a large batch", () => { +// for (const key of testKeys) { +// const serialized = serializeKeyForTag(key); +// const deserialized = deserializeKeyFromTag(serialized); +// expect(deserialized).toEqual(key); +// } +// }); +// }); +// +// // Test edge cases +// describe("edge cases", () => { +// test("handles backslash at the end", () => { +// const key = ["abc\\"]; +// const serialized = serializeKeyForTag(key); +// const deserialized = deserializeKeyFromTag(serialized); +// expect(deserialized).toEqual(key); +// }); +// +// test("handles backslashes in middle of string", () => { +// const keys = [ +// ["abc\\def"], +// ["abc\\\\def"], +// ["path\\to\\file"] +// ]; +// +// for (const key of keys) { +// const serialized = serializeKeyForTag(key); +// const deserialized = deserializeKeyFromTag(serialized); +// expect(deserialized).toEqual(key); +// } +// }); +// +// test("handles commas at the end of strings", () => { +// const serialized = serializeKeyForTag(["abc\\,"]); +// expect(deserializeKeyFromTag(serialized)).toEqual(["abc\\,"]); +// }); +// +// test("handles mixed backslashes and commas", () => { +// const keys = [ +// ["path\\to\\file,dir"], +// ["file\\with,comma"], +// ["path\\to\\file", "with,comma"] +// ]; +// +// for (const key of keys) { +// const serialized = serializeKeyForTag(key); +// const deserialized = deserializeKeyFromTag(serialized); +// expect(deserialized).toEqual(key); +// } +// }); +// +// test("handles multiple consecutive commas", () => { +// const key = ["a,,b"]; +// const serialized = serializeKeyForTag(key); +// const deserialized = deserializeKeyFromTag(serialized); +// expect(deserialized).toEqual(key); +// }); +// +// test("handles special characters", () => { +// const key = ["a💻b", "c🔑d"]; +// const serialized = serializeKeyForTag(key); +// const deserialized = deserializeKeyFromTag(serialized); +// expect(deserialized).toEqual(key); +// }); +// +// test("handles escaped commas immediately after separator", () => { +// const key = ["abc", ",def"]; +// const serialized = serializeKeyForTag(key); +// expect(serialized).toBe(`abc${KEY_SEPARATOR}\\,def`); +// expect(deserializeKeyFromTag(serialized)).toEqual(key); +// }); +// }); +// +// // Test exact key matching +// describe("exact key matching", () => { +// test("differentiates [a,b] from [a,b,c]", () => { +// const key1 = ["a", "b"]; +// const key2 = ["a", "b", "c"]; +// +// const serialized1 = serializeKeyForTag(key1); +// const serialized2 = serializeKeyForTag(key2); +// +// expect(serialized1).not.toBe(serialized2); +// }); +// +// test("differentiates [a,b] from [a]", () => { +// const key1 = ["a", "b"]; +// const key2 = ["a"]; +// +// const serialized1 = serializeKeyForTag(key1); +// const serialized2 = serializeKeyForTag(key2); +// +// expect(serialized1).not.toBe(serialized2); +// }); +// +// test("differentiates [a,b] from [a:b]", () => { +// const key1 = ["a,b"]; +// const key2 = ["a", "b"]; +// +// const serialized1 = serializeKeyForTag(key1); +// const serialized2 = serializeKeyForTag(key2); +// +// expect(serialized1).not.toBe(serialized2); +// expect(deserializeKeyFromTag(serialized1)).toEqual(key1); +// expect(deserializeKeyFromTag(serialized2)).toEqual(key2); +// }); +// }); +// }); diff --git a/packages/core/tests/rivet/rivet-deploy.ts b/packages/core/tests/rivet/rivet-deploy.ts new file mode 100644 index 000000000..2193f15cb --- /dev/null +++ b/packages/core/tests/rivet/rivet-deploy.ts @@ -0,0 +1,365 @@ +// import fs from "node:fs/promises"; +// import path from "node:path"; +// import os from "node:os"; +// import { spawn, exec } from "node:child_process"; +// import crypto from "node:crypto"; +// import { promisify } from "node:util"; +// import invariant from "invariant"; +// import { RivetClient } from "@rivet-gg/api"; +// import { RivetClientConfig } from "../src/rivet-client"; +// +// const execPromise = promisify(exec); +// const apiEndpoint = process.env.RIVET_ENDPOINT!; +// invariant(apiEndpoint, "missing RIVET_ENDPOINT"); +// const rivetCloudToken = process.env.RIVET_CLOUD_TOKEN!; +// invariant(rivetCloudToken, "missing RIVET_CLOUD_TOKEN"); +// const project = process.env.RIVET_PROJECT!; +// invariant(project, "missing RIVET_PROJECT"); +// const environment = process.env.RIVET_ENVIRONMENT!; +// invariant(environment, "missing RIVET_ENVIRONMENT"); +// +// export const rivetClientConfig: RivetClientConfig = { +// endpoint: apiEndpoint, +// token: rivetCloudToken, +// project, +// environment, +// }; +// +// const rivetClient = new RivetClient({ +// environment: apiEndpoint, +// token: rivetCloudToken, +// }); +// +// /** +// * Helper function to write a file to the filesystem +// */ +// async function writeFile( +// dirPath: string, +// filename: string, +// content: string | object, +// ): Promise { +// const filePath = path.join(dirPath, filename); +// const fileContent = +// typeof content === "string" ? content : JSON.stringify(content, null, 2); +// +// console.log(`Writing ${filename}`); +// await fs.writeFile(filePath, fileContent); +// } +// +// /** +// * Pack a package using pnpm pack and return the path to the packed tarball +// */ +// async function packPackage( +// packageDir: string, +// tmpDir: string, +// packageName: string, +// ): Promise { +// console.log(`Packing package from ${packageDir}...`); +// // Generate a unique filename +// const outputFileName = `${packageName}-${crypto.randomUUID()}.tgz`; +// const outputPath = path.join(tmpDir, outputFileName); +// +// // Run pnpm pack with specific output path +// await execPromise(`pnpm pack --install-if-needed --out ${outputPath}`, { +// cwd: packageDir, +// }); +// console.log(`Generated tarball at ${outputPath}`); +// return outputFileName; +// } +// +// /** +// * Deploy an app to Rivet and return the endpoint +// */ +// export async function deployToRivet(projectPath: string) { +// console.log("=== START deployToRivet ==="); +// console.log(`Deploying registry from path: ${projectPath}`); +// +// // Create a temporary directory for the test +// const uuid = crypto.randomUUID(); +// const tmpDirName = `rivetkit-test-${uuid}`; +// const tmpDir = path.join(os.tmpdir(), tmpDirName); +// console.log(`Creating temp directory: ${tmpDir}`); +// await fs.mkdir(tmpDir, { recursive: true }); +// +// // Get the workspace root and package paths +// const workspaceRoot = path.resolve(__dirname, "../../../.."); +// const rivetPlatformPath = path.resolve(__dirname, "../"); +// const rivetkitCorePath = path.resolve(workspaceRoot, "packages/core"); +// +// // Pack the required packages directly to the temp directory +// console.log("Packing required packages..."); +// const rivetPlatformFilename = await packPackage( +// rivetPlatformPath, +// tmpDir, +// "rivetkit-rivet", +// ); +// const rivetkitFilename = await packPackage( +// rivetkitCorePath, +// tmpDir, +// "rivetkit", +// ); +// +// // Create package.json with file dependencies +// const packageJson = { +// name: "rivetkit-test", +// private: true, +// version: "1.0.0", +// scripts: { +// build: "tsc", +// }, +// dependencies: { +// "@rivetkit/rivet": `file:./${rivetPlatformFilename}`, +// rivetkit: `file:./${rivetkitFilename}`, +// }, +// devDependencies: { +// typescript: "^5.3.0", +// }, +// packageManager: +// "pnpm@10.7.1+sha512.2d92c86b7928dc8284f53494fb4201f983da65f0fb4f0d40baafa5cf628fa31dae3e5968f12466f17df7e97310e30f343a648baea1b9b350685dafafffdf5808", +// }; +// await writeFile(tmpDir, "package.json", packageJson); +// +// // Create rivet.json with workspace dependencies +// const rivetJson = { +// functions: { +// manager: { +// tags: { role: "manager", framework: "rivetkit" }, +// dockerfile: "Dockerfile", +// runtime: { +// environment: { +// RIVET_ENDPOINT: apiEndpoint, +// RIVET_SERVICE_TOKEN: rivetCloudToken, // TODO: This should be a service token, but both work +// RIVET_PROJECT: project, +// RIVET_ENVIRONMENT: environment, +// _LOG_LEVEL: "DEBUG", +// _WORKER_LOG_LEVEL: "DEBUG", +// }, +// }, +// resources: { +// cpu: 250, +// memory: 256, +// }, +// }, +// }, +// actors: { +// worker: { +// tags: { role: "worker", framework: "rivetkit" }, +// script: "src/worker.ts", +// }, +// }, +// }; +// await writeFile(tmpDir, "rivet.json", rivetJson); +// +// // Create Dockerfile +// const dockerfile = ` +// FROM node:22-alpine AS builder +// +// RUN npm i -g corepack && corepack enable +// +// WORKDIR /app +// +// COPY package.json pnpm-lock.yaml ./ +// COPY *.tgz ./ +// +// RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ +// pnpm install --frozen-lockfile +// +// COPY . . +// # HACK: Remove worker.ts bc file is invalid in Node +// RUN rm src/worker.ts && pnpm build +// +// RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ +// pnpm install --prod --frozen-lockfile +// +// FROM node:22-alpine AS runtime +// +// RUN addgroup -g 1001 -S rivet && \ +// adduser -S rivet -u 1001 -G rivet +// +// WORKDIR /app +// +// COPY --from=builder --chown=rivet:rivet /app/dist ./dist +// COPY --from=builder --chown=rivet:rivet /app/node_modules ./node_modules +// COPY --from=builder --chown=rivet:rivet /app/package.json ./ +// +// USER rivet +// +// CMD ["node", "dist/server.js"] +// `; +// await writeFile(tmpDir, "Dockerfile", dockerfile); +// +// // Create .dockerignore +// const dockerignore = ` +// node_modules +// `; +// await writeFile(tmpDir, ".dockerignore", dockerignore); +// +// // Disable PnP +// const yarnPnp = "nodeLinker: node-modules"; +// await writeFile(tmpDir, ".yarnrc.yml", yarnPnp); +// +// // Create tsconfig.json +// const tsconfig = { +// compilerOptions: { +// target: "ESNext", +// module: "NodeNext", +// moduleResolution: "NodeNext", +// esModuleInterop: true, +// strict: true, +// skipLibCheck: true, +// forceConsistentCasingInFileNames: true, +// outDir: "dist", +// sourceMap: true, +// declaration: true, +// }, +// include: ["src/**/*.ts"], +// }; +// await writeFile(tmpDir, "tsconfig.json", tsconfig); +// +// // Install deps +// console.log("Installing dependencies..."); +// try { +// const installOutput = await execPromise("pnpm install", { cwd: tmpDir }); +// console.log("Install output:", installOutput.stdout); +// } catch (error) { +// console.error("Error installing dependencies:", error); +// throw error; +// } +// +// // Copy project to test directory +// console.log(`Copying project from ${projectPath} to ${tmpDir}/src/workers`); +// const projectDestDir = path.join(tmpDir, "src", "workers"); +// await fs.cp(projectPath, projectDestDir, { recursive: true }); +// +// const serverTsContent = `import { startManager } from "@rivetkit/rivet/manager"; +// import { registry } from "./workers/registry"; +// +// // TODO: Find a cleaner way of flagging an registry as test mode (ideally not in the config itself) +// // Force enable test +// registry.config.test.enabled = true; +// +// startManager(registry); +// `; +// await writeFile(tmpDir, "src/server.ts", serverTsContent); +// +// const workerTsContent = `import { createWorkerHandler } from "@rivetkit/worker/drivers/rivet"; +// import { registry } from "./workers/registry"; +// +// // TODO: Find a cleaner way of flagging an registry as test mode (ideally not in the config itself) +// // Force enable test +// registry.config.test.enabled = true; +// +// export default createWorkerHandler(registry);`; +// await writeFile(tmpDir, "src/worker.ts", workerTsContent); +// +// // Build and deploy to Rivet +// console.log("Building and deploying to Rivet..."); +// +// if (!process.env._RIVET_SKIP_DEPLOY) { +// // Deploy using the rivet CLI +// console.log("Spawning rivet deploy command..."); +// const deployProcess = spawn( +// "rivet", +// ["deploy", "--environment", environment, "--non-interactive"], +// { +// cwd: tmpDir, +// env: { +// ...process.env, +// RIVET_ENDPOINT: apiEndpoint, +// RIVET_CLOUD_TOKEN: rivetCloudToken, +// //CI: "1", +// }, +// stdio: "inherit", // Stream output directly to console +// }, +// ); +// +// console.log("Waiting for deploy process to complete..."); +// await new Promise((resolve, reject) => { +// deployProcess.on("exit", (code) => { +// if (code === 0) { +// resolve(undefined); +// } else { +// reject(new Error(`Deploy process exited with code ${code}`)); +// } +// }); +// deployProcess.on("error", (err) => { +// console.error("Deploy process error:", err); +// reject(err); +// }); +// }); +// console.log("Deploy process completed successfully"); +// } +// +// // Get the endpoint URL +// console.log("Getting Rivet endpoint..."); +// +// // // HACK: We have to get the endpoint of the actor directly since we can't route functions with hostnames on localhost yet +// // const { actors } = await rivetClient.actors.list({ +// // tagsJson: JSON.stringify({ +// // type: "function", +// // function: "manager", +// // appName, +// // }), +// // project, +// // environment, +// // }); +// // const managerActor = actors[0]; +// // invariant(managerActor, "missing manager actor"); +// // const endpoint = managerActor.network.ports.http?.url; +// // invariant(endpoint, "missing manager actor endpoint"); +// +// // TODO: This doesn't work in local dev since we can't route functions on localhost yet +// // Get the endpoint using the CLI endpoint command +// console.log("Spawning rivet function endpoint command..."); +// const endpointProcess = spawn( +// "rivet", +// ["function", "endpoint", "--environment", environment, "manager"], +// { +// cwd: tmpDir, +// env: { +// ...process.env, +// CI: "1", +// }, +// stdio: ["inherit", "pipe", "inherit"], // Capture stdout +// }, +// ); +// +// // Capture the endpoint +// let endpointOutput = ""; +// endpointProcess.stdout.on("data", (data) => { +// const output = data.toString(); +// console.log(`Endpoint output: ${output}`); +// endpointOutput += output; +// }); +// +// // Wait for endpoint command to complete +// console.log("Waiting for endpoint process to complete..."); +// await new Promise((resolve, reject) => { +// endpointProcess.on("exit", (code) => { +// console.log(`Endpoint process exited with code: ${code}`); +// if (code === 0) { +// resolve(undefined); +// } else { +// reject(new Error(`Endpoint command failed with code ${code}`)); +// } +// }); +// endpointProcess.on("error", (err) => { +// console.error("Endpoint process error:", err); +// reject(err); +// }); +// }); +// +// invariant(endpointOutput, "endpoint command returned empty output"); +// console.log(`Raw endpoint output: ${endpointOutput}`); +// +// // Look for something that looks like a URL in the string +// const lines = endpointOutput.trim().split("\n"); +// const endpoint = lines[lines.length - 1]; +// invariant(endpoint, "endpoint not found"); +// +// console.log("Manager endpoint", endpoint); +// +// console.log("=== END deployToRivet ==="); +// +// return endpoint; +// } diff --git a/packages/drivers/memory/README.md b/packages/drivers/memory/README.md deleted file mode 100644 index 40e452832..000000000 --- a/packages/drivers/memory/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# RivetKit Memory Driver - -_Lightweight Libraries for Backends_ - -[Learn More →](https://github.com/rivet-gg/rivetkit) - -[Discord](https://rivet.gg/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-gg/rivetkit/issues) - -## License - -Apache 2.0 \ No newline at end of file diff --git a/packages/drivers/memory/package.json b/packages/drivers/memory/package.json deleted file mode 100644 index ec0f70cc8..000000000 --- a/packages/drivers/memory/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "@rivetkit/memory", - "version": "0.9.0-rc.1", - "keywords": ["rivetkit", "driver", "memory", "storage", "development", "testing"], - "files": [ - "src", - "dist", - "package.json" - ], - "type": "module", - "exports": { - "import": { - "types": "./dist/mod.d.ts", - "default": "./dist/mod.js" - }, - "require": { - "types": "./dist/mod.d.cts", - "default": "./dist/mod.cjs" - } - }, - "sideEffects": false, - "scripts": { - "build": "tsup src/mod.ts", - "check-types": "tsc --noEmit", - "test": "vitest run" - }, - "peerDependencies": { - "@rivetkit/core": "*" - }, - "devDependencies": { - "@rivetkit/core": "workspace:*", - "tsup": "^8.4.0", - "typescript": "^5.5.2" - }, - "dependencies": { - "@types/node": "^22.13.1", - "hono": "^4.7.0", - "vitest": "^3.1.1" - }, - "stableVersion": "0.8.0" -} diff --git a/packages/drivers/memory/tests/driver-tests.test.ts b/packages/drivers/memory/tests/driver-tests.test.ts deleted file mode 100644 index fb1318507..000000000 --- a/packages/drivers/memory/tests/driver-tests.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MemoryGlobalState } from "@/global-state"; -import { createMemoryDriver } from "@/mod"; -import { runDriverTests, createTestRuntime } from "@rivetkit/core/driver-test-suite"; - -runDriverTests({ - async start(appPath: string) { - return await createTestRuntime(appPath, async () => { - return { - driver: createMemoryDriver(), - }; - }); - }, -}); diff --git a/packages/drivers/memory/tsconfig.json b/packages/drivers/memory/tsconfig.json deleted file mode 100644 index a15ae12a6..000000000 --- a/packages/drivers/memory/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["src/**/*", "tests/**/*", "fixtures/apps/**/*"] -} diff --git a/packages/drivers/memory/tsup.config.ts b/packages/drivers/memory/tsup.config.ts deleted file mode 100644 index 677cffb7b..000000000 --- a/packages/drivers/memory/tsup.config.ts +++ /dev/null @@ -1,4 +0,0 @@ -import defaultConfig from "../../../tsup.base.ts"; -import { defineConfig } from "tsup"; - -export default defineConfig(defaultConfig); diff --git a/packages/drivers/memory/turbo.json b/packages/drivers/memory/turbo.json deleted file mode 100644 index 95960709b..000000000 --- a/packages/drivers/memory/turbo.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "extends": ["//"] -} diff --git a/packages/platforms/cloudflare-workers/src/config.ts b/packages/platforms/cloudflare-workers/src/config.ts index b20a3abe9..c451ea7de 100644 --- a/packages/platforms/cloudflare-workers/src/config.ts +++ b/packages/platforms/cloudflare-workers/src/config.ts @@ -1,6 +1,6 @@ -import { RunConfigSchema } from "@rivetkit/core/driver-helpers"; -import { z } from "zod"; - -export const ConfigSchema = RunConfigSchema.omit({ driver: true, getUpgradeWebSocket: true }).default({}); -export type InputConfig = z.input; -export type Config = z.infer; +// import { RunConfigSchema } from "@rivetkit/core/driver-helpers"; +// import { z } from "zod"; +// +// export const ConfigSchema = RunConfigSchema.omit({ driver: true, getUpgradeWebSocket: true }).default({}); +// export type InputConfig = z.input; +// export type Config = z.infer; diff --git a/packages/platforms/cloudflare-workers/src/handler.ts b/packages/platforms/cloudflare-workers/src/handler.ts index 89ff6f9c0..24f13d725 100644 --- a/packages/platforms/cloudflare-workers/src/handler.ts +++ b/packages/platforms/cloudflare-workers/src/handler.ts @@ -1,230 +1,230 @@ -import { - type DurableObjectConstructor, - type WorkerHandlerInterface, - createWorkerDurableObject, -} from "./worker-handler-do"; -import { ConfigSchema, type InputConfig } from "./config"; -import { assertUnreachable } from "@rivetkit/core/utils"; -import { - HEADER_AUTH_DATA, - HEADER_CONN_PARAMS, - HEADER_ENCODING, - HEADER_EXPOSE_INTERNAL_ERROR, -} from "@rivetkit/core/driver-helpers"; -import type { Hono } from "hono"; -import { PartitionTopologyManager } from "@rivetkit/core/topologies/partition"; -import { logger } from "./log"; -import { CloudflareWorkersManagerDriver } from "./manager-driver"; -import { Encoding, Registry, RunConfig } from "@rivetkit/core"; -import { upgradeWebSocket } from "./websocket"; -import invariant from "invariant"; -import { AsyncLocalStorage } from "node:async_hooks"; -import { InternalError } from "@rivetkit/core/errors"; - -/** Cloudflare Workers env */ -export interface Bindings { - WORKER_KV: KVNamespace; - WORKER_DO: DurableObjectNamespace; -} - -/** - * Stores the env for the current request. Required since some contexts like the inline client driver does not have access to the Hono context. - * - * Use getCloudflareAmbientEnv unless using CF_AMBIENT_ENV.run. - */ -export const CF_AMBIENT_ENV = new AsyncLocalStorage(); - -const STANDARD_WEBSOCKET_HEADERS = [ - "connection", - "upgrade", - "sec-websocket-key", - "sec-websocket-version", - "sec-websocket-protocol", - "sec-websocket-extensions", -]; - -export function getCloudflareAmbientEnv(): Bindings { - const env = CF_AMBIENT_ENV.getStore(); - invariant(env, "missing CF_AMBIENT_ENV"); - return env; -} - -export function createHandler( - registry: Registry, - inputConfig?: InputConfig, -): { - handler: ExportedHandler; - WorkerHandler: DurableObjectConstructor; -} { - // Create router - const { router, WorkerHandler } = createRouter(registry, inputConfig); - - // Create Cloudflare handler - const handler = { - fetch: (request, env, ctx) => { - return CF_AMBIENT_ENV.run(env, () => router.fetch(request, env, ctx)); - }, - } satisfies ExportedHandler; - - return { handler, WorkerHandler }; -} - -export function createRouter( - registry: Registry, - inputConfig?: InputConfig, -): { - router: Hono<{ Bindings: Bindings }>; - WorkerHandler: DurableObjectConstructor; -} { - const config = ConfigSchema.parse(inputConfig); - const runConfig = { - driver: { - topology: "partition", - manager: new CloudflareWorkersManagerDriver(), - // HACK: We can't build the worker driver until we're inside the Druable Object - worker: undefined as any, - }, - getUpgradeWebSocket: () => upgradeWebSocket, - ...config, - } satisfies RunConfig; - - // Create Durable Object - const WorkerHandler = createWorkerDurableObject(registry, runConfig); - - const managerTopology = new PartitionTopologyManager( - registry.config, - runConfig, - { - sendRequest: async (workerId, workerRequest): Promise => { - const env = getCloudflareAmbientEnv(); - - logger().debug("sending request to durable object", { - workerId, - method: workerRequest.method, - url: workerRequest.url, - }); - - const id = env.WORKER_DO.idFromString(workerId); - const stub = env.WORKER_DO.get(id); - - return await stub.fetch(workerRequest); - }, - - openWebSocket: async ( - workerId, - encodingKind: Encoding, - params: unknown, - ): Promise => { - const env = getCloudflareAmbientEnv(); - - logger().debug("opening websocket to durable object", { workerId }); - - // Make a fetch request to the Durable Object with WebSocket upgrade - const id = env.WORKER_DO.idFromString(workerId); - const stub = env.WORKER_DO.get(id); - - const headers: Record = { - Upgrade: "websocket", - Connection: "Upgrade", - [HEADER_EXPOSE_INTERNAL_ERROR]: "true", - [HEADER_ENCODING]: encodingKind, - }; - if (params) { - headers[HEADER_CONN_PARAMS] = JSON.stringify(params); - } - // HACK: See packages/platforms/cloudflare-workers/src/websocket.ts - headers["sec-websocket-protocol"] = "rivetkit"; - - const response = await stub.fetch("http://worker/connect/websocket", { - headers, - }); - const webSocket = response.webSocket; - - if (!webSocket) { - throw new InternalError( - "missing websocket connection in response from DO", - ); - } - - logger().debug("durable object websocket connection open", { - workerId, - }); - - webSocket.accept(); - - // TODO: Is this still needed? - // HACK: Cloudflare does not call onopen automatically, so we need - // to call this on the next tick - setTimeout(() => { - (webSocket as any).onopen?.(new Event("open")); - }, 0); - - return webSocket as unknown as WebSocket; - }, - - proxyRequest: async (c, workerRequest, workerId): Promise => { - logger().debug("forwarding request to durable object", { - workerId, - method: workerRequest.method, - url: workerRequest.url, - }); - - const id = c.env.WORKER_DO.idFromString(workerId); - const stub = c.env.WORKER_DO.get(id); - - return await stub.fetch(workerRequest); - }, - proxyWebSocket: async (c, path, workerId, encoding, params, authData) => { - logger().debug("forwarding websocket to durable object", { - workerId, - path, - }); - - // Validate upgrade - const upgradeHeader = c.req.header("Upgrade"); - if (!upgradeHeader || upgradeHeader !== "websocket") { - return new Response("Expected Upgrade: websocket", { - status: 426, - }); - } - - // TODO: strip headers - const newUrl = new URL(`http://worker${path}`); - const workerRequest = new Request(newUrl, c.req.raw); - - // Always build fresh request to prevent forwarding unwanted headers - // HACK: Since we can't build a new request, we need to remove - // non-standard headers manually - const headerKeys: string[] = []; - workerRequest.headers.forEach((v, k) => headerKeys.push(k)); - for (const k of headerKeys) { - if (!STANDARD_WEBSOCKET_HEADERS.includes(k)) { - workerRequest.headers.delete(k); - } - } - - // Add RivetKit headers - workerRequest.headers.set(HEADER_EXPOSE_INTERNAL_ERROR, "true"); - workerRequest.headers.set(HEADER_ENCODING, encoding); - if (params) { - workerRequest.headers.set(HEADER_CONN_PARAMS, JSON.stringify(params)); - } - if (authData) { - workerRequest.headers.set(HEADER_AUTH_DATA, JSON.stringify(authData)); - } - - const id = c.env.WORKER_DO.idFromString(workerId); - const stub = c.env.WORKER_DO.get(id); - - return await stub.fetch(workerRequest); - }, - }, - ); - - // Force the router to have access to the Cloudflare bindings - const router = managerTopology.router as unknown as Hono<{ - Bindings: Bindings; - }>; - - return { router, WorkerHandler }; -} +// import { +// type DurableObjectConstructor, +// type WorkerHandlerInterface, +// createWorkerDurableObject, +// } from "./worker-handler-do"; +// import { ConfigSchema, type InputConfig } from "./config"; +// import { assertUnreachable } from "@rivetkit/core/utils"; +// import { +// HEADER_AUTH_DATA, +// HEADER_CONN_PARAMS, +// HEADER_ENCODING, +// HEADER_EXPOSE_INTERNAL_ERROR, +// } from "@rivetkit/core/driver-helpers"; +// import type { Hono } from "hono"; +// import { PartitionTopologyManager } from "@rivetkit/core/topologies/partition"; +// import { logger } from "./log"; +// import { CloudflareWorkersManagerDriver } from "./manager-driver"; +// import { Encoding, Registry, RunConfig } from "@rivetkit/core"; +// import { upgradeWebSocket } from "./websocket"; +// import invariant from "invariant"; +// import { AsyncLocalStorage } from "node:async_hooks"; +// import { InternalError } from "@rivetkit/core/errors"; +// +// /** Cloudflare Workers env */ +// export interface Bindings { +// WORKER_KV: KVNamespace; +// WORKER_DO: DurableObjectNamespace; +// } +// +// /** +// * Stores the env for the current request. Required since some contexts like the inline client driver does not have access to the Hono context. +// * +// * Use getCloudflareAmbientEnv unless using CF_AMBIENT_ENV.run. +// */ +// export const CF_AMBIENT_ENV = new AsyncLocalStorage(); +// +// const STANDARD_WEBSOCKET_HEADERS = [ +// "connection", +// "upgrade", +// "sec-websocket-key", +// "sec-websocket-version", +// "sec-websocket-protocol", +// "sec-websocket-extensions", +// ]; +// +// export function getCloudflareAmbientEnv(): Bindings { +// const env = CF_AMBIENT_ENV.getStore(); +// invariant(env, "missing CF_AMBIENT_ENV"); +// return env; +// } +// +// export function createHandler( +// registry: Registry, +// inputConfig?: InputConfig, +// ): { +// handler: ExportedHandler; +// WorkerHandler: DurableObjectConstructor; +// } { +// // Create router +// const { router, WorkerHandler } = createRouter(registry, inputConfig); +// +// // Create Cloudflare handler +// const handler = { +// fetch: (request, env, ctx) => { +// return CF_AMBIENT_ENV.run(env, () => router.fetch(request, env, ctx)); +// }, +// } satisfies ExportedHandler; +// +// return { handler, WorkerHandler }; +// } +// +// export function createRouter( +// registry: Registry, +// inputConfig?: InputConfig, +// ): { +// router: Hono<{ Bindings: Bindings }>; +// WorkerHandler: DurableObjectConstructor; +// } { +// const config = ConfigSchema.parse(inputConfig); +// const runConfig = { +// driver: { +// topology: "partition", +// manager: new CloudflareWorkersManagerDriver(), +// // HACK: We can't build the worker driver until we're inside the Druable Object +// worker: undefined as any, +// }, +// getUpgradeWebSocket: () => upgradeWebSocket, +// ...config, +// } satisfies RunConfig; +// +// // Create Durable Object +// const WorkerHandler = createWorkerDurableObject(registry, runConfig); +// +// const managerTopology = new PartitionTopologyManager( +// registry.config, +// runConfig, +// { +// sendRequest: async (workerId, workerRequest): Promise => { +// const env = getCloudflareAmbientEnv(); +// +// logger().debug("sending request to durable object", { +// workerId, +// method: workerRequest.method, +// url: workerRequest.url, +// }); +// +// const id = env.WORKER_DO.idFromString(workerId); +// const stub = env.WORKER_DO.get(id); +// +// return await stub.fetch(workerRequest); +// }, +// +// openWebSocket: async ( +// workerId, +// encodingKind: Encoding, +// params: unknown, +// ): Promise => { +// const env = getCloudflareAmbientEnv(); +// +// logger().debug("opening websocket to durable object", { workerId }); +// +// // Make a fetch request to the Durable Object with WebSocket upgrade +// const id = env.WORKER_DO.idFromString(workerId); +// const stub = env.WORKER_DO.get(id); +// +// const headers: Record = { +// Upgrade: "websocket", +// Connection: "Upgrade", +// [HEADER_EXPOSE_INTERNAL_ERROR]: "true", +// [HEADER_ENCODING]: encodingKind, +// }; +// if (params) { +// headers[HEADER_CONN_PARAMS] = JSON.stringify(params); +// } +// // HACK: See packages/platforms/cloudflare-workers/src/websocket.ts +// headers["sec-websocket-protocol"] = "rivetkit"; +// +// const response = await stub.fetch("http://worker/connect/websocket", { +// headers, +// }); +// const webSocket = response.webSocket; +// +// if (!webSocket) { +// throw new InternalError( +// "missing websocket connection in response from DO", +// ); +// } +// +// logger().debug("durable object websocket connection open", { +// workerId, +// }); +// +// webSocket.accept(); +// +// // TODO: Is this still needed? +// // HACK: Cloudflare does not call onopen automatically, so we need +// // to call this on the next tick +// setTimeout(() => { +// (webSocket as any).onopen?.(new Event("open")); +// }, 0); +// +// return webSocket as unknown as WebSocket; +// }, +// +// proxyRequest: async (c, workerRequest, workerId): Promise => { +// logger().debug("forwarding request to durable object", { +// workerId, +// method: workerRequest.method, +// url: workerRequest.url, +// }); +// +// const id = c.env.WORKER_DO.idFromString(workerId); +// const stub = c.env.WORKER_DO.get(id); +// +// return await stub.fetch(workerRequest); +// }, +// proxyWebSocket: async (c, path, workerId, encoding, params, authData) => { +// logger().debug("forwarding websocket to durable object", { +// workerId, +// path, +// }); +// +// // Validate upgrade +// const upgradeHeader = c.req.header("Upgrade"); +// if (!upgradeHeader || upgradeHeader !== "websocket") { +// return new Response("Expected Upgrade: websocket", { +// status: 426, +// }); +// } +// +// // TODO: strip headers +// const newUrl = new URL(`http://worker${path}`); +// const workerRequest = new Request(newUrl, c.req.raw); +// +// // Always build fresh request to prevent forwarding unwanted headers +// // HACK: Since we can't build a new request, we need to remove +// // non-standard headers manually +// const headerKeys: string[] = []; +// workerRequest.headers.forEach((v, k) => headerKeys.push(k)); +// for (const k of headerKeys) { +// if (!STANDARD_WEBSOCKET_HEADERS.includes(k)) { +// workerRequest.headers.delete(k); +// } +// } +// +// // Add RivetKit headers +// workerRequest.headers.set(HEADER_EXPOSE_INTERNAL_ERROR, "true"); +// workerRequest.headers.set(HEADER_ENCODING, encoding); +// if (params) { +// workerRequest.headers.set(HEADER_CONN_PARAMS, JSON.stringify(params)); +// } +// if (authData) { +// workerRequest.headers.set(HEADER_AUTH_DATA, JSON.stringify(authData)); +// } +// +// const id = c.env.WORKER_DO.idFromString(workerId); +// const stub = c.env.WORKER_DO.get(id); +// +// return await stub.fetch(workerRequest); +// }, +// }, +// ); +// +// // Force the router to have access to the Cloudflare bindings +// const router = managerTopology.router as unknown as Hono<{ +// Bindings: Bindings; +// }>; +// +// return { router, WorkerHandler }; +// } diff --git a/packages/platforms/cloudflare-workers/src/log.ts b/packages/platforms/cloudflare-workers/src/log.ts index 0f64c76ec..0a2419302 100644 --- a/packages/platforms/cloudflare-workers/src/log.ts +++ b/packages/platforms/cloudflare-workers/src/log.ts @@ -1,7 +1,7 @@ -import { getLogger } from "@rivetkit/core/log"; - -export const LOGGER_NAME = "driver-cloudflare-workers"; - -export function logger() { - return getLogger(LOGGER_NAME); -} +// import { getLogger } from "@rivetkit/core/log"; +// +// export const LOGGER_NAME = "driver-cloudflare-workers"; +// +// export function logger() { +// return getLogger(LOGGER_NAME); +// } diff --git a/packages/platforms/cloudflare-workers/src/manager-driver.ts b/packages/platforms/cloudflare-workers/src/manager-driver.ts index 1a575ab92..d48ac1c33 100644 --- a/packages/platforms/cloudflare-workers/src/manager-driver.ts +++ b/packages/platforms/cloudflare-workers/src/manager-driver.ts @@ -1,175 +1,175 @@ -import type { - ManagerDriver, - GetForIdInput, - GetWithKeyInput, - WorkerOutput, - CreateInput, - GetOrCreateWithKeyInput, -} from "@rivetkit/core/driver-helpers"; -import { WorkerAlreadyExists } from "@rivetkit/core/errors"; -import { Bindings } from "./mod"; -import { logger } from "./log"; -import { serializeNameAndKey, serializeKey } from "./util"; -import { getCloudflareAmbientEnv } from "./handler"; - -// Worker metadata structure -interface WorkerData { - name: string; - key: string[]; -} - -// Key constants similar to Redis implementation -const KEYS = { - WORKER: { - // Combined key for worker metadata (name and key) - metadata: (workerId: string) => `worker:${workerId}:metadata`, - - // Key index function for worker lookup - keyIndex: (name: string, key: string[] = []) => { - // Use serializeKey for consistent handling of all keys - return `worker_key:${serializeKey(key)}`; - }, - }, -}; - -export class CloudflareWorkersManagerDriver implements ManagerDriver { - async getForId({ - c, - workerId, - }: GetForIdInput<{ Bindings: Bindings }>): Promise { - const env = getCloudflareAmbientEnv(); - - // Get worker metadata from KV (combined name and key) - const workerData = (await env.WORKER_KV.get(KEYS.WORKER.metadata(workerId), { - type: "json", - })) as WorkerData | null; - - // If the worker doesn't exist, return undefined - if (!workerData) { - return undefined; - } - - return { - workerId, - name: workerData.name, - key: workerData.key, - }; - } - - async getWithKey({ - c, - name, - key, - }: GetWithKeyInput<{ Bindings: Bindings }>): Promise< - WorkerOutput | undefined - > { - const env = getCloudflareAmbientEnv(); - - logger().debug("getWithKey: searching for worker", { name, key }); - - // Generate deterministic ID from the name and key - // This is aligned with how createWorker generates IDs - const nameKeyString = serializeNameAndKey(name, key); - const workerId = env.WORKER_DO.idFromName(nameKeyString).toString(); - - // Check if the worker metadata exists - const workerData = await env.WORKER_KV.get(KEYS.WORKER.metadata(workerId), { - type: "json", - }); - - if (!workerData) { - logger().debug("getWithKey: no worker found with matching name and key", { - name, - key, - workerId, - }); - return undefined; - } - - logger().debug("getWithKey: found worker with matching name and key", { - workerId, - name, - key, - }); - return this.#buildWorkerOutput(c, workerId); - } - - async getOrCreateWithKey( - input: GetOrCreateWithKeyInput, - ): Promise { - // TODO: Prevent race condition here - const getOutput = await this.getWithKey(input); - if (getOutput) { - return getOutput; - } else { - return await this.createWorker(input); - } - } - - async createWorker({ - c, - name, - key, - input, - }: CreateInput<{ Bindings: Bindings }>): Promise { - const env = getCloudflareAmbientEnv(); - - // Check if worker with the same name and key already exists - const existingWorker = await this.getWithKey({ c, name, key }); - if (existingWorker) { - throw new WorkerAlreadyExists(name, key); - } - - // Create a deterministic ID from the worker name and key - // This ensures that workers with the same name and key will have the same ID - const nameKeyString = serializeNameAndKey(name, key); - const doId = env.WORKER_DO.idFromName(nameKeyString); - const workerId = doId.toString(); - - // Init worker - const worker = env.WORKER_DO.get(doId); - await worker.initialize({ - name, - key, - input, - }); - - // Store combined worker metadata (name and key) - const workerData: WorkerData = { name, key }; - await env.WORKER_KV.put( - KEYS.WORKER.metadata(workerId), - JSON.stringify(workerData), - ); - - // Add to key index for lookups by name and key - await env.WORKER_KV.put(KEYS.WORKER.keyIndex(name, key), workerId); - - return { - workerId, - name, - key, - }; - } - - // Helper method to build worker output from an ID - async #buildWorkerOutput( - c: any, - workerId: string, - ): Promise { - const env = getCloudflareAmbientEnv(); - - const workerData = (await env.WORKER_KV.get(KEYS.WORKER.metadata(workerId), { - type: "json", - })) as WorkerData | null; - - if (!workerData) { - return undefined; - } - - return { - workerId, - name: workerData.name, - key: workerData.key, - }; - } -} +// import type { +// ManagerDriver, +// GetForIdInput, +// GetWithKeyInput, +// WorkerOutput, +// CreateInput, +// GetOrCreateWithKeyInput, +// } from "@rivetkit/core/driver-helpers"; +// import { WorkerAlreadyExists } from "@rivetkit/core/errors"; +// import { Bindings } from "./mod"; +// import { logger } from "./log"; +// import { serializeNameAndKey, serializeKey } from "./util"; +// import { getCloudflareAmbientEnv } from "./handler"; +// +// // Worker metadata structure +// interface WorkerData { +// name: string; +// key: string[]; +// } +// +// // Key constants similar to Redis implementation +// const KEYS = { +// WORKER: { +// // Combined key for worker metadata (name and key) +// metadata: (workerId: string) => `worker:${workerId}:metadata`, +// +// // Key index function for worker lookup +// keyIndex: (name: string, key: string[] = []) => { +// // Use serializeKey for consistent handling of all keys +// return `worker_key:${serializeKey(key)}`; +// }, +// }, +// }; +// +// export class CloudflareWorkersManagerDriver implements ManagerDriver { +// async getForId({ +// c, +// workerId, +// }: GetForIdInput<{ Bindings: Bindings }>): Promise { +// const env = getCloudflareAmbientEnv(); +// +// // Get worker metadata from KV (combined name and key) +// const workerData = (await env.WORKER_KV.get(KEYS.WORKER.metadata(workerId), { +// type: "json", +// })) as WorkerData | null; +// +// // If the worker doesn't exist, return undefined +// if (!workerData) { +// return undefined; +// } +// +// return { +// workerId, +// name: workerData.name, +// key: workerData.key, +// }; +// } +// +// async getWithKey({ +// c, +// name, +// key, +// }: GetWithKeyInput<{ Bindings: Bindings }>): Promise< +// WorkerOutput | undefined +// > { +// const env = getCloudflareAmbientEnv(); +// +// logger().debug("getWithKey: searching for worker", { name, key }); +// +// // Generate deterministic ID from the name and key +// // This is aligned with how createWorker generates IDs +// const nameKeyString = serializeNameAndKey(name, key); +// const workerId = env.WORKER_DO.idFromName(nameKeyString).toString(); +// +// // Check if the worker metadata exists +// const workerData = await env.WORKER_KV.get(KEYS.WORKER.metadata(workerId), { +// type: "json", +// }); +// +// if (!workerData) { +// logger().debug("getWithKey: no worker found with matching name and key", { +// name, +// key, +// workerId, +// }); +// return undefined; +// } +// +// logger().debug("getWithKey: found worker with matching name and key", { +// workerId, +// name, +// key, +// }); +// return this.#buildWorkerOutput(c, workerId); +// } +// +// async getOrCreateWithKey( +// input: GetOrCreateWithKeyInput, +// ): Promise { +// // TODO: Prevent race condition here +// const getOutput = await this.getWithKey(input); +// if (getOutput) { +// return getOutput; +// } else { +// return await this.createWorker(input); +// } +// } +// +// async createWorker({ +// c, +// name, +// key, +// input, +// }: CreateInput<{ Bindings: Bindings }>): Promise { +// const env = getCloudflareAmbientEnv(); +// +// // Check if worker with the same name and key already exists +// const existingWorker = await this.getWithKey({ c, name, key }); +// if (existingWorker) { +// throw new WorkerAlreadyExists(name, key); +// } +// +// // Create a deterministic ID from the worker name and key +// // This ensures that workers with the same name and key will have the same ID +// const nameKeyString = serializeNameAndKey(name, key); +// const doId = env.WORKER_DO.idFromName(nameKeyString); +// const workerId = doId.toString(); +// +// // Init worker +// const worker = env.WORKER_DO.get(doId); +// await worker.initialize({ +// name, +// key, +// input, +// }); +// +// // Store combined worker metadata (name and key) +// const workerData: WorkerData = { name, key }; +// await env.WORKER_KV.put( +// KEYS.WORKER.metadata(workerId), +// JSON.stringify(workerData), +// ); +// +// // Add to key index for lookups by name and key +// await env.WORKER_KV.put(KEYS.WORKER.keyIndex(name, key), workerId); +// +// return { +// workerId, +// name, +// key, +// }; +// } +// +// // Helper method to build worker output from an ID +// async #buildWorkerOutput( +// c: any, +// workerId: string, +// ): Promise { +// const env = getCloudflareAmbientEnv(); +// +// const workerData = (await env.WORKER_KV.get(KEYS.WORKER.metadata(workerId), { +// type: "json", +// })) as WorkerData | null; +// +// if (!workerData) { +// return undefined; +// } +// +// return { +// workerId, +// name: workerData.name, +// key: workerData.key, +// }; +// } +// } diff --git a/packages/platforms/cloudflare-workers/src/mod.ts b/packages/platforms/cloudflare-workers/src/mod.ts index 0d9d97cae..a8365bea4 100644 --- a/packages/platforms/cloudflare-workers/src/mod.ts +++ b/packages/platforms/cloudflare-workers/src/mod.ts @@ -1,2 +1,2 @@ -export { type Bindings, createHandler, createRouter } from "./handler"; -export type { InputConfig as Config } from "./config"; +// export { type Bindings, createHandler, createRouter } from "./handler"; +// export type { InputConfig as Config } from "./config"; diff --git a/packages/platforms/cloudflare-workers/src/util.ts b/packages/platforms/cloudflare-workers/src/util.ts index b6385dc6c..f26fa4abc 100644 --- a/packages/platforms/cloudflare-workers/src/util.ts +++ b/packages/platforms/cloudflare-workers/src/util.ts @@ -1,105 +1,105 @@ -// Constants for key handling -export const EMPTY_KEY = "(none)"; -export const KEY_SEPARATOR = ","; - -/** - * Serializes an array of key strings into a single string for use with idFromName - * - * @param name The worker name - * @param key Array of key strings to serialize - * @returns A single string containing the serialized name and key - */ -export function serializeNameAndKey(name: string, key: string[]): string { - // Escape colons in the name - const escapedName = name.replace(/:/g, "\\:"); - - // For empty keys, just return the name and a marker - if (key.length === 0) { - return `${escapedName}:${EMPTY_KEY}`; - } - - // Serialize the key array - const serializedKey = serializeKey(key); - - // Combine name and serialized key - return `${escapedName}:${serializedKey}`; -} - -/** - * Serializes an array of key strings into a single string - * - * @param key Array of key strings to serialize - * @returns A single string containing the serialized key - */ -export function serializeKey(key: string[]): string { - // Use a special marker for empty key arrays - if (key.length === 0) { - return EMPTY_KEY; - } - - // Escape each key part to handle the separator and the empty key marker - const escapedParts = key.map(part => { - // First check if it matches our empty key marker - if (part === EMPTY_KEY) { - return `\\${EMPTY_KEY}`; - } - - // Escape backslashes first, then commas - let escaped = part.replace(/\\/g, "\\\\"); - escaped = escaped.replace(/,/g, "\\,"); - return escaped; - }); - - return escapedParts.join(KEY_SEPARATOR); -} - -/** - * Deserializes a key string back into an array of key strings - * - * @param keyString The serialized key string - * @returns Array of key strings - */ -export function deserializeKey(keyString: string): string[] { - // Handle empty values - if (!keyString) { - return []; - } - - // Check for special empty key marker - if (keyString === EMPTY_KEY) { - return []; - } - - // Split by unescaped commas and unescape the escaped characters - const parts: string[] = []; - let currentPart = ''; - let escaping = false; - - for (let i = 0; i < keyString.length; i++) { - const char = keyString[i]; - - if (escaping) { - // This is an escaped character, add it directly - currentPart += char; - escaping = false; - } else if (char === '\\') { - // Start of an escape sequence - escaping = true; - } else if (char === KEY_SEPARATOR) { - // This is a separator - parts.push(currentPart); - currentPart = ''; - } else { - // Regular character - currentPart += char; - } - } - - // Add the last part if it exists - if (currentPart || parts.length > 0) { - parts.push(currentPart); - } - - return parts; -} - +// // Constants for key handling +// export const EMPTY_KEY = "(none)"; +// export const KEY_SEPARATOR = ","; +// +// /** +// * Serializes an array of key strings into a single string for use with idFromName +// * +// * @param name The worker name +// * @param key Array of key strings to serialize +// * @returns A single string containing the serialized name and key +// */ +// export function serializeNameAndKey(name: string, key: string[]): string { +// // Escape colons in the name +// const escapedName = name.replace(/:/g, "\\:"); +// +// // For empty keys, just return the name and a marker +// if (key.length === 0) { +// return `${escapedName}:${EMPTY_KEY}`; +// } +// +// // Serialize the key array +// const serializedKey = serializeKey(key); +// +// // Combine name and serialized key +// return `${escapedName}:${serializedKey}`; +// } +// +// /** +// * Serializes an array of key strings into a single string +// * +// * @param key Array of key strings to serialize +// * @returns A single string containing the serialized key +// */ +// export function serializeKey(key: string[]): string { +// // Use a special marker for empty key arrays +// if (key.length === 0) { +// return EMPTY_KEY; +// } +// +// // Escape each key part to handle the separator and the empty key marker +// const escapedParts = key.map(part => { +// // First check if it matches our empty key marker +// if (part === EMPTY_KEY) { +// return `\\${EMPTY_KEY}`; +// } +// +// // Escape backslashes first, then commas +// let escaped = part.replace(/\\/g, "\\\\"); +// escaped = escaped.replace(/,/g, "\\,"); +// return escaped; +// }); +// +// return escapedParts.join(KEY_SEPARATOR); +// } +// +// /** +// * Deserializes a key string back into an array of key strings +// * +// * @param keyString The serialized key string +// * @returns Array of key strings +// */ +// export function deserializeKey(keyString: string): string[] { +// // Handle empty values +// if (!keyString) { +// return []; +// } +// +// // Check for special empty key marker +// if (keyString === EMPTY_KEY) { +// return []; +// } +// +// // Split by unescaped commas and unescape the escaped characters +// const parts: string[] = []; +// let currentPart = ''; +// let escaping = false; +// +// for (let i = 0; i < keyString.length; i++) { +// const char = keyString[i]; +// +// if (escaping) { +// // This is an escaped character, add it directly +// currentPart += char; +// escaping = false; +// } else if (char === '\\') { +// // Start of an escape sequence +// escaping = true; +// } else if (char === KEY_SEPARATOR) { +// // This is a separator +// parts.push(currentPart); +// currentPart = ''; +// } else { +// // Regular character +// currentPart += char; +// } +// } +// +// // Add the last part if it exists +// if (currentPart || parts.length > 0) { +// parts.push(currentPart); +// } +// +// return parts; +// } +// diff --git a/packages/platforms/cloudflare-workers/src/websocket.ts b/packages/platforms/cloudflare-workers/src/websocket.ts index 37229452e..06e43e018 100644 --- a/packages/platforms/cloudflare-workers/src/websocket.ts +++ b/packages/platforms/cloudflare-workers/src/websocket.ts @@ -1,70 +1,70 @@ -// Modified from https://github.com/honojs/hono/blob/40ea0eee58e39b31053a0246c595434f1094ad31/src/adapter/cloudflare-workers/websocket.ts#L17 +// // Modified from https://github.com/honojs/hono/blob/40ea0eee58e39b31053a0246c595434f1094ad31/src/adapter/cloudflare-workers/websocket.ts#L17 +// // +// // This version calls the open event by default // -// This version calls the open event by default - -import { WSContext, defineWebSocketHelper } from "hono/ws"; -import type { UpgradeWebSocket, WSEvents, WSReadyState } from "hono/ws"; - -// Based on https://github.com/honojs/hono/issues/1153#issuecomment-1767321332 -export const upgradeWebSocket: UpgradeWebSocket< - WebSocket, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - any, - WSEvents -> = defineWebSocketHelper(async (c, events) => { - const upgradeHeader = c.req.header("Upgrade"); - if (upgradeHeader !== "websocket") { - return; - } - - const webSocketPair = new WebSocketPair(); - const client: WebSocket = webSocketPair[0]; - const server: WebSocket = webSocketPair[1]; - - const wsContext = new WSContext({ - close: (code, reason) => server.close(code, reason), - get protocol() { - return server.protocol; - }, - raw: server, - get readyState() { - return server.readyState as WSReadyState; - }, - url: server.url ? new URL(server.url) : null, - send: (source) => server.send(source), - }); - - if (events.onClose) { - server.addEventListener("close", (evt: CloseEvent) => - events.onClose?.(evt, wsContext), - ); - } - if (events.onMessage) { - server.addEventListener("message", (evt: MessageEvent) => - events.onMessage?.(evt, wsContext), - ); - } - if (events.onError) { - server.addEventListener("error", (evt: Event) => - events.onError?.(evt, wsContext), - ); - } - - server.accept?.(); - - // note: cloudflare workers doesn't support 'open' event, so we call it immediately with a fake event - // - // we have to do this after `server.accept() is called` - events.onOpen?.(new Event("open"), wsContext); - - return new Response(null, { - status: 101, - headers: { - // HACK: Required in order for Cloudflare to not error with "Network connection lost" - // - // This bug undocumented. Cannot easily reproduce outside of RivetKit. - "Sec-WebSocket-Protocol": "rivetkit", - }, - webSocket: client, - }); -}); +// import { WSContext, defineWebSocketHelper } from "hono/ws"; +// import type { UpgradeWebSocket, WSEvents, WSReadyState } from "hono/ws"; +// +// // Based on https://github.com/honojs/hono/issues/1153#issuecomment-1767321332 +// export const upgradeWebSocket: UpgradeWebSocket< +// WebSocket, +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// any, +// WSEvents +// > = defineWebSocketHelper(async (c, events) => { +// const upgradeHeader = c.req.header("Upgrade"); +// if (upgradeHeader !== "websocket") { +// return; +// } +// +// const webSocketPair = new WebSocketPair(); +// const client: WebSocket = webSocketPair[0]; +// const server: WebSocket = webSocketPair[1]; +// +// const wsContext = new WSContext({ +// close: (code, reason) => server.close(code, reason), +// get protocol() { +// return server.protocol; +// }, +// raw: server, +// get readyState() { +// return server.readyState as WSReadyState; +// }, +// url: server.url ? new URL(server.url) : null, +// send: (source) => server.send(source), +// }); +// +// if (events.onClose) { +// server.addEventListener("close", (evt: CloseEvent) => +// events.onClose?.(evt, wsContext), +// ); +// } +// if (events.onMessage) { +// server.addEventListener("message", (evt: MessageEvent) => +// events.onMessage?.(evt, wsContext), +// ); +// } +// if (events.onError) { +// server.addEventListener("error", (evt: Event) => +// events.onError?.(evt, wsContext), +// ); +// } +// +// server.accept?.(); +// +// // note: cloudflare workers doesn't support 'open' event, so we call it immediately with a fake event +// // +// // we have to do this after `server.accept() is called` +// events.onOpen?.(new Event("open"), wsContext); +// +// return new Response(null, { +// status: 101, +// headers: { +// // HACK: Required in order for Cloudflare to not error with "Network connection lost" +// // +// // This bug undocumented. Cannot easily reproduce outside of RivetKit. +// "Sec-WebSocket-Protocol": "rivetkit", +// }, +// webSocket: client, +// }); +// }); diff --git a/packages/platforms/cloudflare-workers/src/worker-driver.ts b/packages/platforms/cloudflare-workers/src/worker-driver.ts index 11054e212..d47c8724f 100644 --- a/packages/platforms/cloudflare-workers/src/worker-driver.ts +++ b/packages/platforms/cloudflare-workers/src/worker-driver.ts @@ -1,66 +1,66 @@ -import { WorkerDriver, AnyWorkerInstance } from "@rivetkit/core/driver-helpers"; -import invariant from "invariant"; -import { KEYS } from "./worker-handler-do"; - -interface DurableObjectGlobalState { - ctx: DurableObjectState; - env: unknown; -} - -/** - * Cloudflare DO can have multiple DO running within the same global scope. - * - * This allows for storing the worker context globally and looking it up by ID in `CloudflareWorkersWorkerDriver`. - */ -export class CloudflareDurableObjectGlobalState { - // Single map for all worker state - #dos: Map = new Map(); - - getDOState(workerId: string): DurableObjectGlobalState { - const state = this.#dos.get(workerId); - invariant(state !== undefined, "durable object state not in global state"); - return state; - } - - setDOState(workerId: string, state: DurableObjectGlobalState) { - this.#dos.set(workerId, state); - } -} - -export interface WorkerDriverContext { - ctx: DurableObjectState; - env: unknown; -} - -export class CloudflareWorkersWorkerDriver implements WorkerDriver { - #globalState: CloudflareDurableObjectGlobalState; - - constructor(globalState: CloudflareDurableObjectGlobalState) { - this.#globalState = globalState; - } - - #getDOCtx(workerId: string) { - return this.#globalState.getDOState(workerId).ctx; - } - - getContext(workerId: string): WorkerDriverContext { - const state = this.#globalState.getDOState(workerId); - return { ctx: state.ctx, env: state.env }; - } - - async readInput(workerId: string): Promise { - return await this.#getDOCtx(workerId).storage.get(KEYS.INPUT); - } - - async readPersistedData(workerId: string): Promise { - return await this.#getDOCtx(workerId).storage.get(KEYS.PERSISTED_DATA); - } - - async writePersistedData(workerId: string, data: unknown): Promise { - await this.#getDOCtx(workerId).storage.put(KEYS.PERSISTED_DATA, data); - } - - async setAlarm(worker: AnyWorkerInstance, timestamp: number): Promise { - await this.#getDOCtx(worker.id).storage.setAlarm(timestamp); - } -} +// import { WorkerDriver, AnyWorkerInstance } from "@rivetkit/core/driver-helpers"; +// import invariant from "invariant"; +// import { KEYS } from "./worker-handler-do"; +// +// interface DurableObjectGlobalState { +// ctx: DurableObjectState; +// env: unknown; +// } +// +// /** +// * Cloudflare DO can have multiple DO running within the same global scope. +// * +// * This allows for storing the worker context globally and looking it up by ID in `CloudflareWorkersWorkerDriver`. +// */ +// export class CloudflareDurableObjectGlobalState { +// // Single map for all worker state +// #dos: Map = new Map(); +// +// getDOState(workerId: string): DurableObjectGlobalState { +// const state = this.#dos.get(workerId); +// invariant(state !== undefined, "durable object state not in global state"); +// return state; +// } +// +// setDOState(workerId: string, state: DurableObjectGlobalState) { +// this.#dos.set(workerId, state); +// } +// } +// +// export interface WorkerDriverContext { +// ctx: DurableObjectState; +// env: unknown; +// } +// +// export class CloudflareWorkersWorkerDriver implements WorkerDriver { +// #globalState: CloudflareDurableObjectGlobalState; +// +// constructor(globalState: CloudflareDurableObjectGlobalState) { +// this.#globalState = globalState; +// } +// +// #getDOCtx(workerId: string) { +// return this.#globalState.getDOState(workerId).ctx; +// } +// +// getContext(workerId: string): WorkerDriverContext { +// const state = this.#globalState.getDOState(workerId); +// return { ctx: state.ctx, env: state.env }; +// } +// +// async readInput(workerId: string): Promise { +// return await this.#getDOCtx(workerId).storage.get(KEYS.INPUT); +// } +// +// async readPersistedData(workerId: string): Promise { +// return await this.#getDOCtx(workerId).storage.get(KEYS.PERSISTED_DATA); +// } +// +// async writePersistedData(workerId: string, data: unknown): Promise { +// await this.#getDOCtx(workerId).storage.put(KEYS.PERSISTED_DATA, data); +// } +// +// async setAlarm(worker: AnyWorkerInstance, timestamp: number): Promise { +// await this.#getDOCtx(worker.id).storage.setAlarm(timestamp); +// } +// } diff --git a/packages/platforms/cloudflare-workers/src/worker-handler-do.ts b/packages/platforms/cloudflare-workers/src/worker-handler-do.ts index 0654b4c9d..b83ce9fb9 100644 --- a/packages/platforms/cloudflare-workers/src/worker-handler-do.ts +++ b/packages/platforms/cloudflare-workers/src/worker-handler-do.ts @@ -1,186 +1,186 @@ -import { DurableObject } from "cloudflare:workers"; -import type { Registry, RunConfig, WorkerKey } from "@rivetkit/core"; -import { logger } from "./log"; -import { PartitionTopologyWorker } from "@rivetkit/core/topologies/partition"; -import { - CloudflareDurableObjectGlobalState, - CloudflareWorkersWorkerDriver, -} from "./worker-driver"; -import { Bindings, CF_AMBIENT_ENV } from "./handler"; -import { ExecutionContext } from "hono"; - -export const KEYS = { - INITIALIZED: "rivetkit:initialized", - NAME: "rivetkit:name", - KEY: "rivetkit:key", - INPUT: "rivetkit:input", - PERSISTED_DATA: "rivetkit:data", -}; - -export interface WorkerHandlerInterface extends DurableObject { - initialize(req: WorkerInitRequest): Promise; -} - -export interface WorkerInitRequest { - name: string; - key: WorkerKey; - input?: unknown; -} - -interface InitializedData { - name: string; - key: WorkerKey; -} - -export type DurableObjectConstructor = new ( - ...args: ConstructorParameters> -) => DurableObject; - -interface LoadedWorker { - workerTopology: PartitionTopologyWorker; -} - -export function createWorkerDurableObject( - registry: Registry, - runConfig: RunConfig, -): DurableObjectConstructor { - const globalState = new CloudflareDurableObjectGlobalState(); - - /** - * Startup steps: - * 1. If not already created call `initialize`, otherwise check KV to ensure it's initialized - * 2. Load worker - * 3. Start service requests - */ - return class WorkerHandler - extends DurableObject - implements WorkerHandlerInterface - { - #initialized?: InitializedData; - #initializedPromise?: PromiseWithResolvers; - - #worker?: LoadedWorker; - - async #loadWorker(): Promise { - // This is always called from another context using CF_AMBIENT_ENV - - // Wait for init - if (!this.#initialized) { - // Wait for init - if (this.#initializedPromise) { - await this.#initializedPromise.promise; - } else { - this.#initializedPromise = Promise.withResolvers(); - const res = await this.ctx.storage.get([ - KEYS.INITIALIZED, - KEYS.NAME, - KEYS.KEY, - ]); - if (res.get(KEYS.INITIALIZED)) { - const name = res.get(KEYS.NAME) as string; - if (!name) throw new Error("missing worker name"); - const key = res.get(KEYS.KEY) as WorkerKey; - if (!key) throw new Error("missing worker key"); - - logger().debug("already initialized", { name, key }); - - this.#initialized = { name, key }; - this.#initializedPromise.resolve(); - } else { - logger().debug("waiting to initialize"); - } - } - } - - // Check if already loaded - if (this.#worker) { - return this.#worker; - } - - if (!this.#initialized) throw new Error("Not initialized"); - - // Configure worker driver - runConfig.driver.worker = new CloudflareWorkersWorkerDriver(globalState); - - const workerTopology = new PartitionTopologyWorker( - registry.config, - runConfig, - ); - - // Register DO with global state - // HACK: This leaks the DO context, but DO does not provide a native way - // of knowing when the DO shuts down. We're making a broad assumption - // that DO will boot a new isolate frequenlty enough that this is not an issue. - const workerId = this.ctx.id.toString(); - globalState.setDOState(workerId, { ctx: this.ctx, env: this.env }); - - // Save worker - this.#worker = { - workerTopology, - }; - - // Start worker - await workerTopology.start( - workerId, - this.#initialized.name, - this.#initialized.key, - // TODO: - "unknown", - ); - - return this.#worker; - } - - /** RPC called by the service that creates the DO to initialize it. */ - async initialize(req: WorkerInitRequest) { - // TODO: Need to add this to a core promise that needs to be resolved before start - - return await CF_AMBIENT_ENV.run(this.env, async () => { - await this.ctx.storage.put({ - [KEYS.INITIALIZED]: true, - [KEYS.NAME]: req.name, - [KEYS.KEY]: req.key, - [KEYS.INPUT]: req.input, - }); - this.#initialized = { - name: req.name, - key: req.key, - }; - - logger().debug("initialized worker", { key: req.key }); - - // Preemptively worker so the lifecycle hooks are called - await this.#loadWorker(); - }); - } - - async fetch(request: Request): Promise { - return await CF_AMBIENT_ENV.run(this.env, async () => { - const { workerTopology } = await this.#loadWorker(); - - const ctx = this.ctx; - return await workerTopology.router.fetch( - request, - this.env, - // Implement execution context so we can wait on requests - { - waitUntil(promise: Promise) { - ctx.waitUntil(promise); - }, - passThroughOnException() { - // Do nothing - }, - props: {}, - } satisfies ExecutionContext, - ); - }); - } - - async alarm(): Promise { - return await CF_AMBIENT_ENV.run(this.env, async () => { - const { workerTopology } = await this.#loadWorker(); - await workerTopology.worker.onAlarm(); - }); - } - }; -} +// import { DurableObject } from "cloudflare:workers"; +// import type { Registry, RunConfig, WorkerKey } from "@rivetkit/core"; +// import { logger } from "./log"; +// import { PartitionTopologyWorker } from "@rivetkit/core/topologies/partition"; +// import { +// CloudflareDurableObjectGlobalState, +// CloudflareWorkersWorkerDriver, +// } from "./worker-driver"; +// import { Bindings, CF_AMBIENT_ENV } from "./handler"; +// import { ExecutionContext } from "hono"; +// +// export const KEYS = { +// INITIALIZED: "rivetkit:initialized", +// NAME: "rivetkit:name", +// KEY: "rivetkit:key", +// INPUT: "rivetkit:input", +// PERSISTED_DATA: "rivetkit:data", +// }; +// +// export interface WorkerHandlerInterface extends DurableObject { +// initialize(req: WorkerInitRequest): Promise; +// } +// +// export interface WorkerInitRequest { +// name: string; +// key: WorkerKey; +// input?: unknown; +// } +// +// interface InitializedData { +// name: string; +// key: WorkerKey; +// } +// +// export type DurableObjectConstructor = new ( +// ...args: ConstructorParameters> +// ) => DurableObject; +// +// interface LoadedWorker { +// workerTopology: PartitionTopologyWorker; +// } +// +// export function createWorkerDurableObject( +// registry: Registry, +// runConfig: RunConfig, +// ): DurableObjectConstructor { +// const globalState = new CloudflareDurableObjectGlobalState(); +// +// /** +// * Startup steps: +// * 1. If not already created call `initialize`, otherwise check KV to ensure it's initialized +// * 2. Load worker +// * 3. Start service requests +// */ +// return class WorkerHandler +// extends DurableObject +// implements WorkerHandlerInterface +// { +// #initialized?: InitializedData; +// #initializedPromise?: PromiseWithResolvers; +// +// #worker?: LoadedWorker; +// +// async #loadWorker(): Promise { +// // This is always called from another context using CF_AMBIENT_ENV +// +// // Wait for init +// if (!this.#initialized) { +// // Wait for init +// if (this.#initializedPromise) { +// await this.#initializedPromise.promise; +// } else { +// this.#initializedPromise = Promise.withResolvers(); +// const res = await this.ctx.storage.get([ +// KEYS.INITIALIZED, +// KEYS.NAME, +// KEYS.KEY, +// ]); +// if (res.get(KEYS.INITIALIZED)) { +// const name = res.get(KEYS.NAME) as string; +// if (!name) throw new Error("missing worker name"); +// const key = res.get(KEYS.KEY) as WorkerKey; +// if (!key) throw new Error("missing worker key"); +// +// logger().debug("already initialized", { name, key }); +// +// this.#initialized = { name, key }; +// this.#initializedPromise.resolve(); +// } else { +// logger().debug("waiting to initialize"); +// } +// } +// } +// +// // Check if already loaded +// if (this.#worker) { +// return this.#worker; +// } +// +// if (!this.#initialized) throw new Error("Not initialized"); +// +// // Configure worker driver +// runConfig.driver.worker = new CloudflareWorkersWorkerDriver(globalState); +// +// const workerTopology = new PartitionTopologyWorker( +// registry.config, +// runConfig, +// ); +// +// // Register DO with global state +// // HACK: This leaks the DO context, but DO does not provide a native way +// // of knowing when the DO shuts down. We're making a broad assumption +// // that DO will boot a new isolate frequenlty enough that this is not an issue. +// const workerId = this.ctx.id.toString(); +// globalState.setDOState(workerId, { ctx: this.ctx, env: this.env }); +// +// // Save worker +// this.#worker = { +// workerTopology, +// }; +// +// // Start worker +// await workerTopology.start( +// workerId, +// this.#initialized.name, +// this.#initialized.key, +// // TODO: +// "unknown", +// ); +// +// return this.#worker; +// } +// +// /** RPC called by the service that creates the DO to initialize it. */ +// async initialize(req: WorkerInitRequest) { +// // TODO: Need to add this to a core promise that needs to be resolved before start +// +// return await CF_AMBIENT_ENV.run(this.env, async () => { +// await this.ctx.storage.put({ +// [KEYS.INITIALIZED]: true, +// [KEYS.NAME]: req.name, +// [KEYS.KEY]: req.key, +// [KEYS.INPUT]: req.input, +// }); +// this.#initialized = { +// name: req.name, +// key: req.key, +// }; +// +// logger().debug("initialized worker", { key: req.key }); +// +// // Preemptively worker so the lifecycle hooks are called +// await this.#loadWorker(); +// }); +// } +// +// async fetch(request: Request): Promise { +// return await CF_AMBIENT_ENV.run(this.env, async () => { +// const { workerTopology } = await this.#loadWorker(); +// +// const ctx = this.ctx; +// return await workerTopology.router.fetch( +// request, +// this.env, +// // Implement execution context so we can wait on requests +// { +// waitUntil(promise: Promise) { +// ctx.waitUntil(promise); +// }, +// passThroughOnException() { +// // Do nothing +// }, +// props: {}, +// } satisfies ExecutionContext, +// ); +// }); +// } +// +// async alarm(): Promise { +// return await CF_AMBIENT_ENV.run(this.env, async () => { +// const { workerTopology } = await this.#loadWorker(); +// await workerTopology.worker.onAlarm(); +// }); +// } +// }; +// } diff --git a/packages/platforms/nodejs/package.json b/packages/platforms/nodejs/package.json deleted file mode 100644 index 8bc48b3e9..000000000 --- a/packages/platforms/nodejs/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "@rivetkit/nodejs", - "version": "0.9.0-rc.1", - "keywords": [ - "rivetkit", - "nodejs", - "platform", - "server", - "runtime" - ], - "files": [ - "src", - "dist", - "package.json" - ], - "type": "module", - "exports": { - ".": { - "import": { - "types": "./dist/mod.d.ts", - "default": "./dist/mod.js" - }, - "require": { - "types": "./dist/mod.d.cts", - "default": "./dist/mod.cjs" - } - } - }, - "sideEffects": false, - "scripts": { - "build": "tsup src/mod.ts", - "check-types": "tsc --noEmit" - }, - "peerDependencies": { - "@rivetkit/file-system": "*", - "@rivetkit/memory": "*", - "@rivetkit/core": "*" - }, - "devDependencies": { - "@rivetkit/file-system": "workspace:*", - "@rivetkit/memory": "workspace:*", - "hono": "^4.8.0", - "@rivetkit/core": "workspace:*", - "tsup": "^8.4.0", - "typescript": "^5.5.2" - }, - "dependencies": { - "@hono/node-server": "^1.14.4", - "@hono/node-ws": "^1.0.8", - "@types/node": "^24.0.3", - "zod": "^3.24.2" - }, - "stableVersion": "0.8.0" -} diff --git a/packages/platforms/nodejs/src/log.ts b/packages/platforms/nodejs/src/log.ts deleted file mode 100644 index cea6d275d..000000000 --- a/packages/platforms/nodejs/src/log.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { getLogger } from "@rivetkit/core/log"; - -export const LOGGER_NAME = "nodejs"; - -export function logger() { - return getLogger(LOGGER_NAME); -} diff --git a/packages/platforms/nodejs/src/mod.ts b/packages/platforms/nodejs/src/mod.ts deleted file mode 100644 index 09889e6b1..000000000 --- a/packages/platforms/nodejs/src/mod.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { serve as honoServe, type ServerType } from "@hono/node-server"; -import { logger } from "./log"; -import type { Registry } from "@rivetkit/core"; -import { z } from "zod"; -import type { Client } from "@rivetkit/core/client"; -import { createNodeWebSocket, type NodeWebSocket } from "@hono/node-ws"; -import { RunConfigSchema } from "@rivetkit/core/driver-helpers"; -import { RegistryWorkers } from "@rivetkit/core"; -import { Hono } from "hono"; - -const ConfigSchema = RunConfigSchema.extend({ - basePath: z.string().optional().default("/registry"), - hostname: z - .string() - .optional() - .default(process.env.HOSTNAME ?? "127.0.0.1"), - port: z - .number() - .optional() - .default(Number.parseInt(process.env.PORT ?? "6420")), -}); - -export type InputConfig = z.input; - -export function serve( - registry: Registry, - inputConfig?: InputConfig, -): { server: ServerType; client: Client> } { - const runConfig = ConfigSchema.parse(inputConfig); - - // Setup WebSocket routing for Node - // - // Save `injectWebSocket` for after server is created - let injectWebSocket: NodeWebSocket["injectWebSocket"] | undefined; - if (!runConfig.getUpgradeWebSocket) { - runConfig.getUpgradeWebSocket = (router) => { - const webSocket = createNodeWebSocket({ app: router }); - injectWebSocket = webSocket.injectWebSocket; - return webSocket.upgradeWebSocket; - }; - } - - const { client, hono: rawHono } = registry.run(runConfig); - - const hono = new Hono().route(runConfig.basePath, rawHono); - - const server = honoServe({ - fetch: hono.fetch, - hostname: runConfig.hostname, - port: runConfig.port, - }); - if (!injectWebSocket) throw new Error("missing injectWebSocket"); - injectWebSocket(server); - - logger().info("rivetkit started", { - hostname: runConfig.hostname, - port: runConfig.port, - }); - - return { server, client }; -} diff --git a/packages/platforms/nodejs/tsconfig.json b/packages/platforms/nodejs/tsconfig.json deleted file mode 100644 index 043f499c0..000000000 --- a/packages/platforms/nodejs/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["src/**/*", "tests/**/*"] -} diff --git a/packages/platforms/nodejs/tsup.config.ts b/packages/platforms/nodejs/tsup.config.ts deleted file mode 100644 index 677cffb7b..000000000 --- a/packages/platforms/nodejs/tsup.config.ts +++ /dev/null @@ -1,4 +0,0 @@ -import defaultConfig from "../../../tsup.base.ts"; -import { defineConfig } from "tsup"; - -export default defineConfig(defaultConfig); diff --git a/packages/platforms/nodejs/turbo.json b/packages/platforms/nodejs/turbo.json deleted file mode 100644 index 95960709b..000000000 --- a/packages/platforms/nodejs/turbo.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "extends": ["//"] -} diff --git a/packages/platforms/rivet/.actorcore/entrypoint-counter.js b/packages/platforms/rivet/.actorcore/entrypoint-counter.js deleted file mode 100644 index f6ace2d0c..000000000 --- a/packages/platforms/rivet/.actorcore/entrypoint-counter.js +++ /dev/null @@ -1,3 +0,0 @@ -import { createActorHandler } from "@actor-core/rivet"; -import { app } from "../tmp/actor-core-test-fa4426da-4d36-40b3-aa42-7e8f444d32f6/app.ts"; -export default createActorHandler({ app }); \ No newline at end of file diff --git a/packages/platforms/rivet/README.md b/packages/platforms/rivet/README.md deleted file mode 100644 index 7dbf0d601..000000000 --- a/packages/platforms/rivet/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# RivetKit Rivet Adapter - -_Lightweight Libraries for Backends_ - -[Learn More →](https://github.com/rivet-gg/rivetkit) - -[Discord](https://rivet.gg/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-gg/rivetkit/issues) - -## License - -Apache 2.0 \ No newline at end of file diff --git a/packages/platforms/rivet/TESTING.md b/packages/platforms/rivet/TESTING.md deleted file mode 100644 index 18c016e1e..000000000 --- a/packages/platforms/rivet/TESTING.md +++ /dev/null @@ -1,12 +0,0 @@ -# Testing - -The `RIVET_CLOUD_TOKEN` and other `RIVET_*` env vars are required to run tests. - -``` -export RIVET_CLOUD_TOKEN=xxxxx -# If not using Rivet Cloud -export RIVET_ENDPOINT=http://localhost:8080 -rivet shell -pnpm test -``` - diff --git a/packages/platforms/rivet/package.json b/packages/platforms/rivet/package.json deleted file mode 100644 index e6252266c..000000000 --- a/packages/platforms/rivet/package.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "name": "@rivetkit/rivet", - "version": "0.9.0-rc.1", - "keywords": ["rivetkit", "rivet", "platform", "cloud", "managed", "scaling"], - "files": [ - "src", - "dist", - "package.json" - ], - "type": "module", - "exports": { - ".": { - "import": { - "types": "./dist/mod.d.ts", - "default": "./dist/mod.js" - }, - "require": { - "types": "./dist/mod.d.cts", - "default": "./dist/mod.cjs" - } - }, - "./worker": { - "import": { - "types": "./dist/worker.d.ts", - "default": "./dist/worker.js" - }, - "require": { - "types": "./dist/worker.d.cts", - "default": "./dist/worker.cjs" - } - }, - "./manager": { - "import": { - "types": "./dist/manager.d.ts", - "default": "./dist/manager.js" - }, - "require": { - "types": "./dist/manager.d.cts", - "default": "./dist/manager.cjs" - } - }, - "./tsconfig": "./dist/tsconfig.json" - }, - "sideEffects": false, - "scripts": { - "build": "tsup src/mod.ts src/worker.ts src/manager.ts", - "check-types": "tsc --noEmit", - "test": "vitest run" - }, - "peerDependencies": { - "@rivetkit/core": "*" - }, - "devDependencies": { - "@rivet-gg/actor-core": "^25.1.0", - "@rivet-gg/api": "^25.4.2", - "@types/deno": "^2.0.0", - "@types/invariant": "^2", - "@types/node": "^22.13.1", - "@rivetkit/core": "workspace:*", - "tsup": "^8.4.0", - "typescript": "^5.5.2", - "vitest": "^3.1.1" - }, - "dependencies": { - "@hono/node-server": "^1.14.4", - "@hono/node-ws": "^1.1.7", - "hono": "^4.7.0", - "invariant": "^2.2.4", - "zod": "^3.24.2" - }, - "stableVersion": "0.8.0" -} diff --git a/packages/platforms/rivet/public/tsconfig.json b/packages/platforms/rivet/public/tsconfig.json deleted file mode 100644 index c39fc17c7..000000000 --- a/packages/platforms/rivet/public/tsconfig.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "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": ["deno"], - /* 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/**/*", "tests/**/*"] -} diff --git a/packages/platforms/rivet/src/globals.d.ts b/packages/platforms/rivet/src/globals.d.ts deleted file mode 100644 index bac4a2804..000000000 --- a/packages/platforms/rivet/src/globals.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare const Deno: any; diff --git a/packages/platforms/rivet/src/manager.ts b/packages/platforms/rivet/src/manager.ts deleted file mode 100644 index f7d939bb3..000000000 --- a/packages/platforms/rivet/src/manager.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { setupLogging } from "@rivetkit/core/log"; -import { serve as honoServe } from "@hono/node-server"; -import { createNodeWebSocket, NodeWebSocket } from "@hono/node-ws"; -import { logger } from "./log"; -import { GetWorkerMeta, RivetManagerDriver } from "./manager-driver"; -import type { RivetClientConfig } from "./rivet-client"; -import { PartitionTopologyManager } from "@rivetkit/core/topologies/partition"; -import { proxy } from "hono/proxy"; -import invariant from "invariant"; -import { ConfigSchema, InputConfig } from "./config"; -import type { Registry, RunConfig } from "@rivetkit/core"; -import { createWebSocketProxy } from "./ws-proxy"; -import { flushCache, getWorkerMeta } from "./worker-meta"; -import { - HEADER_AUTH_DATA, - HEADER_CONN_PARAMS, - HEADER_ENCODING, - HEADER_EXPOSE_INTERNAL_ERROR, -} from "@rivetkit/core/driver-helpers"; -import { importWebSocket } from "@rivetkit/core/driver-helpers/websocket"; -import { RivetWorkerDriver } from "./worker-driver"; - -export async function startManager( - registry: Registry, - inputConfig?: InputConfig, -): Promise { - setupLogging(); - - const portStr = process.env.PORT_HTTP; - if (!portStr) { - throw "Missing port"; - } - const port = Number.parseInt(portStr); - if (!Number.isFinite(port)) { - throw "Invalid port"; - } - - const endpoint = process.env.RIVET_ENDPOINT; - if (!endpoint) throw new Error("missing RIVET_ENDPOINT"); - const token = process.env.RIVET_SERVICE_TOKEN; - if (!token) throw new Error("missing RIVET_SERVICE_TOKEN"); - const project = process.env.RIVET_PROJECT; - if (!project) throw new Error("missing RIVET_PROJECT"); - const environment = process.env.RIVET_ENVIRONMENT; - if (!environment) throw new Error("missing RIVET_ENVIRONMENT"); - - const clientConfig: RivetClientConfig = { - endpoint, - token, - project, - environment, - }; - - const config = ConfigSchema.parse(inputConfig); - let injectWebSocket: NodeWebSocket["injectWebSocket"] | undefined; - const runConfig = { - driver: { - topology: "partition", - manager: new RivetManagerDriver(clientConfig), - // HACK: We can't build the worker driver until we're inside the worker - worker: undefined as any, - }, - // Setup WebSocket routing for Node - // - // Save `injectWebSocket` for after server is created - getUpgradeWebSocket: (app) => { - const webSocket = createNodeWebSocket({ app }); - injectWebSocket = webSocket.injectWebSocket; - return webSocket.upgradeWebSocket; - }, - ...config, - } satisfies RunConfig; - - //// Force disable inspector - //driverConfig.registry.config.inspector = { - // enabled: false, - //}; - - //const corsConfig = driverConfig.registry.config.cors; - // - //// Enable CORS for Rivet domains - //driverConfig.registry.config.cors = { - // ...driverConfig.registry.config.cors, - // origin: (origin, c) => { - // const isRivetOrigin = - // origin.endsWith(".rivet.gg") || origin.includes("localhost:"); - // const configOrigin = corsConfig?.origin; - // - // if (isRivetOrigin) { - // return origin; - // } - // if (typeof configOrigin === "function") { - // return configOrigin(origin, c); - // } - // if (typeof configOrigin === "string") { - // return configOrigin; - // } - // return null; - // }, - //}; - - // Create manager topology - const managerTopology = new PartitionTopologyManager( - registry.config, - runConfig, - { - sendRequest: async (workerId, workerRequest) => { - const meta = await getWorkerMeta(clientConfig, workerId); - invariant(meta, "worker should exist"); - - const parsedRequestUrl = new URL(workerRequest.url); - const workerUrl = `${meta.endpoint}${parsedRequestUrl.pathname}${parsedRequestUrl.search}`; - - logger().debug("proxying request to rivet worker", { - method: workerRequest.method, - url: workerUrl, - }); - - const proxyRequest = new Request(workerUrl, workerRequest); - return await fetch(proxyRequest); - }, - openWebSocket: async (workerId, encodingKind, params: unknown) => { - const WebSocket = await importWebSocket(); - - const meta = await getWorkerMeta(clientConfig, workerId); - invariant(meta, "worker should exist"); - - const wsEndpoint = meta.endpoint.replace(/^http/, "ws"); - const url = `${wsEndpoint}/connect/websocket`; - - const headers: Record = { - Upgrade: "websocket", - Connection: "Upgrade", - [HEADER_EXPOSE_INTERNAL_ERROR]: "true", - [HEADER_ENCODING]: encodingKind, - }; - if (params) { - headers[HEADER_CONN_PARAMS] = JSON.stringify(params); - } - - logger().debug("opening websocket to worker", { - workerId, - url, - }); - - return new WebSocket(url, { headers }); - }, - proxyRequest: async (c, workerRequest, workerId) => { - const meta = await getWorkerMeta(clientConfig, workerId); - invariant(meta, "worker should exist"); - - const parsedRequestUrl = new URL(workerRequest.url); - const workerUrl = `${meta.endpoint}${parsedRequestUrl.pathname}${parsedRequestUrl.search}`; - - logger().debug("proxying request to rivet worker", { - method: workerRequest.method, - url: workerUrl, - }); - - const proxyRequest = new Request(workerUrl, workerRequest); - return await proxy(proxyRequest); - }, - proxyWebSocket: async ( - c, - path, - workerId, - encoding, - connParmas, - authData, - upgradeWebSocket, - ) => { - const meta = await getWorkerMeta(clientConfig, workerId); - invariant(meta, "worker should exist"); - - const workerUrl = `${meta.endpoint}${path}`; - - logger().debug("proxying websocket to rivet worker", { - url: workerUrl, - }); - - // Build headers - const headers: Record = { - [HEADER_EXPOSE_INTERNAL_ERROR]: "true", - [HEADER_ENCODING]: encoding, - }; - if (connParmas) { - headers[HEADER_CONN_PARAMS] = JSON.stringify(connParmas); - } - if (authData) { - headers[HEADER_AUTH_DATA] = JSON.stringify(authData); - } - - const handlers = await createWebSocketProxy(workerUrl, headers); - - // upgradeWebSocket is middleware, so we need to pass fake handlers - invariant(upgradeWebSocket, "missing upgradeWebSocket"); - return upgradeWebSocket((c) => handlers)(c, async () => {}); - }, - }, - ); - - // HACK: Expose endpoint for tests to flush cache - if (registry.config.test.enabled) { - managerTopology.router.post("/.test/rivet/flush-cache", (c) => { - flushCache(); - return c.text("ok"); - }); - } - - // Start server with ambient env wrapper - logger().info("server running", { port }); - const server = honoServe({ - fetch: managerTopology.router.fetch, - hostname: "0.0.0.0", - port, - }); - if (!injectWebSocket) throw new Error("injectWebSocket not defined"); - injectWebSocket(server); -} diff --git a/packages/platforms/rivet/src/mod.ts b/packages/platforms/rivet/src/mod.ts deleted file mode 100644 index 65bef516c..000000000 --- a/packages/platforms/rivet/src/mod.ts +++ /dev/null @@ -1 +0,0 @@ -export type { InputConfig as Config } from "./config"; diff --git a/packages/platforms/rivet/tests/deployment.test.ts b/packages/platforms/rivet/tests/deployment.test.ts deleted file mode 100644 index f01cd0a3f..000000000 --- a/packages/platforms/rivet/tests/deployment.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { describe, test, expect, beforeAll, afterAll } from "vitest"; -import os from "node:os"; -import fs from "node:fs/promises"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { deployToRivet } from "./rivet-deploy"; -import { randomUUID } from "node:crypto"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -// Simple counter worker definition to deploy -const COUNTER_WORKER = ` -import { worker, setup } from "@rivetkit/core"; - -const counter = worker({ - state: { count: 0 }, - actions: { - increment: (c, amount) => { - c.state.count += amount; - c.broadcast("newCount", c.state.count); - return c.state.count; - }, - getCount: (c) => { - return c.state.count; - }, - }, -}); - -export const registry = setup({ - workers: { counter }, -}); - -export type Registry = typeof registry; -`; - -test("Rivet deployment tests", async () => { - const tempFilePath = path.join(os.tmpdir(), `registry-${randomUUID()}`); - await fs.writeFile(tempFilePath, COUNTER_WORKER); - await deployToRivet("test-registry", tempFilePath, true); -}); diff --git a/packages/platforms/rivet/tests/driver-tests.test.ts b/packages/platforms/rivet/tests/driver-tests.test.ts deleted file mode 100644 index 7036edf86..000000000 --- a/packages/platforms/rivet/tests/driver-tests.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { runDriverTests } from "@rivetkit/core/driver-test-suite"; -import { deployToRivet, rivetClientConfig } from "./rivet-deploy"; -import { RivetClientConfig, rivetRequest } from "../src/rivet-client"; -import invariant from "invariant"; - -let deployProjectOnce: Promise | undefined = undefined; - -// IMPORTANT: Unlike other tests, Rivet tests are ran without parallelism since we reuse the same shared environment. Eventually we can create an environment per test to create isolated instances. -runDriverTests({ - useRealTimers: true, - HACK_skipCleanupNet: true, - async start(projectPath: string) { - // Setup project - if (!deployProjectOnce) { - deployProjectOnce = deployToRivet(projectPath); - } - const endpoint = await deployProjectOnce; - - // Cleanup workers from previous tests - await deleteAllWorkers(rivetClientConfig); - - // Flush cache since we manually updated the workers - const res = await fetch(`${endpoint}/.test/rivet/flush-cache`, { - method: "POST", - }); - invariant(res.ok, `request failed: ${res.status}`); - - return { - endpoint, - async cleanup() { - // This takes time and slows down tests -- it's fine if we leak workers that'll be cleaned up in the next run - // await deleteAllWorkers(rivetClientConfig); - }, - }; - }, -}); - -async function deleteAllWorkers(clientConfig: RivetClientConfig) { - // TODO: This is not paginated - - console.log("Listing workers to delete"); - const { actors } = await rivetRequest< - void, - { actors: { id: string; tags: Record }[] } - >(clientConfig, "GET", "/actors"); - - for (const actor of actors) { - if (actor.tags.role !== "worker") continue; - - console.log(`Deleting worker ${actor.id} (${JSON.stringify(actor.tags)})`); - await rivetRequest( - clientConfig, - "DELETE", - `/actors/${actor.id}`, - ); - } -} diff --git a/packages/platforms/rivet/tests/key-serialization.test.ts b/packages/platforms/rivet/tests/key-serialization.test.ts deleted file mode 100644 index 0866134e2..000000000 --- a/packages/platforms/rivet/tests/key-serialization.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { describe, test, expect } from "vitest"; -import { serializeKeyForTag, deserializeKeyFromTag, EMPTY_KEY, KEY_SEPARATOR } from "../src/util"; - -describe("Key serialization and deserialization", () => { - // Test serialization - describe("serializeKeyForTag", () => { - test("serializes empty key array", () => { - expect(serializeKeyForTag([])).toBe(EMPTY_KEY); - }); - - test("serializes single key", () => { - expect(serializeKeyForTag(["test"])).toBe("test"); - }); - - test("serializes multiple keys", () => { - expect(serializeKeyForTag(["a", "b", "c"])).toBe(`a${KEY_SEPARATOR}b${KEY_SEPARATOR}c`); - }); - - test("escapes commas in keys", () => { - expect(serializeKeyForTag(["a,b"])).toBe("a\\,b"); - expect(serializeKeyForTag(["a,b", "c"])).toBe(`a\\,b${KEY_SEPARATOR}c`); - }); - - test("escapes empty key marker in keys", () => { - expect(serializeKeyForTag([EMPTY_KEY])).toBe(`\\${EMPTY_KEY}`); - }); - - test("handles complex keys", () => { - expect(serializeKeyForTag(["a,b", EMPTY_KEY, "c,d"])).toBe(`a\\,b${KEY_SEPARATOR}\\${EMPTY_KEY}${KEY_SEPARATOR}c\\,d`); - }); - }); - - // Test deserialization - describe("deserializeKeyFromTag", () => { - test("deserializes empty string", () => { - expect(deserializeKeyFromTag("")).toEqual([]); - }); - - test("deserializes undefined/null", () => { - expect(deserializeKeyFromTag(undefined as unknown as string)).toEqual([]); - expect(deserializeKeyFromTag(null as unknown as string)).toEqual([]); - }); - - test("deserializes empty key marker", () => { - expect(deserializeKeyFromTag(EMPTY_KEY)).toEqual([]); - }); - - test("deserializes single key", () => { - expect(deserializeKeyFromTag("test")).toEqual(["test"]); - }); - - test("deserializes multiple keys", () => { - expect(deserializeKeyFromTag(`a${KEY_SEPARATOR}b${KEY_SEPARATOR}c`)).toEqual(["a", "b", "c"]); - }); - - test("deserializes keys with escaped commas", () => { - expect(deserializeKeyFromTag("a\\,b")).toEqual(["a,b"]); - expect(deserializeKeyFromTag(`a\\,b${KEY_SEPARATOR}c`)).toEqual(["a,b", "c"]); - }); - - test("deserializes keys with escaped empty key marker", () => { - expect(deserializeKeyFromTag(`\\${EMPTY_KEY}`)).toEqual([EMPTY_KEY]); - }); - - test("deserializes complex keys", () => { - expect(deserializeKeyFromTag(`a\\,b${KEY_SEPARATOR}\\${EMPTY_KEY}${KEY_SEPARATOR}c\\,d`)).toEqual(["a,b", EMPTY_KEY, "c,d"]); - }); - }); - - // Test roundtrip - describe("roundtrip", () => { - const testKeys = [ - [], - ["test"], - ["a", "b", "c"], - ["a,b", "c"], - [EMPTY_KEY], - ["a,b", EMPTY_KEY, "c,d"], - ["special\\chars", "more:complex,keys", "final key"] - ]; - - testKeys.forEach(key => { - test(`roundtrip: ${JSON.stringify(key)}`, () => { - const serialized = serializeKeyForTag(key); - const deserialized = deserializeKeyFromTag(serialized); - expect(deserialized).toEqual(key); - }); - }); - - test("handles all test cases in a large batch", () => { - for (const key of testKeys) { - const serialized = serializeKeyForTag(key); - const deserialized = deserializeKeyFromTag(serialized); - expect(deserialized).toEqual(key); - } - }); - }); - - // Test edge cases - describe("edge cases", () => { - test("handles backslash at the end", () => { - const key = ["abc\\"]; - const serialized = serializeKeyForTag(key); - const deserialized = deserializeKeyFromTag(serialized); - expect(deserialized).toEqual(key); - }); - - test("handles backslashes in middle of string", () => { - const keys = [ - ["abc\\def"], - ["abc\\\\def"], - ["path\\to\\file"] - ]; - - for (const key of keys) { - const serialized = serializeKeyForTag(key); - const deserialized = deserializeKeyFromTag(serialized); - expect(deserialized).toEqual(key); - } - }); - - test("handles commas at the end of strings", () => { - const serialized = serializeKeyForTag(["abc\\,"]); - expect(deserializeKeyFromTag(serialized)).toEqual(["abc\\,"]); - }); - - test("handles mixed backslashes and commas", () => { - const keys = [ - ["path\\to\\file,dir"], - ["file\\with,comma"], - ["path\\to\\file", "with,comma"] - ]; - - for (const key of keys) { - const serialized = serializeKeyForTag(key); - const deserialized = deserializeKeyFromTag(serialized); - expect(deserialized).toEqual(key); - } - }); - - test("handles multiple consecutive commas", () => { - const key = ["a,,b"]; - const serialized = serializeKeyForTag(key); - const deserialized = deserializeKeyFromTag(serialized); - expect(deserialized).toEqual(key); - }); - - test("handles special characters", () => { - const key = ["a💻b", "c🔑d"]; - const serialized = serializeKeyForTag(key); - const deserialized = deserializeKeyFromTag(serialized); - expect(deserialized).toEqual(key); - }); - - test("handles escaped commas immediately after separator", () => { - const key = ["abc", ",def"]; - const serialized = serializeKeyForTag(key); - expect(serialized).toBe(`abc${KEY_SEPARATOR}\\,def`); - expect(deserializeKeyFromTag(serialized)).toEqual(key); - }); - }); - - // Test exact key matching - describe("exact key matching", () => { - test("differentiates [a,b] from [a,b,c]", () => { - const key1 = ["a", "b"]; - const key2 = ["a", "b", "c"]; - - const serialized1 = serializeKeyForTag(key1); - const serialized2 = serializeKeyForTag(key2); - - expect(serialized1).not.toBe(serialized2); - }); - - test("differentiates [a,b] from [a]", () => { - const key1 = ["a", "b"]; - const key2 = ["a"]; - - const serialized1 = serializeKeyForTag(key1); - const serialized2 = serializeKeyForTag(key2); - - expect(serialized1).not.toBe(serialized2); - }); - - test("differentiates [a,b] from [a:b]", () => { - const key1 = ["a,b"]; - const key2 = ["a", "b"]; - - const serialized1 = serializeKeyForTag(key1); - const serialized2 = serializeKeyForTag(key2); - - expect(serialized1).not.toBe(serialized2); - expect(deserializeKeyFromTag(serialized1)).toEqual(key1); - expect(deserializeKeyFromTag(serialized2)).toEqual(key2); - }); - }); -}); \ No newline at end of file diff --git a/packages/platforms/rivet/tests/rivet-deploy.ts b/packages/platforms/rivet/tests/rivet-deploy.ts deleted file mode 100644 index 37f7e4b83..000000000 --- a/packages/platforms/rivet/tests/rivet-deploy.ts +++ /dev/null @@ -1,365 +0,0 @@ -import fs from "node:fs/promises"; -import path from "node:path"; -import os from "node:os"; -import { spawn, exec } from "node:child_process"; -import crypto from "node:crypto"; -import { promisify } from "node:util"; -import invariant from "invariant"; -import { RivetClient } from "@rivet-gg/api"; -import { RivetClientConfig } from "../src/rivet-client"; - -const execPromise = promisify(exec); -const apiEndpoint = process.env.RIVET_ENDPOINT!; -invariant(apiEndpoint, "missing RIVET_ENDPOINT"); -const rivetCloudToken = process.env.RIVET_CLOUD_TOKEN!; -invariant(rivetCloudToken, "missing RIVET_CLOUD_TOKEN"); -const project = process.env.RIVET_PROJECT!; -invariant(project, "missing RIVET_PROJECT"); -const environment = process.env.RIVET_ENVIRONMENT!; -invariant(environment, "missing RIVET_ENVIRONMENT"); - -export const rivetClientConfig: RivetClientConfig = { - endpoint: apiEndpoint, - token: rivetCloudToken, - project, - environment, -}; - -const rivetClient = new RivetClient({ - environment: apiEndpoint, - token: rivetCloudToken, -}); - -/** - * Helper function to write a file to the filesystem - */ -async function writeFile( - dirPath: string, - filename: string, - content: string | object, -): Promise { - const filePath = path.join(dirPath, filename); - const fileContent = - typeof content === "string" ? content : JSON.stringify(content, null, 2); - - console.log(`Writing ${filename}`); - await fs.writeFile(filePath, fileContent); -} - -/** - * Pack a package using pnpm pack and return the path to the packed tarball - */ -async function packPackage( - packageDir: string, - tmpDir: string, - packageName: string, -): Promise { - console.log(`Packing package from ${packageDir}...`); - // Generate a unique filename - const outputFileName = `${packageName}-${crypto.randomUUID()}.tgz`; - const outputPath = path.join(tmpDir, outputFileName); - - // Run pnpm pack with specific output path - await execPromise(`pnpm pack --install-if-needed --out ${outputPath}`, { - cwd: packageDir, - }); - console.log(`Generated tarball at ${outputPath}`); - return outputFileName; -} - -/** - * Deploy an app to Rivet and return the endpoint - */ -export async function deployToRivet(projectPath: string) { - console.log("=== START deployToRivet ==="); - console.log(`Deploying registry from path: ${projectPath}`); - - // Create a temporary directory for the test - const uuid = crypto.randomUUID(); - const tmpDirName = `rivetkit-test-${uuid}`; - const tmpDir = path.join(os.tmpdir(), tmpDirName); - console.log(`Creating temp directory: ${tmpDir}`); - await fs.mkdir(tmpDir, { recursive: true }); - - // Get the workspace root and package paths - const workspaceRoot = path.resolve(__dirname, "../../../.."); - const rivetPlatformPath = path.resolve(__dirname, "../"); - const rivetkitCorePath = path.resolve(workspaceRoot, "packages/core"); - - // Pack the required packages directly to the temp directory - console.log("Packing required packages..."); - const rivetPlatformFilename = await packPackage( - rivetPlatformPath, - tmpDir, - "rivetkit-rivet", - ); - const rivetkitFilename = await packPackage( - rivetkitCorePath, - tmpDir, - "rivetkit", - ); - - // Create package.json with file dependencies - const packageJson = { - name: "rivetkit-test", - private: true, - version: "1.0.0", - scripts: { - build: "tsc", - }, - dependencies: { - "@rivetkit/rivet": `file:./${rivetPlatformFilename}`, - rivetkit: `file:./${rivetkitFilename}`, - }, - devDependencies: { - typescript: "^5.3.0", - }, - packageManager: - "pnpm@10.7.1+sha512.2d92c86b7928dc8284f53494fb4201f983da65f0fb4f0d40baafa5cf628fa31dae3e5968f12466f17df7e97310e30f343a648baea1b9b350685dafafffdf5808", - }; - await writeFile(tmpDir, "package.json", packageJson); - - // Create rivet.json with workspace dependencies - const rivetJson = { - functions: { - manager: { - tags: { role: "manager", framework: "rivetkit" }, - dockerfile: "Dockerfile", - runtime: { - environment: { - RIVET_ENDPOINT: apiEndpoint, - RIVET_SERVICE_TOKEN: rivetCloudToken, // TODO: This should be a service token, but both work - RIVET_PROJECT: project, - RIVET_ENVIRONMENT: environment, - _LOG_LEVEL: "DEBUG", - _WORKER_LOG_LEVEL: "DEBUG", - }, - }, - resources: { - cpu: 250, - memory: 256, - }, - }, - }, - actors: { - worker: { - tags: { role: "worker", framework: "rivetkit" }, - script: "src/worker.ts", - }, - }, - }; - await writeFile(tmpDir, "rivet.json", rivetJson); - - // Create Dockerfile - const dockerfile = ` -FROM node:22-alpine AS builder - -RUN npm i -g corepack && corepack enable - -WORKDIR /app - -COPY package.json pnpm-lock.yaml ./ -COPY *.tgz ./ - -RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ - pnpm install --frozen-lockfile - -COPY . . -# HACK: Remove worker.ts bc file is invalid in Node -RUN rm src/worker.ts && pnpm build - -RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ - pnpm install --prod --frozen-lockfile - -FROM node:22-alpine AS runtime - -RUN addgroup -g 1001 -S rivet && \ - adduser -S rivet -u 1001 -G rivet - -WORKDIR /app - -COPY --from=builder --chown=rivet:rivet /app/dist ./dist -COPY --from=builder --chown=rivet:rivet /app/node_modules ./node_modules -COPY --from=builder --chown=rivet:rivet /app/package.json ./ - -USER rivet - -CMD ["node", "dist/server.js"] -`; - await writeFile(tmpDir, "Dockerfile", dockerfile); - - // Create .dockerignore - const dockerignore = ` -node_modules -`; - await writeFile(tmpDir, ".dockerignore", dockerignore); - - // Disable PnP - const yarnPnp = "nodeLinker: node-modules"; - await writeFile(tmpDir, ".yarnrc.yml", yarnPnp); - - // Create tsconfig.json - const tsconfig = { - compilerOptions: { - target: "ESNext", - module: "NodeNext", - moduleResolution: "NodeNext", - esModuleInterop: true, - strict: true, - skipLibCheck: true, - forceConsistentCasingInFileNames: true, - outDir: "dist", - sourceMap: true, - declaration: true, - }, - include: ["src/**/*.ts"], - }; - await writeFile(tmpDir, "tsconfig.json", tsconfig); - - // Install deps - console.log("Installing dependencies..."); - try { - const installOutput = await execPromise("pnpm install", { cwd: tmpDir }); - console.log("Install output:", installOutput.stdout); - } catch (error) { - console.error("Error installing dependencies:", error); - throw error; - } - - // Copy project to test directory - console.log(`Copying project from ${projectPath} to ${tmpDir}/src/workers`); - const projectDestDir = path.join(tmpDir, "src", "workers"); - await fs.cp(projectPath, projectDestDir, { recursive: true }); - - const serverTsContent = `import { startManager } from "@rivetkit/rivet/manager"; -import { registry } from "./workers/registry"; - -// TODO: Find a cleaner way of flagging an registry as test mode (ideally not in the config itself) -// Force enable test -registry.config.test.enabled = true; - -startManager(registry); -`; - await writeFile(tmpDir, "src/server.ts", serverTsContent); - - const workerTsContent = `import { createWorkerHandler } from "@rivetkit/rivet/worker"; -import { registry } from "./workers/registry"; - -// TODO: Find a cleaner way of flagging an registry as test mode (ideally not in the config itself) -// Force enable test -registry.config.test.enabled = true; - -export default createWorkerHandler(registry);`; - await writeFile(tmpDir, "src/worker.ts", workerTsContent); - - // Build and deploy to Rivet - console.log("Building and deploying to Rivet..."); - - if (!process.env._RIVET_SKIP_DEPLOY) { - // Deploy using the rivet CLI - console.log("Spawning rivet deploy command..."); - const deployProcess = spawn( - "rivet", - ["deploy", "--environment", environment, "--non-interactive"], - { - cwd: tmpDir, - env: { - ...process.env, - RIVET_ENDPOINT: apiEndpoint, - RIVET_CLOUD_TOKEN: rivetCloudToken, - //CI: "1", - }, - stdio: "inherit", // Stream output directly to console - }, - ); - - console.log("Waiting for deploy process to complete..."); - await new Promise((resolve, reject) => { - deployProcess.on("exit", (code) => { - if (code === 0) { - resolve(undefined); - } else { - reject(new Error(`Deploy process exited with code ${code}`)); - } - }); - deployProcess.on("error", (err) => { - console.error("Deploy process error:", err); - reject(err); - }); - }); - console.log("Deploy process completed successfully"); - } - - // Get the endpoint URL - console.log("Getting Rivet endpoint..."); - - // // HACK: We have to get the endpoint of the actor directly since we can't route functions with hostnames on localhost yet - // const { actors } = await rivetClient.actors.list({ - // tagsJson: JSON.stringify({ - // type: "function", - // function: "manager", - // appName, - // }), - // project, - // environment, - // }); - // const managerActor = actors[0]; - // invariant(managerActor, "missing manager actor"); - // const endpoint = managerActor.network.ports.http?.url; - // invariant(endpoint, "missing manager actor endpoint"); - - // TODO: This doesn't work in local dev since we can't route functions on localhost yet - // Get the endpoint using the CLI endpoint command - console.log("Spawning rivet function endpoint command..."); - const endpointProcess = spawn( - "rivet", - ["function", "endpoint", "--environment", environment, "manager"], - { - cwd: tmpDir, - env: { - ...process.env, - CI: "1", - }, - stdio: ["inherit", "pipe", "inherit"], // Capture stdout - }, - ); - - // Capture the endpoint - let endpointOutput = ""; - endpointProcess.stdout.on("data", (data) => { - const output = data.toString(); - console.log(`Endpoint output: ${output}`); - endpointOutput += output; - }); - - // Wait for endpoint command to complete - console.log("Waiting for endpoint process to complete..."); - await new Promise((resolve, reject) => { - endpointProcess.on("exit", (code) => { - console.log(`Endpoint process exited with code: ${code}`); - if (code === 0) { - resolve(undefined); - } else { - reject(new Error(`Endpoint command failed with code ${code}`)); - } - }); - endpointProcess.on("error", (err) => { - console.error("Endpoint process error:", err); - reject(err); - }); - }); - - invariant(endpointOutput, "endpoint command returned empty output"); - console.log(`Raw endpoint output: ${endpointOutput}`); - - // Look for something that looks like a URL in the string - const lines = endpointOutput.trim().split("\n"); - const endpoint = lines[lines.length - 1]; - invariant(endpoint, "endpoint not found"); - - console.log("Manager endpoint", endpoint); - - console.log("=== END deployToRivet ==="); - - return endpoint; -} diff --git a/packages/platforms/rivet/tsconfig.json b/packages/platforms/rivet/tsconfig.json deleted file mode 100644 index 666d38420..000000000 --- a/packages/platforms/rivet/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "types": ["node"], - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["src/**/*"] -} diff --git a/packages/platforms/rivet/tsup.config.ts b/packages/platforms/rivet/tsup.config.ts deleted file mode 100644 index 677cffb7b..000000000 --- a/packages/platforms/rivet/tsup.config.ts +++ /dev/null @@ -1,4 +0,0 @@ -import defaultConfig from "../../../tsup.base.ts"; -import { defineConfig } from "tsup"; - -export default defineConfig(defaultConfig); diff --git a/packages/platforms/rivet/turbo.json b/packages/platforms/rivet/turbo.json deleted file mode 100644 index ecc7d8f3b..000000000 --- a/packages/platforms/rivet/turbo.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "extends": ["//"], - "tasks": { - "test": { - "dependsOn": ["^build", "check-types", "build"], - "env": [ - "RIVET_ENDPOINT", - "RIVET_CLOUD_TOKEN", - "RIVET_PROJECT", - "RIVET_ENVIRONMENT", - "_RIVET_SKIP_DEPLOY" - ] - } - } -} diff --git a/packages/platforms/rivet/vitest.config.ts b/packages/platforms/rivet/vitest.config.ts deleted file mode 100644 index 62f33085a..000000000 --- a/packages/platforms/rivet/vitest.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - globals: true, - environment: "node", - testTimeout: 120_000, - hookTimeout: 120_000, - }, -}); diff --git a/packages/worker/package.json b/packages/worker/package.json index 12a7a1876..48b337b65 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,18 @@ { "name": "@rivetkit/worker", "version": "0.9.0-rc.1", - "keywords": ["rivetkit"], + "keywords": [ + "rivetkit", + "worker", + "stateful", + "actor", + "rpc", + "events", + "realtime", + "backend", + "scaling", + "distributed" + ], "files": ["src", "dist", "package.json"], "type": "module", "exports": { @@ -54,11 +65,21 @@ "types": "./dist/test.d.cts", "default": "./dist/test.cjs" } + }, + "./drivers/rivet": { + "import": { + "types": "./dist/drivers/rivet.d.ts", + "default": "./dist/drivers/rivet.js" + }, + "require": { + "types": "./dist/drivers/rivet.d.cts", + "default": "./dist/drivers/rivet.cjs" + } } }, "sideEffects": false, "scripts": { - "build": "tsup src/mod.ts src/client.ts src/log.ts src/errors.ts src/test.ts", + "build": "tsup src/mod.ts src/client.ts src/log.ts src/errors.ts src/test.ts src/drivers/rivet.ts", "check-types": "tsc --noEmit", "test": "vitest run" }, diff --git a/packages/worker/src/drivers/rivet.ts b/packages/worker/src/drivers/rivet.ts new file mode 100644 index 000000000..b2c8ca9ed --- /dev/null +++ b/packages/worker/src/drivers/rivet.ts @@ -0,0 +1 @@ +export * from "@rivetkit/core/drivers/rivet"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d12afb07..c1c53383e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,9 +54,6 @@ importers: '@hono/node-server': specifier: ^1.14.4 version: 1.14.4(hono@4.8.0) - '@rivetkit/memory': - specifier: workspace:0.9.0-rc.1 - version: link:../../packages/drivers/memory '@rivetkit/react': specifier: workspace:0.9.0-rc.1 version: link:../../packages/frameworks/react @@ -105,10 +102,6 @@ importers: version: 3.2.4(@types/node@22.15.32)(tsx@3.14.0)(yaml@2.8.0) examples/chat-room: - dependencies: - '@rivetkit/nodejs': - specifier: workspace:* - version: link:../../packages/platforms/nodejs devDependencies: '@rivetkit/worker': specifier: workspace:* @@ -158,10 +151,6 @@ importers: version: 3.114.9(@cloudflare/workers-types@4.20250619.0) examples/counter: - dependencies: - '@rivetkit/nodejs': - specifier: workspace:* - version: link:../../packages/platforms/nodejs devDependencies: '@rivetkit/worker': specifier: workspace:* @@ -181,9 +170,6 @@ importers: examples/elysia: dependencies: - '@rivetkit/memory': - specifier: workspace:0.9.0-rc.1 - version: link:../../packages/drivers/memory '@rivetkit/react': specifier: workspace:0.9.0-rc.1 version: link:../../packages/frameworks/react @@ -209,9 +195,6 @@ importers: examples/express: dependencies: - '@rivetkit/memory': - specifier: workspace:0.9.0-rc.1 - version: link:../../packages/drivers/memory '@rivetkit/react': specifier: workspace:0.9.0-rc.1 version: link:../../packages/frameworks/react @@ -246,9 +229,6 @@ importers: '@hono/node-server': specifier: ^1.14.4 version: 1.14.4(hono@4.8.0) - '@rivetkit/memory': - specifier: workspace:0.9.0-rc.1 - version: link:../../packages/drivers/memory hono: specifier: ^4.7.0 version: 4.8.0 @@ -271,9 +251,6 @@ importers: '@hono/node-server': specifier: ^1.14.4 version: 1.14.4(hono@4.8.0) - '@rivetkit/memory': - specifier: workspace:0.9.0-rc.1 - version: link:../../packages/drivers/memory '@rivetkit/react': specifier: workspace:0.9.0-rc.1 version: link:../../packages/frameworks/react @@ -318,36 +295,8 @@ importers: specifier: ^3.1.1 version: 3.2.4(@types/node@22.15.32)(tsx@3.14.0)(yaml@2.8.0) - examples/nodejs: - dependencies: - '@rivetkit/memory': - specifier: workspace:0.9.0-rc.1 - version: link:../../packages/drivers/memory - '@rivetkit/nodejs': - specifier: workspace:0.9.0-rc.1 - version: link:../../packages/platforms/nodejs - devDependencies: - '@rivetkit/worker': - specifier: workspace:* - version: link:../../packages/worker - '@types/node': - specifier: ^22.13.9 - version: 22.15.32 - tsx: - specifier: ^3.12.7 - version: 3.14.0 - typescript: - specifier: ^5.5.2 - version: 5.8.3 - examples/react: dependencies: - '@rivetkit/memory': - specifier: workspace:0.9.0-rc.1 - version: link:../../packages/drivers/memory - '@rivetkit/nodejs': - specifier: workspace:0.9.0-rc.1 - version: link:../../packages/platforms/nodejs '@rivetkit/react': specifier: workspace:0.9.0-rc.1 version: link:../../packages/frameworks/react @@ -391,12 +340,9 @@ importers: examples/rivet: dependencies: - '@rivetkit/rivet': - specifier: https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/rivet@65c3659 - version: https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/rivet@65c3659(rivetkit@https://pkg.pr.new/rivet-gg/rivetkit@65c3659(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2)) '@rivetkit/worker': - specifier: https://pkg.pr.new/rivet-gg/rivetkit@65c3659 - version: rivetkit@https://pkg.pr.new/rivet-gg/rivetkit@65c3659(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2) + specifier: https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/worker@38d8fca + version: https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/worker@38d8fca(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2) devDependencies: '@types/node': specifier: ^22.13.9 @@ -410,9 +356,6 @@ importers: examples/trpc: dependencies: - '@rivetkit/memory': - specifier: workspace:0.9.0-rc.1 - version: link:../../packages/drivers/memory '@trpc/client': specifier: ^11.3.1 version: 11.4.2(@trpc/server@11.4.2(typescript@5.8.3))(typescript@5.8.3) @@ -466,6 +409,9 @@ importers: '@hono/node-ws': specifier: ^1.1.1 version: 1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0) + '@rivet-gg/actor-core': + specifier: ^25.1.0 + version: 25.2.0 '@types/invariant': specifier: ^2 version: 2.2.37 @@ -531,28 +477,6 @@ importers: specifier: ^3.1.1 version: 3.2.4(@types/node@22.15.32)(@vitest/ui@3.1.1)(tsx@4.20.3)(yaml@2.8.0) - packages/drivers/memory: - dependencies: - '@types/node': - specifier: ^22.13.1 - version: 22.15.32 - hono: - specifier: ^4.7.0 - version: 4.8.0 - vitest: - specifier: ^3.1.1 - version: 3.2.4(@types/node@22.15.32)(@vitest/ui@3.1.1)(tsx@4.20.3)(yaml@2.8.0) - devDependencies: - '@rivetkit/core': - specifier: workspace:* - version: link:../../core - tsup: - specifier: ^8.4.0 - version: 8.5.0(@microsoft/api-extractor@7.52.8(@types/node@22.15.32))(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0) - typescript: - specifier: ^5.5.2 - version: 5.8.3 - packages/drivers/redis: dependencies: '@types/node': @@ -699,86 +623,6 @@ importers: specifier: ^3.101.0 version: 3.114.9(@cloudflare/workers-types@4.20250619.0) - packages/platforms/nodejs: - dependencies: - '@hono/node-server': - specifier: ^1.14.4 - version: 1.14.4(hono@4.8.0) - '@hono/node-ws': - specifier: ^1.0.8 - version: 1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0) - '@types/node': - specifier: ^24.0.3 - version: 24.0.3 - zod: - specifier: ^3.24.2 - version: 3.25.67 - devDependencies: - '@rivetkit/core': - specifier: workspace:* - version: link:../../core - '@rivetkit/file-system': - specifier: workspace:* - version: link:../../drivers/file-system - '@rivetkit/memory': - specifier: workspace:* - version: link:../../drivers/memory - hono: - specifier: ^4.7.0 - version: 4.8.0 - tsup: - specifier: ^8.4.0 - version: 8.5.0(@microsoft/api-extractor@7.52.8(@types/node@24.0.3))(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0) - typescript: - specifier: ^5.5.2 - version: 5.8.3 - - packages/platforms/rivet: - dependencies: - '@hono/node-server': - specifier: ^1.14.4 - version: 1.14.4(hono@4.8.0) - '@hono/node-ws': - specifier: ^1.1.7 - version: 1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0) - hono: - specifier: ^4.7.0 - version: 4.8.0 - invariant: - specifier: ^2.2.4 - version: 2.2.4 - zod: - specifier: ^3.24.2 - version: 3.25.67 - devDependencies: - '@rivet-gg/actor-core': - specifier: ^25.1.0 - version: 25.2.0 - '@rivet-gg/api': - specifier: ^25.4.2 - version: 25.4.2 - '@rivetkit/core': - specifier: workspace:* - version: link:../../core - '@types/deno': - specifier: ^2.0.0 - version: 2.3.0 - '@types/invariant': - specifier: ^2 - version: 2.2.37 - '@types/node': - specifier: ^22.13.1 - version: 22.15.32 - tsup: - specifier: ^8.4.0 - version: 8.5.0(@microsoft/api-extractor@7.52.8(@types/node@22.15.32))(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0) - typescript: - specifier: ^5.5.2 - version: 5.8.3 - vitest: - specifier: ^3.1.1 - version: 3.2.4(@types/node@22.15.32)(@vitest/ui@3.1.1)(tsx@4.20.3)(yaml@2.8.0) - packages/rivetkit: {} packages/worker: @@ -1858,14 +1702,28 @@ packages: '@rivet-gg/actor-core@25.2.0': resolution: {integrity: sha512-4K72XcDLVAz44Ae6G6GuyzWyxQZOLN8jM/W+sVKm6fHr70X8FNCSC5+/9hFIxz/OH9E6q6Wi3V/UN/k6immUBQ==} - '@rivet-gg/api@25.4.2': - resolution: {integrity: sha512-VmQ+1dfwWoDamF+49A+cB5htnjJbD4LzmQiaiycBS4ICqAiC3b9rW6l1UAyUfG32x02cSRRJ80yApBl54CdlqQ==} - - '@rivetkit/rivet@https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/rivet@65c3659': - resolution: {tarball: https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/rivet@65c3659} + '@rivetkit/core@https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/core@38d8fcab136be6bdbd976a802f93581384c12fb2': + resolution: {tarball: https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/core@38d8fcab136be6bdbd976a802f93581384c12fb2} version: 0.9.0-rc.1 + engines: {node: '>=22.0.0'} peerDependencies: - rivetkit: '*' + '@hono/node-server': ^1.14.0 + '@hono/node-ws': ^1.1.1 + eventsource: ^3.0.5 + ws: ^8.0.0 + peerDependenciesMeta: + '@hono/node-server': + optional: true + '@hono/node-ws': + optional: true + eventsource: + optional: true + ws: + optional: true + + '@rivetkit/worker@https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/worker@38d8fca': + resolution: {tarball: https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/worker@38d8fca} + version: 0.9.0-rc.1 '@rolldown/pluginutils@1.0.0-beta.11': resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==} @@ -2065,9 +1923,6 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/deno@2.3.0': - resolution: {integrity: sha512-/4SyefQpKjwNKGkq9qG3Ln7MazfbWKvydyVFBnXzP5OQA4u1paoFtaOe1iHKycIWHHkhYag0lPxyheThV1ijzw==} - '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -2225,10 +2080,6 @@ packages: '@vue/shared@3.5.17': resolution: {integrity: sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==} - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -2305,15 +2156,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - better-auth@1.2.10: resolution: {integrity: sha512-nEj1RG4DdLUuJiV5CR93ORyPCptGRBwksaPPCkUtGo9ka+UIlTpaiKoTaTqVLLYlqwX4bOj9tJ32oBNdf2G3Kg==} @@ -2348,9 +2193,6 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2428,10 +2270,6 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -2537,10 +2375,6 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -2616,10 +2450,6 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - esbuild@0.17.19: resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} engines: {node: '>=12'} @@ -2667,14 +2497,6 @@ packages: event-stream@3.3.4: resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - eventsource-parser@3.0.2: resolution: {integrity: sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==} engines: {node: '>=18.0.0'} @@ -2756,10 +2578,6 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - form-data@4.0.3: - resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} - engines: {node: '>= 6'} - formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -2847,10 +2665,6 @@ packages: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -2942,9 +2756,6 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - js-base64@3.7.7: - resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3105,18 +2916,10 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - mime-types@3.0.1: resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} engines: {node: '>= 0.6'} @@ -3188,15 +2991,6 @@ packages: engines: {node: '>=10.5.0'} deprecated: Use your platform's native DOMException instead - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - node-fetch@3.3.1: resolution: {integrity: sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3334,10 +3128,6 @@ packages: printable-characters@1.0.42: resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -3402,10 +3192,6 @@ packages: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} - readable-stream@4.7.0: - resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -3446,25 +3232,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rivetkit@https://pkg.pr.new/rivet-gg/rivetkit@65c3659: - resolution: {tarball: https://pkg.pr.new/rivet-gg/rivetkit@65c3659} - version: 0.9.0-rc.1 - engines: {node: '>=22.0.0'} - peerDependencies: - '@hono/node-server': ^1.14.0 - '@hono/node-ws': ^1.1.1 - eventsource: ^3.0.5 - ws: ^8.0.0 - peerDependenciesMeta: - '@hono/node-server': - optional: true - '@hono/node-ws': - optional: true - eventsource: - optional: true - ws: - optional: true - rollup-plugin-inject@3.0.2: resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==} deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject. @@ -3653,9 +3420,6 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3740,9 +3504,6 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} @@ -3878,10 +3639,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - url-join@5.0.0: - resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - use-sync-external-store@1.5.0: resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} peerDependencies: @@ -4011,9 +3768,6 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -4021,9 +3775,6 @@ packages: resolution: {integrity: sha512-cSwwQIeg8v4i3p4ajHhwgR7N6VyxAf+KYSSsY6Pd3aETE+xEU4vbitz7qQkB0I321xnhDdgtxuiSfk5r/FVtjg==} hasBin: true - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} @@ -4951,28 +4702,29 @@ snapshots: dependencies: zod: 3.25.67 - '@rivet-gg/api@25.4.2': + '@rivetkit/core@https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/core@38d8fcab136be6bdbd976a802f93581384c12fb2(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2)': dependencies: - form-data: 4.0.3 - js-base64: 3.7.7 - node-fetch: 2.7.0 - qs: 6.14.0 - readable-stream: 4.7.0 - url-join: 5.0.0 - transitivePeerDependencies: - - encoding - - '@rivetkit/rivet@https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/rivet@65c3659(rivetkit@https://pkg.pr.new/rivet-gg/rivetkit@65c3659(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2))': - dependencies: - '@hono/node-server': 1.14.4(hono@4.8.0) - '@hono/node-ws': 1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0) + '@hono/zod-openapi': 0.19.8(hono@4.8.0)(zod@3.25.67) + cbor-x: 1.6.0 hono: 4.8.0 invariant: 2.2.4 - rivetkit: https://pkg.pr.new/rivet-gg/rivetkit@65c3659(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2) + on-change: 5.0.1 + p-retry: 6.2.1 zod: 3.25.67 + optionalDependencies: + '@hono/node-server': 1.14.4(hono@4.8.0) + '@hono/node-ws': 1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0) + eventsource: 3.0.7 + ws: 8.18.2 + + '@rivetkit/worker@https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/worker@38d8fca(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2)': + dependencies: + '@rivetkit/core': https://pkg.pr.new/rivet-gg/rivetkit/@rivetkit/core@38d8fcab136be6bdbd976a802f93581384c12fb2(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2) transitivePeerDependencies: - - bufferutil - - utf-8-validate + - '@hono/node-server' + - '@hono/node-ws' + - eventsource + - ws '@rolldown/pluginutils@1.0.0-beta.11': {} @@ -5191,8 +4943,6 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/deno@2.3.0': {} - '@types/estree@1.0.8': {} '@types/express-serve-static-core@4.19.6': @@ -5415,10 +5165,6 @@ snapshots: '@vue/shared@3.5.17': {} - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - accepts@2.0.0: dependencies: mime-types: 3.0.1 @@ -5482,12 +5228,8 @@ snapshots: assertion-error@2.0.1: {} - asynckit@0.4.0: {} - balanced-match@1.0.2: {} - base64-js@1.5.1: {} - better-auth@1.2.10: dependencies: '@better-auth/utils': 0.2.5 @@ -5550,11 +5292,6 @@ snapshots: buffer-from@1.1.2: {} - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - bundle-require@5.1.0(esbuild@0.25.5): dependencies: esbuild: 0.25.5 @@ -5641,10 +5378,6 @@ snapshots: color-string: 1.9.1 optional: true - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - commander@4.1.1: {} compare-versions@6.1.1: {} @@ -5717,8 +5450,6 @@ snapshots: defu@6.1.4: {} - delayed-stream@1.0.0: {} - denque@2.1.0: {} depd@2.0.0: {} @@ -5777,13 +5508,6 @@ snapshots: dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - esbuild@0.17.19: optionalDependencies: '@esbuild/android-arm': 0.17.19 @@ -5914,10 +5638,6 @@ snapshots: stream-combiner: 0.0.4 through: 2.3.8 - event-target-shim@5.0.1: {} - - events@3.3.0: {} - eventsource-parser@3.0.2: {} eventsource@3.0.7: @@ -6030,14 +5750,6 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.3: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -6125,10 +5837,6 @@ snapshots: has-symbols@1.1.0: {} - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -6212,8 +5920,6 @@ snapshots: joycon@3.1.1: {} - js-base64@3.7.7: {} - js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -6338,14 +6044,8 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: {} - mime-db@1.54.0: {} - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - mime-types@3.0.1: dependencies: mime-db: 1.54.0 @@ -6427,10 +6127,6 @@ snapshots: node-domexception@1.0.0: {} - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - node-fetch@3.3.1: dependencies: data-uri-to-buffer: 4.0.1 @@ -6542,8 +6238,6 @@ snapshots: printable-characters@1.0.42: {} - process@0.11.10: {} - prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -6602,14 +6296,6 @@ snapshots: react@19.1.0: {} - readable-stream@4.7.0: - dependencies: - abort-controller: 3.0.0 - buffer: 6.0.3 - events: 3.3.0 - process: 0.11.10 - string_decoder: 1.3.0 - readdirp@4.1.2: {} redis-errors@1.2.0: {} @@ -6636,21 +6322,6 @@ snapshots: reusify@1.1.0: {} - rivetkit@https://pkg.pr.new/rivet-gg/rivetkit@65c3659(@hono/node-server@1.14.4(hono@4.8.0))(@hono/node-ws@1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0))(eventsource@3.0.7)(ws@8.18.2): - dependencies: - '@hono/zod-openapi': 0.19.8(hono@4.8.0)(zod@3.25.67) - cbor-x: 1.6.0 - hono: 4.8.0 - invariant: 2.2.4 - on-change: 5.0.1 - p-retry: 6.2.1 - zod: 3.25.67 - optionalDependencies: - '@hono/node-server': 1.14.4(hono@4.8.0) - '@hono/node-ws': 1.1.7(@hono/node-server@1.14.4(hono@4.8.0))(hono@4.8.0) - eventsource: 3.0.7 - ws: 8.18.2 - rollup-plugin-inject@3.0.2: dependencies: estree-walker: 0.6.1 @@ -6898,10 +6569,6 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -6978,8 +6645,6 @@ snapshots: totalist@3.0.1: {} - tr46@0.0.3: {} - tr46@1.0.1: dependencies: punycode: 2.3.1 @@ -7146,8 +6811,6 @@ snapshots: dependencies: punycode: 2.3.1 - url-join@5.0.0: {} - use-sync-external-store@1.5.0(react@19.1.0): dependencies: react: 19.1.0 @@ -7415,17 +7078,10 @@ snapshots: web-streams-polyfill@3.3.3: {} - webidl-conversions@3.0.1: {} - webidl-conversions@4.0.2: {} webpod@0.0.2: {} - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - whatwg-url@7.1.0: dependencies: lodash.sortby: 4.7.0