From 65754e21b5be11643f0008df0e55cfda16dc1e10 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Thu, 19 Jun 2025 21:37:52 +0000 Subject: [PATCH] chore: implement `Registry.run` --- CLAUDE.md | 96 ++- docs/workers/quickstart.mdx | 1 - examples/chat-room-python/.gitignore | 2 - examples/chat-room-python/requirements.txt | 4 - examples/chat-room-python/scripts/cli.py | 52 -- examples/chat-room-python/scripts/connect.py | 30 - examples/chat-room-python/src/server.ts | 4 - .../chat-room-python/src/workers/registry.ts | 40 - .../chat-room-python/tests/test_chat_room.py | 66 -- examples/chat-room/README.md | 37 + examples/counter/README.md | 37 + examples/elysia/README.md | 33 + examples/elysia/package.json | 26 + examples/elysia/src/registry.ts | 20 + examples/elysia/src/server.ts | 25 + examples/elysia/tsconfig.json | 43 + examples/express/README.md | 33 + examples/express/package.json | 28 + examples/express/src/registry.ts | 20 + examples/express/src/server.ts | 28 + .../tsconfig.json | 2 +- examples/hono-react/README.md | 33 + examples/hono-react/package.json | 40 + examples/hono-react/src/backend/registry.ts | 21 + examples/hono-react/src/backend/server.ts | 33 + examples/hono-react/src/frontend/App.tsx | 39 + examples/hono-react/src/frontend/index.html | 12 + examples/hono-react/src/frontend/main.tsx | 9 + examples/hono-react/tsconfig.json | 43 + examples/hono-react/vite.config.ts | 13 + examples/hono/README.md | 33 + examples/hono/package.json | 25 + examples/hono/src/registry.ts | 20 + examples/hono/src/server.ts | 29 + .../public => examples/hono}/tsconfig.json | 4 +- examples/linear-coding-agent/README.md | 203 +---- examples/nodejs/README.md | 33 + .../{chat-room-python => nodejs}/package.json | 14 +- examples/nodejs/src/registry.ts | 20 + examples/nodejs/src/server.ts | 7 + examples/nodejs/tsconfig.json | 43 + examples/react/README.md | 33 + examples/react/package.json | 37 + examples/react/src/backend/registry.ts | 21 + examples/react/src/backend/server.ts | 11 + examples/react/src/frontend/App.tsx | 37 + examples/react/src/frontend/index.html | 12 + examples/react/src/frontend/main.tsx | 9 + examples/react/tsconfig.json | 43 + examples/react/vite.config.ts | 13 + examples/resend-streaks/README.md | 34 + examples/snippets/README.md | 36 +- examples/trpc/README.md | 37 + examples/trpc/package.json | 27 + examples/trpc/scripts/client.ts | 34 + examples/trpc/src/registry.ts | 20 + examples/trpc/src/server.ts | 37 + examples/trpc/tsconfig.json | 41 + package.json | 2 + packages/core/README.md | 23 +- packages/core/package.json | 27 +- packages/core/scripts/dump-openapi.ts | 19 +- .../core/src/client/http-client-driver.ts | 3 +- packages/core/src/common/utils.ts | 5 +- packages/core/src/common/websocket.ts | 5 +- packages/core/src/driver-helpers/config.ts | 37 - packages/core/src/driver-helpers/mod.ts | 2 +- packages/core/src/driver-test-suite/mod.ts | 45 +- .../test-inline-client-driver.ts | 3 +- .../tests/worker-conn-state.ts | 48 +- packages/core/src/globals.d.ts | 7 +- packages/core/src/inline-client-driver/mod.ts | 3 +- packages/core/src/manager/router.ts | 92 ++- packages/core/src/registry/config.ts | 64 +- packages/core/src/registry/mod.ts | 60 +- packages/core/src/registry/run-config.ts | 78 ++ packages/core/src/test/config.ts | 6 +- packages/core/src/test/driver/manager.ts | 2 - packages/core/src/test/driver/mod.ts | 14 + packages/core/src/test/mod.ts | 28 +- .../src/topologies/coordinate/conn/mod.ts | 14 +- .../src/topologies/coordinate/node/message.ts | 10 +- .../src/topologies/coordinate/router/sse.ts | 6 +- .../topologies/coordinate/router/websocket.ts | 8 +- .../src/topologies/coordinate/topology.ts | 22 +- .../src/topologies/coordinate/worker-peer.ts | 26 +- .../core/src/topologies/partition/topology.ts | 19 +- .../src/topologies/partition/worker-router.ts | 36 +- .../src/topologies/standalone/topology.ts | 89 ++- .../core/src/worker/conn-routing-handler.ts | 1 - packages/core/src/worker/router-endpoints.ts | 18 +- packages/core/tests/driver-test-suite.test.ts | 19 +- packages/drivers/file-system/README.md | 11 + packages/drivers/file-system/package.json | 1 + packages/drivers/memory/README.md | 11 + packages/drivers/memory/package.json | 1 + packages/drivers/memory/src/manager.ts | 6 +- packages/drivers/memory/src/mod.ts | 16 +- .../drivers/memory/tests/driver-tests.test.ts | 13 +- packages/drivers/redis/README.md | 11 + packages/drivers/redis/package.json | 1 + packages/frameworks/framework-base/README.md | 11 + packages/frameworks/framework-base/lib/mod.ts | 91 ++- .../frameworks/framework-base/package.json | 1 + .../frameworks/framework-base/src/main.ts | 1 - packages/frameworks/framework-base/src/mod.ts | 148 ---- .../frameworks/framework-base/vite.config.ts | 2 +- packages/frameworks/react/README.md | 82 +- packages/frameworks/react/lib/mod.tsx | 82 -- packages/frameworks/react/lib/vite-env.d.ts | 1 - packages/frameworks/react/package.json | 16 +- packages/frameworks/react/src/App.tsx | 96 --- packages/frameworks/react/src/main.tsx | 9 - packages/frameworks/react/src/mod.tsx | 159 ++-- packages/frameworks/react/src/vite-env.d.ts | 1 - packages/frameworks/react/tsconfig.build.json | 4 - packages/frameworks/react/tsconfig.json | 3 +- packages/frameworks/react/tsconfig.node.json | 10 - packages/frameworks/react/tsup.config.ts | 4 + packages/frameworks/react/vite.config.ts | 19 - packages/misc/docs-middleware/README.md | 11 + packages/misc/docs-middleware/package.json | 1 + packages/platforms/bun/README.md | 12 +- packages/platforms/bun/package.json | 1 + packages/platforms/bun/src/mod.ts | 18 +- .../platforms/cloudflare-workers/README.md | 12 +- .../platforms/cloudflare-workers/package.json | 1 + .../cloudflare-workers/src/handler.ts | 6 +- .../src/worker-handler-do.ts | 4 +- packages/platforms/nodejs/README.md | 13 - packages/platforms/nodejs/package.json | 14 +- packages/platforms/nodejs/src/config.ts | 15 - packages/platforms/nodejs/src/mod.ts | 119 +-- packages/platforms/nodejs/tsconfig.json | 2 +- packages/platforms/rivet/README.md | 12 +- packages/platforms/rivet/package.json | 1 + packages/platforms/rivet/src/manager.ts | 4 +- packages/platforms/rivet/src/worker.ts | 4 +- pnpm-lock.yaml | 747 +++++++++++++++++- tsup.base.ts | 7 +- 140 files changed, 2852 insertions(+), 1587 deletions(-) delete mode 100644 examples/chat-room-python/.gitignore delete mode 100644 examples/chat-room-python/requirements.txt delete mode 100644 examples/chat-room-python/scripts/cli.py delete mode 100644 examples/chat-room-python/scripts/connect.py delete mode 100644 examples/chat-room-python/src/server.ts delete mode 100644 examples/chat-room-python/src/workers/registry.ts delete mode 100644 examples/chat-room-python/tests/test_chat_room.py create mode 100644 examples/chat-room/README.md create mode 100644 examples/counter/README.md create mode 100644 examples/elysia/README.md create mode 100644 examples/elysia/package.json create mode 100644 examples/elysia/src/registry.ts create mode 100644 examples/elysia/src/server.ts create mode 100644 examples/elysia/tsconfig.json create mode 100644 examples/express/README.md create mode 100644 examples/express/package.json create mode 100644 examples/express/src/registry.ts create mode 100644 examples/express/src/server.ts rename examples/{chat-room-python => express}/tsconfig.json (96%) create mode 100644 examples/hono-react/README.md create mode 100644 examples/hono-react/package.json create mode 100644 examples/hono-react/src/backend/registry.ts create mode 100644 examples/hono-react/src/backend/server.ts create mode 100644 examples/hono-react/src/frontend/App.tsx create mode 100644 examples/hono-react/src/frontend/index.html create mode 100644 examples/hono-react/src/frontend/main.tsx create mode 100644 examples/hono-react/tsconfig.json create mode 100644 examples/hono-react/vite.config.ts create mode 100644 examples/hono/README.md create mode 100644 examples/hono/package.json create mode 100644 examples/hono/src/registry.ts create mode 100644 examples/hono/src/server.ts rename {packages/platforms/nodejs/public => examples/hono}/tsconfig.json (96%) create mode 100644 examples/nodejs/README.md rename examples/{chat-room-python => nodejs}/package.json (57%) create mode 100644 examples/nodejs/src/registry.ts create mode 100644 examples/nodejs/src/server.ts create mode 100644 examples/nodejs/tsconfig.json create mode 100644 examples/react/README.md create mode 100644 examples/react/package.json create mode 100644 examples/react/src/backend/registry.ts create mode 100644 examples/react/src/backend/server.ts create mode 100644 examples/react/src/frontend/App.tsx create mode 100644 examples/react/src/frontend/index.html create mode 100644 examples/react/src/frontend/main.tsx create mode 100644 examples/react/tsconfig.json create mode 100644 examples/react/vite.config.ts create mode 100644 examples/resend-streaks/README.md create mode 100644 examples/trpc/README.md create mode 100644 examples/trpc/package.json create mode 100644 examples/trpc/scripts/client.ts create mode 100644 examples/trpc/src/registry.ts create mode 100644 examples/trpc/src/server.ts create mode 100644 examples/trpc/tsconfig.json delete mode 100644 packages/core/src/driver-helpers/config.ts create mode 100644 packages/core/src/registry/run-config.ts create mode 100644 packages/drivers/file-system/README.md create mode 100644 packages/drivers/memory/README.md create mode 100644 packages/drivers/redis/README.md create mode 100644 packages/frameworks/framework-base/README.md delete mode 100644 packages/frameworks/framework-base/src/main.ts delete mode 100644 packages/frameworks/framework-base/src/mod.ts delete mode 100644 packages/frameworks/react/lib/mod.tsx delete mode 100644 packages/frameworks/react/lib/vite-env.d.ts delete mode 100644 packages/frameworks/react/src/App.tsx delete mode 100644 packages/frameworks/react/src/main.tsx delete mode 100644 packages/frameworks/react/src/vite-env.d.ts delete mode 100644 packages/frameworks/react/tsconfig.build.json delete mode 100644 packages/frameworks/react/tsconfig.node.json create mode 100644 packages/frameworks/react/tsup.config.ts delete mode 100644 packages/frameworks/react/vite.config.ts create mode 100644 packages/misc/docs-middleware/README.md delete mode 100644 packages/platforms/nodejs/README.md delete mode 100644 packages/platforms/nodejs/src/config.ts diff --git a/CLAUDE.md b/CLAUDE.md index 2d71f27b3..2eeda6c49 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,6 +5,29 @@ - Use `RivetKit` when referring to the project in documentation and plain English - Use `rivetkit` when referring to the project in code, package names, and imports +## `packages/**/package.json` + +- Always include relevant keywords for the packages + +## `packages/**/README.md` + +Always include a README.md for new packages. The `README.md` should always follow this structure: + + ```md + # RivetKit {subname, e.g. library: RivetKit Workers, driver and platform: RivetKit Redis Adapter, RivetKit Cloudflare Workers Adapter} + + _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 + ``` + + ## Common Terminology - **Worker**: A stateful, long-lived entity that processes messages and maintains state @@ -20,12 +43,14 @@ ## Build Commands +Run these commands from the root of the project. They depend on Turborepo, so you cannot run the commands within the package itself. Running these commands are important in order to ensure that all dependencies are automatically built. + - **Type Check:** `pnpm check-types` - Verify TypeScript types - **Check specific package:** `pnpm check-types -F rivetkit` - Check only specified package - **Build:** `pnpm build` - Production build using Turbopack - **Build specific package:** `pnpm build -F rivetkit` - Build only specified package - **Format:** `pnpm fmt` - Format code with Biome -- Do not run the format command automatically. + - Do not run the format command automatically. ## Core Concepts @@ -44,6 +69,7 @@ Driver interfaces define the contract between rivetkit and various backends: - **WorkerDriver:** Manages worker state, lifecycle, and persistence - **ManagerDriver:** Manages worker discovery, routing, and scaling - **CoordinateDriver:** Handles peer-to-peer communication between worker instances + - Only applicable in coordinate topologies ### Driver Implementations @@ -75,6 +101,7 @@ This ensures imports resolve correctly across different build environments and p ## Code Style Guidelines - **Formatting:** Uses Biome for consistent formatting + - See biome.json for reference on formatting rules - **Imports:** Organized imports enforced, unused imports warned - **TypeScript:** Strict mode enabled, target ESNext - **Naming:** @@ -83,26 +110,28 @@ This ensures imports resolve correctly across different build environments and p - UPPER_CASE for constants - Use `#` prefix for private class members (not `private` keyword) - **Error Handling:** - - Extend from `WorkerError` base class + - Extend from `WorkerError` base class (packages/core/src/worker/errors.ts) - Use `UserError` for client-safe errors - Use `InternalError` for internal errors - Don't try to fix type issues by casting to unknown or any. If you need to do this, then stop and ask me to manually intervene. - Write log messages in lowercase -- Instead of returning raw HTTP responses with c.json, use or write an error in packages/rivetkit/src/worker/errors.ts and throw that instead. The middleware will automatically serialize the response for you. +- Use `logger()` to log messages + - Do not store `logger()` as a variable, always call it using `logger().info("...")` + - Use structured logging where it makes sense, for example: `logger().info("foo", { bar: 5, baz: 10 })` + - Supported logging methods are: trace, debug, info, warn, error, critical +- Instead of returning errors as raw HTTP responses with c.json, use or write an error in packages/rivetkit/src/worker/errors.ts and throw that instead. The middleware will automatically serialize the response for you. ## Project Structure - Monorepo with pnpm workspaces and Turborepo -- Core code in `packages/rivetkit/` +- Core code in `packages/core/` - Platform implementations in `packages/platforms/` - Driver implementations in `packages/drivers/` ## Development Notes -- Prefer classes over factory functions - Use zod for runtime type validation - Use `assertUnreachable(x: never)` for exhaustive type checking in switch statements -- Follow existing patterns for P2P networking - Add proper JSDoc comments for public APIs - Ensure proper error handling with descriptive messages - Run `pnpm check-types` regularly during development to catch type errors early. Prefer `pnpm check-types` instead of `pnpm build`. @@ -112,3 +141,58 @@ This ensures imports resolve correctly across different build environments and p ## Test Guidelines - Do not check if errors are an instanceOf WorkerError in tests. Many error types do not have the same prototype chain when sent over the network, but still have the same properties so you can safely cast with `as`. + +## Examples + +Examples live in the `examples/` folder. + +### `examples/*/package.json` + +- Always name the example `example-{name}` +- Always use `workspace:*` for dependencies +- Use `tsx` unless otherwise instructed +- Always have a `dev` and `check-types` scripts + - `dev` should use `tsx --watch` unless otherwise instructed + - `check-types` should use `tsc --noEmit` + +### `examples/*/README.md` + +Always include a README.md. The `README.md` should always follow this structure: + + ```md + # {human readable title} for RivetKit + + Example project demonstrating {specific feature} 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 or bun based on demo} + - {any other related services if this integrates with external SaaS} + + ### Installation + + ```sh + git clone https://github.com/rivet-gg/rivetkit + cd rivetkit/examples/{name} + npm install + ``` + + ### Development + + ```sh + npm run dev + ``` + + {instructions to either open browser or run script to test it} + + ## License + + Apache 2.0 + ``` + diff --git a/docs/workers/quickstart.mdx b/docs/workers/quickstart.mdx index e8837c446..a74a812d1 100644 --- a/docs/workers/quickstart.mdx +++ b/docs/workers/quickstart.mdx @@ -30,7 +30,6 @@ const myRealtime = realtime({ const registry = setup({ registry: { counter, myWorkflow, myRealtime }, - maxConnParamLength: 123 }); export type Registry = typeof registry; diff --git a/examples/chat-room-python/.gitignore b/examples/chat-room-python/.gitignore deleted file mode 100644 index 79b7a1192..000000000 --- a/examples/chat-room-python/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.actorcore -node_modules \ No newline at end of file diff --git a/examples/chat-room-python/requirements.txt b/examples/chat-room-python/requirements.txt deleted file mode 100644 index 075e3830a..000000000 --- a/examples/chat-room-python/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -rivetkit-client>=0.8.0 -prompt_toolkit>=3.0.0 -pytest>=7.0.0 -pytest-asyncio>=0.21.0 diff --git a/examples/chat-room-python/scripts/cli.py b/examples/chat-room-python/scripts/cli.py deleted file mode 100644 index 73cb7e920..000000000 --- a/examples/chat-room-python/scripts/cli.py +++ /dev/null @@ -1,52 +0,0 @@ -import asyncio -from rivetkit_client import AsyncClient as ActorClient -import prompt_toolkit -from prompt_toolkit.patch_stdout import patch_stdout -from typing import TypedDict, List - -async def init_prompt() -> tuple[str, str]: - username = await prompt_toolkit.prompt_async("Username: ") - room = await prompt_toolkit.prompt_async("Room: ") - return username, room - -async def main(): - # Get username and room - username, room = await init_prompt() - print(f"Joining room '{room}' as '{username}'") - - # Create client and connect to chat room - client = ActorClient("http://localhost:6420") - chat_room = await client.get("chatRoom", tags={"room": room}, params={"room": room}) - - # Get and display history - history = await chat_room.action("getHistory", []) - if history: - print("\nHistory:") - for msg in history: - print(f"[{msg['username']}] {msg['message']}") - - # Set up message handler - def on_message(username: str, message: str): - print(f"\n[{username}] {message}") - - chat_room.on_event("newMessage", on_message) - - # Main message loop - print("\nStart typing messages (press Ctrl+D or send empty message to exit)") - try: - with patch_stdout(): - while True: - # NOTE: Using prompt_toolkit to keep messages - # intact, regardless of other threads / tasks. - message = await prompt_toolkit.prompt_async("\nMessage: ") - if not message: - break - await chat_room.action("sendMessage", [username, message]) - except EOFError: - pass - finally: - print("\nDisconnecting...") - await chat_room.disconnect() - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/examples/chat-room-python/scripts/connect.py b/examples/chat-room-python/scripts/connect.py deleted file mode 100644 index b0a6fe38d..000000000 --- a/examples/chat-room-python/scripts/connect.py +++ /dev/null @@ -1,30 +0,0 @@ -import asyncio -import os -from rivetkit_client import AsyncClient as ActorClient - -async def main(): - # Create client - endpoint = os.getenv("ENDPOINT", "http://localhost:6420") - client = ActorClient(endpoint) - - # Connect to chat room - chat_room = await client.get("chatRoom") - - # Get existing messages - messages = await chat_room.action("getHistory", []) - print("Messages:", messages) - - # Listen for new messages - def on_message(username: str, message: str): - print(f"Message from {username}: {message}") - - chat_room.on_event("newMessage", on_message) - - # Send message to room - await chat_room.action("sendMessage", ["william", "All the world's a stage."]) - - # Disconnect from actor when finished - await chat_room.disconnect() - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/examples/chat-room-python/src/server.ts b/examples/chat-room-python/src/server.ts deleted file mode 100644 index 4bf6ba53b..000000000 --- a/examples/chat-room-python/src/server.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { serve } from "@rivetkit/nodejs"; -import { registry } from "./workers/registry"; - -serve(registry); diff --git a/examples/chat-room-python/src/workers/registry.ts b/examples/chat-room-python/src/workers/registry.ts deleted file mode 100644 index a7a5bafef..000000000 --- a/examples/chat-room-python/src/workers/registry.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { worker, setup } from "rivetkit"; - -// state managed by the actor -export interface State { - messages: { username: string; message: string }[]; -} - -export const chatRoom = worker({ - // initialize state - state: { messages: [] } as State, - - // define actions - actions: { - // receive an action call from the client - sendMessage: (c, username: string, message: string) => { - // save message to persistent storage - c.state.messages.push({ username, message }); - - // broadcast message to all clients - c.broadcast("newMessage", username, message); - }, - - getHistory: (c) => { - return c.state.messages; - }, - }, -}); - -// Create and export the app -export const registry = setup({ - workers: { chatRoom }, - cors: { - origin: "*", // Allow all origins - allowMethods: ["GET", "POST", "OPTIONS"], // Allow specific methods - allowHeaders: ["Content-Type", "Authorization", "User-Agent"], // Allow specific headers - }, -}); - -// Export type for client type checking -export type Registry = typeof registry; diff --git a/examples/chat-room-python/tests/test_chat_room.py b/examples/chat-room-python/tests/test_chat_room.py deleted file mode 100644 index 5134c3dc6..000000000 --- a/examples/chat-room-python/tests/test_chat_room.py +++ /dev/null @@ -1,66 +0,0 @@ -import pytest -from rivetkit_client import AsyncClient as ActorClient -from rivetkit_test import setup_test -from typing import TypedDict, List - - -async def test_chat_room_should_handle_messages(): - # Set up test environment - client = await setup_test() - - # Connect to chat room - chat_room = await client.get("chatRoom") - - # Initial history should be empty - initial_messages = await chat_room.action("getHistory", []) - assert initial_messages == [] - - # Test event emission - received_data = {"username": "", "message": ""} - - def on_message(username: str, message: str): - received_data["username"] = username - received_data["message"] = message - - chat_room.on_event("newMessage", on_message) - - # Send a message - test_user = "william" - test_message = "All the world's a stage." - await chat_room.action("sendMessage", [test_user, test_message]) - - # Verify event was emitted with correct data - assert received_data["username"] == test_user - assert received_data["message"] == test_message - - # Verify message was stored in history - updated_messages = await chat_room.action("getHistory", []) - assert updated_messages == [{"username": test_user, "message": test_message}] - - # Send multiple messages and verify - users = ["romeo", "juliet", "othello"] - messages = [ - "Wherefore art thou?", - "Here I am!", - "The green-eyed monster." - ] - - for i in range(len(users)): - await chat_room.action("sendMessage", [users[i], messages[i]]) - - # Verify event emission - assert received_data["username"] == users[i] - assert received_data["message"] == messages[i] - - # Verify all messages are in history in correct order - final_history = await chat_room.action("getHistory", []) - expected_history = [{"username": test_user, "message": test_message}] - expected_history.extend([ - {"username": users[i], "message": messages[i]} - for i in range(len(users)) - ]) - - assert final_history == expected_history - - # Cleanup - await chat_room.disconnect() diff --git a/examples/chat-room/README.md b/examples/chat-room/README.md new file mode 100644 index 000000000..e7f7476d3 --- /dev/null +++ b/examples/chat-room/README.md @@ -0,0 +1,37 @@ +# Chat Room for RivetKit + +Example project demonstrating real-time messaging and worker state management 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/chat-room +npm install +``` + +### Development + +```sh +npm run dev +``` + +Run the connect script to interact with the chat room: + +```sh +tsx scripts/connect.ts +``` + +## License + +Apache 2.0 \ No newline at end of file diff --git a/examples/counter/README.md b/examples/counter/README.md new file mode 100644 index 000000000..bcc6a476b --- /dev/null +++ b/examples/counter/README.md @@ -0,0 +1,37 @@ +# Counter for RivetKit + +Example project demonstrating basic worker state management and RPC calls 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/counter +npm install +``` + +### Development + +```sh +npm run dev +``` + +Run the connect script to interact with the counter: + +```sh +tsx scripts/connect.ts +``` + +## License + +Apache 2.0 \ No newline at end of file diff --git a/examples/elysia/README.md b/examples/elysia/README.md new file mode 100644 index 000000000..2ac4fd51a --- /dev/null +++ b/examples/elysia/README.md @@ -0,0 +1,33 @@ +# Elysia Integration for RivetKit + +Example project demonstrating Elysia 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 + +- Bun + +### Installation + +```sh +git clone https://github.com/rivet-gg/rivetkit +cd rivetkit/examples/elysia +npm install +``` + +### Development + +```sh +npm run dev +``` + +Open your browser to http://localhost:3000 to see the Elysia server with RivetKit integration. + +## License + +Apache 2.0 \ No newline at end of file diff --git a/examples/elysia/package.json b/examples/elysia/package.json new file mode 100644 index 000000000..7ad7df28e --- /dev/null +++ b/examples/elysia/package.json @@ -0,0 +1,26 @@ +{ + "name": "example-elysia", + "version": "0.9.0-rc.1", + "private": true, + "type": "module", + "scripts": { + "dev": "bun --watch src/server.ts", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@types/node": "^22.13.9", + "rivetkit": "workspace:*", + "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" +} \ No newline at end of file diff --git a/examples/elysia/src/registry.ts b/examples/elysia/src/registry.ts new file mode 100644 index 000000000..11f62e9b9 --- /dev/null +++ b/examples/elysia/src/registry.ts @@ -0,0 +1,20 @@ +import { worker, setup } from "rivetkit"; + +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; \ No newline at end of file diff --git a/examples/elysia/src/server.ts b/examples/elysia/src/server.ts new file mode 100644 index 000000000..e3eb13286 --- /dev/null +++ b/examples/elysia/src/server.ts @@ -0,0 +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"); diff --git a/examples/elysia/tsconfig.json b/examples/elysia/tsconfig.json new file mode 100644 index 000000000..b2ff2a4a3 --- /dev/null +++ b/examples/elysia/tsconfig.json @@ -0,0 +1,43 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "esnext", + /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "lib": ["esnext"], + /* Specify what JSX code is generated. */ + "jsx": "react-jsx", + + /* Specify what module code is generated. */ + "module": "esnext", + /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "bundler", + /* Specify type package names to be included without being referenced in a source file. */ + "types": ["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/**/*"] +} \ No newline at end of file diff --git a/examples/express/README.md b/examples/express/README.md new file mode 100644 index 000000000..39d1ce60e --- /dev/null +++ b/examples/express/README.md @@ -0,0 +1,33 @@ +# Express Integration for RivetKit + +Example project demonstrating Express 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/express +npm install +``` + +### Development + +```sh +npm run dev +``` + +Open your browser to http://localhost:3000 to see the Express server with RivetKit integration. + +## License + +Apache 2.0 \ No newline at end of file diff --git a/examples/express/package.json b/examples/express/package.json new file mode 100644 index 000000000..7b49b173f --- /dev/null +++ b/examples/express/package.json @@ -0,0 +1,28 @@ +{ + "name": "example-express", + "version": "0.9.0-rc.1", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx --watch src/server.ts", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^22.13.9", + "rivetkit": "workspace:*", + "tsx": "^3.12.7", + "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/registry.ts b/examples/express/src/registry.ts new file mode 100644 index 000000000..ce4f8a995 --- /dev/null +++ b/examples/express/src/registry.ts @@ -0,0 +1,20 @@ +import { worker, setup } from "rivetkit"; + +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/express/src/server.ts b/examples/express/src/server.ts new file mode 100644 index 000000000..8e2a79c1f --- /dev/null +++ b/examples/express/src/server.ts @@ -0,0 +1,28 @@ +import { registry } from "./registry"; +import express from "express"; +import { createMemoryDriver } from "@rivetkit/memory"; + +// Start RivetKit +const { client, handler } = registry.run({ + driver: createMemoryDriver(), +}); + +// Setup router +const app = express(); + +// Expose RivetKit to the frontend (optional) +app.use("/registry", handler); + +// Example HTTP endpoint +app.post("/increment/:name", async (req, res) => { + const name = req.params.name; + + const counter = client.counter.getOrCreate(name); + const newCount = await counter.increment(1); + + res.send(`New Count: ${newCount}`); +}); + +app.listen(6420, () => { + console.log("Listening at http://localhost:6420"); +}); diff --git a/examples/chat-room-python/tsconfig.json b/examples/express/tsconfig.json similarity index 96% rename from examples/chat-room-python/tsconfig.json rename to examples/express/tsconfig.json index 474d2882c..40a9fd759 100644 --- a/examples/chat-room-python/tsconfig.json +++ b/examples/express/tsconfig.json @@ -39,5 +39,5 @@ /* Skip type checking all .d.ts files. */ "skipLibCheck": true }, - "include": ["src/**/*", "actors/**/*", "tests/**/*"] + "include": ["src/**/*"] } diff --git a/examples/hono-react/README.md b/examples/hono-react/README.md new file mode 100644 index 000000000..7833b6532 --- /dev/null +++ b/examples/hono-react/README.md @@ -0,0 +1,33 @@ +# Hono React Integration for RivetKit + +Example project demonstrating full-stack Hono backend with React frontend 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-react +npm install +``` + +### Development + +```sh +npm run dev +``` + +This will start both the Hono backend server and Vite React frontend. Open your browser to http://localhost:5173 to see the React app connected to RivetKit workers. + +## License + +Apache 2.0 \ No newline at end of file diff --git a/examples/hono-react/package.json b/examples/hono-react/package.json new file mode 100644 index 000000000..ab32ac262 --- /dev/null +++ b/examples/hono-react/package.json @@ -0,0 +1,40 @@ +{ + "name": "example-hono-react", + "version": "0.9.0-rc.1", + "private": true, + "type": "module", + "scripts": { + "dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"", + "dev:backend": "tsx --watch src/backend/server.ts", + "dev:frontend": "vite", + "build": "vite build", + "check-types": "tsc --noEmit", + "test": "vitest run" + }, + "devDependencies": { + "@types/node": "^22.13.9", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.2.0", + "concurrently": "^8.2.2", + "rivetkit": "workspace:*", + "tsx": "^3.12.7", + "typescript": "^5.5.2", + "vite": "^5.0.0", + "vitest": "^3.1.1" + }, + "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/registry.ts b/examples/hono-react/src/backend/registry.ts new file mode 100644 index 000000000..70eff98ac --- /dev/null +++ b/examples/hono-react/src/backend/registry.ts @@ -0,0 +1,21 @@ +import { worker, setup } from "rivetkit"; + +export const counter = worker({ + onAuth: () => { + // Configure auth here + }, + state: { count: 0 }, + actions: { + increment: (c, x: number) => { + c.state.count += x; + c.broadcast("newCount", c.state.count); + return c.state.count; + }, + }, +}); + +export const registry = setup({ + workers: { counter }, +}); + +export type Registry = typeof registry; diff --git a/examples/hono-react/src/backend/server.ts b/examples/hono-react/src/backend/server.ts new file mode 100644 index 000000000..1a0dc675c --- /dev/null +++ b/examples/hono-react/src/backend/server.ts @@ -0,0 +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"), +); diff --git a/examples/hono-react/src/frontend/App.tsx b/examples/hono-react/src/frontend/App.tsx new file mode 100644 index 000000000..91152baa2 --- /dev/null +++ b/examples/hono-react/src/frontend/App.tsx @@ -0,0 +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; diff --git a/examples/hono-react/src/frontend/index.html b/examples/hono-react/src/frontend/index.html new file mode 100644 index 000000000..349fe18f8 --- /dev/null +++ b/examples/hono-react/src/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + Hono React Counter + + +
+ + + diff --git a/examples/hono-react/src/frontend/main.tsx b/examples/hono-react/src/frontend/main.tsx new file mode 100644 index 000000000..6d0ba7949 --- /dev/null +++ b/examples/hono-react/src/frontend/main.tsx @@ -0,0 +1,9 @@ +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-react/tsconfig.json b/examples/hono-react/tsconfig.json new file mode 100644 index 000000000..7895675a4 --- /dev/null +++ b/examples/hono-react/tsconfig.json @@ -0,0 +1,43 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "esnext", + /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "lib": ["esnext", "dom"], + /* 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/hono-react/vite.config.ts b/examples/hono-react/vite.config.ts new file mode 100644 index 000000000..2e6d249bb --- /dev/null +++ b/examples/hono-react/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + root: "src/frontend", + build: { + outDir: "../../dist", + }, + server: { + host: "0.0.0.0", + }, +}); diff --git a/examples/hono/README.md b/examples/hono/README.md new file mode 100644 index 000000000..bb41842a6 --- /dev/null +++ b/examples/hono/README.md @@ -0,0 +1,33 @@ +# 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/hono/package.json b/examples/hono/package.json new file mode 100644 index 000000000..9294760a9 --- /dev/null +++ b/examples/hono/package.json @@ -0,0 +1,25 @@ +{ + "name": "example-hono", + "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": "workspace:*", + "tsx": "^3.12.7", + "typescript": "^5.5.2" + }, + "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/registry.ts b/examples/hono/src/registry.ts new file mode 100644 index 000000000..ce4f8a995 --- /dev/null +++ b/examples/hono/src/registry.ts @@ -0,0 +1,20 @@ +import { worker, setup } from "rivetkit"; + +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/hono/src/server.ts b/examples/hono/src/server.ts new file mode 100644 index 000000000..220e49df2 --- /dev/null +++ b/examples/hono/src/server.ts @@ -0,0 +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"), +); diff --git a/packages/platforms/nodejs/public/tsconfig.json b/examples/hono/tsconfig.json similarity index 96% rename from packages/platforms/nodejs/public/tsconfig.json rename to examples/hono/tsconfig.json index 2a978c6a6..40a9fd759 100644 --- a/packages/platforms/nodejs/public/tsconfig.json +++ b/examples/hono/tsconfig.json @@ -1,5 +1,4 @@ { - "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ @@ -39,5 +38,6 @@ /* Skip type checking all .d.ts files. */ "skipLibCheck": true - } + }, + "include": ["src/**/*"] } diff --git a/examples/linear-coding-agent/README.md b/examples/linear-coding-agent/README.md index 04f1b00b8..3b1b3860d 100644 --- a/examples/linear-coding-agent/README.md +++ b/examples/linear-coding-agent/README.md @@ -1,211 +1,36 @@ -# Linear AI Agent with GitHub Integration +# Linear Coding Agent for RivetKit -This project implements an AI agent that automatically manages GitHub code changes based on Linear issues. The agent handles the full development workflow from issue creation to PR merging. +Example project demonstrating AI-powered coding agent with Linear and GitHub integration with [RivetKit](https://rivetkit.org). -## Features +[Learn More →](https://github.com/rivet-gg/rivetkit) -- **Issue-Driven Development**: - - When an issue is created in Linear, the agent creates a branch and PR automatically - - Uses LLM to analyze and implement the required changes - - Links PRs back to Linear issues - -- **Continuous Feedback Loop**: - - Responds to comments on Linear issues by making additional code changes - - Updates the PR with the new changes - - Provides detailed summaries of changes made - -- **End-to-End Workflow**: - - Handles Linear issue status transitions - - Merges or closes PRs based on issue status changes - - Maintains consistency between GitHub PRs and Linear issues +[Discord](https://rivet.gg/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-gg/rivetkit/issues) ## Getting Started ### Prerequisites -- Node.js (v18 or later) +- Node.js - GitHub repository access - Linear workspace - Anthropic API key (for Claude) ### Installation -1. Clone this repository -2. Install dependencies: - ``` - npm install - ``` -3. Copy the environment variables: - ``` - cp .env.example .env - ``` -4. Fill in your configuration details in the `.env` file - -### Running the Agent - -You can run the agent in several ways: - -#### Option 1: Running Everything at Once - -Using concurrently to run both the actor server and webhook server: - -``` -npm run start -``` - -To also expose your local server to the internet via ngrok: - -``` -npm run start:ngrok +```sh +git clone https://github.com/rivet-gg/rivetkit +cd rivetkit/examples/linear-coding-agent +npm install ``` -The ngrok URL can be used to configure a Linear webhook. +### Development -#### Option 2: Running Services Separately - -##### Starting the Actor Core Server - -This starts the RivetKit server that hosts the coding agent: - -``` +```sh npm run dev -# Or using the RivetKit CLI -npx rivetkit/cli dev src/workers/registry.ts -``` - -##### Running the Webhook Server (for Linear integration) - -In a separate terminal, run the HTTP server that receives Linear webhooks and forwards them to the agent: - -``` -npm run server -``` - -The webhook server will be available at: -- HTTP endpoint for Linear webhooks: `http://localhost:3000/api/webhook/linear` -- Queue status endpoint: `http://localhost:3000/api/queue/:issueId` -- Health check: `http://localhost:3000/health` - -The webhook endpoint includes signature verification for added security. When configuring your Linear webhook, make sure to: -1. Copy the webhook secret provided by Linear -2. Add it to your `.env` file as `LINEAR_WEBHOOK_SECRET` - -#### Monitoring and Debugging - -The agent provides several API endpoints for monitoring and debugging: - -##### Queue Status API - -Check the status of the request queue for a specific issue: - -```bash -# Replace ISSUE-ID with your Linear issue ID -curl http://localhost:8080/api/queue/ISSUE-ID -``` - -This returns details about the queue including: -- Number of pending, processing, completed, and failed requests -- Whether the queue is currently being processed -- Timestamp of the last processed request - -##### Debug Information API - -Get detailed debug information about a specific issue: - -```bash -# Replace ISSUE-ID with your Linear issue ID -curl http://localhost:8080/api/debug/ISSUE-ID -``` - -This provides comprehensive debug information including: -- Current operation and processing stage -- Queue status and statistics -- Linear issue details -- GitHub repository and branch information -- Code changes and modifications -- LLM processing status -- Actor metadata - -##### LLM Conversation History API - -View the complete conversation history with the LLM for a specific issue: - -```bash -# Replace ISSUE-ID with your Linear issue ID -curl http://localhost:8080/api/history/ISSUE-ID ``` -This returns the full sequence of messages exchanged with the LLM, including: -- System prompts -- User messages -- Assistant responses -- Tool invocations and results - -##### Exposing Webhooks to the Internet (for Linear integration) - -To expose your local webhook server to the internet: - -``` -npm run ngrok -``` - -### Configuration - -Set the following environment variables: - -- `GITHUB_TOKEN`: GitHub Personal Access Token -- `REPO_OWNER`: GitHub username or organization -- `REPO_NAME`: Repository name -- `BASE_BRANCH`: Base branch name (defaults to 'main') -- `LINEAR_API_KEY`: Linear API Key -- `ANTHROPIC_API_KEY`: Anthropic API Key +Configure your environment variables for GitHub, Linear, and Anthropic API keys. The agent will automatically handle Linear webhooks and create GitHub PRs based on Linear issues. -## Architecture - -The agent is built using the RivetKit framework and consists of: - -- **Coding Agent**: Main actor that handles Linear webhook events -- **GitHub Integration**: API client for branch, PR, and file operations -- **Linear Integration**: API client for issue management -- **LLM Service**: Integration with Anthropic Claude for code generation -- **Request Queue**: Durable, persistent queue for processing Linear events - -### Request Queue System - -The agent implements a durable queue system to ensure reliable processing of Linear events: - -- All incoming requests (issue creation, comments, updates) are added to a persistent queue -- The queue is processed asynchronously, with each request handled in order -- If the agent crashes or restarts, it automatically resumes processing pending requests -- Request status is tracked (pending, processing, completed, failed) and can be monitored via API -- Each request has a unique ID for tracking and correlation - -This ensures that even during high load or if the agent experiences issues, no webhook events are lost and all will eventually be processed. - -## Workflow - -1. **On Issue Creation**: - - Create a new branch - - Use LLM to implement changes - - Create a PR linked to the Linear issue - - Update issue status to "In Review" - -2. **On Issue Comment**: - - Update existing branch with new changes - - Push changes to the PR - - Comment with summary of changes - -3. **On Issue Status Change**: - - If "Done": Merge the PR - - If "Canceled": Close the PR - - For other statuses: Maintain consistency - -## Development - -For local development, use: - -``` -npm run check-types # Type checking -npm run test # Run tests -``` +## License +Apache 2.0 \ No newline at end of file diff --git a/examples/nodejs/README.md b/examples/nodejs/README.md new file mode 100644 index 000000000..bb41842a6 --- /dev/null +++ b/examples/nodejs/README.md @@ -0,0 +1,33 @@ +# 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/chat-room-python/package.json b/examples/nodejs/package.json similarity index 57% rename from examples/chat-room-python/package.json rename to examples/nodejs/package.json index 9a44c0212..87b0fb01b 100644 --- a/examples/chat-room-python/package.json +++ b/examples/nodejs/package.json @@ -1,12 +1,11 @@ { - "name": "chat-room-python", + "name": "example-nodejs", "version": "0.9.0-rc.1", "private": true, "type": "module", "scripts": { - "dev": "tsx src/server.ts", - "check-types": "tsc --noEmit", - "pytest": "pytest tests/test_chat_room.py -v" + "dev": "tsx --watch src/server.ts", + "check-types": "tsc --noEmit" }, "devDependencies": { "@types/node": "^22.13.9", @@ -15,12 +14,11 @@ "typescript": "^5.5.2" }, "dependencies": { - "@rivetkit/nodejs": "workspace:*" + "@rivetkit/memory": "workspace:0.9.0-rc.1", + "@rivetkit/nodejs": "workspace:0.9.0-rc.1" }, "example": { - "platforms": [ - "*" - ] + "platforms": ["*"] }, "stableVersion": "0.8.0" } diff --git a/examples/nodejs/src/registry.ts b/examples/nodejs/src/registry.ts new file mode 100644 index 000000000..ce4f8a995 --- /dev/null +++ b/examples/nodejs/src/registry.ts @@ -0,0 +1,20 @@ +import { worker, setup } from "rivetkit"; + +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 new file mode 100644 index 000000000..9d26449e9 --- /dev/null +++ b/examples/nodejs/src/server.ts @@ -0,0 +1,7 @@ +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 new file mode 100644 index 000000000..40a9fd759 --- /dev/null +++ b/examples/nodejs/tsconfig.json @@ -0,0 +1,43 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "esnext", + /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "lib": ["esnext"], + /* Specify what JSX code is generated. */ + "jsx": "react-jsx", + + /* Specify what module code is generated. */ + "module": "esnext", + /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "bundler", + /* Specify type package names to be included without being referenced in a source file. */ + "types": ["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/README.md b/examples/react/README.md new file mode 100644 index 000000000..7833b6532 --- /dev/null +++ b/examples/react/README.md @@ -0,0 +1,33 @@ +# Hono React Integration for RivetKit + +Example project demonstrating full-stack Hono backend with React frontend 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-react +npm install +``` + +### Development + +```sh +npm run dev +``` + +This will start both the Hono backend server and Vite React frontend. Open your browser to http://localhost:5173 to see the React app connected to RivetKit workers. + +## License + +Apache 2.0 \ No newline at end of file diff --git a/examples/react/package.json b/examples/react/package.json new file mode 100644 index 000000000..f3c135345 --- /dev/null +++ b/examples/react/package.json @@ -0,0 +1,37 @@ +{ + "name": "example-react", + "version": "0.9.0-rc.1", + "private": true, + "type": "module", + "scripts": { + "dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"", + "dev:backend": "tsx --watch src/backend/server.ts", + "dev:frontend": "vite", + "build": "vite build", + "check-types": "tsc --noEmit", + "test": "vitest run" + }, + "devDependencies": { + "@types/node": "^22.13.9", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.2.0", + "concurrently": "^8.2.2", + "rivetkit": "workspace:*", + "tsx": "^3.12.7", + "typescript": "^5.5.2", + "vite": "^5.0.0", + "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/registry.ts b/examples/react/src/backend/registry.ts new file mode 100644 index 000000000..70eff98ac --- /dev/null +++ b/examples/react/src/backend/registry.ts @@ -0,0 +1,21 @@ +import { worker, setup } from "rivetkit"; + +export const counter = worker({ + onAuth: () => { + // Configure auth here + }, + state: { count: 0 }, + actions: { + increment: (c, x: number) => { + c.state.count += x; + c.broadcast("newCount", c.state.count); + return c.state.count; + }, + }, +}); + +export const registry = setup({ + workers: { counter }, +}); + +export type Registry = typeof registry; diff --git a/examples/react/src/backend/server.ts b/examples/react/src/backend/server.ts new file mode 100644 index 000000000..ef8556a69 --- /dev/null +++ b/examples/react/src/backend/server.ts @@ -0,0 +1,11 @@ +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: "*", + }, +}); diff --git a/examples/react/src/frontend/App.tsx b/examples/react/src/frontend/App.tsx new file mode 100644 index 000000000..44dedfd69 --- /dev/null +++ b/examples/react/src/frontend/App.tsx @@ -0,0 +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; diff --git a/examples/react/src/frontend/index.html b/examples/react/src/frontend/index.html new file mode 100644 index 000000000..349fe18f8 --- /dev/null +++ b/examples/react/src/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + Hono React Counter + + +
+ + + diff --git a/examples/react/src/frontend/main.tsx b/examples/react/src/frontend/main.tsx new file mode 100644 index 000000000..6d0ba7949 --- /dev/null +++ b/examples/react/src/frontend/main.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/examples/react/tsconfig.json b/examples/react/tsconfig.json new file mode 100644 index 000000000..7895675a4 --- /dev/null +++ b/examples/react/tsconfig.json @@ -0,0 +1,43 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "esnext", + /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "lib": ["esnext", "dom"], + /* 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/vite.config.ts b/examples/react/vite.config.ts new file mode 100644 index 000000000..2e6d249bb --- /dev/null +++ b/examples/react/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + root: "src/frontend", + build: { + outDir: "../../dist", + }, + server: { + host: "0.0.0.0", + }, +}); diff --git a/examples/resend-streaks/README.md b/examples/resend-streaks/README.md new file mode 100644 index 000000000..38b20c8b6 --- /dev/null +++ b/examples/resend-streaks/README.md @@ -0,0 +1,34 @@ +# Resend Streaks for RivetKit + +Example project demonstrating email automation and streak tracking using Resend 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 +- Resend API key + +### Installation + +```sh +git clone https://github.com/rivet-gg/rivetkit +cd rivetkit/examples/resend-streaks +npm install +``` + +### Development + +```sh +npm run dev +``` + +Configure your Resend API key in the environment to enable email functionality. + +## License + +Apache 2.0 \ No newline at end of file diff --git a/examples/snippets/README.md b/examples/snippets/README.md index 17b861a27..65e99085f 100644 --- a/examples/snippets/README.md +++ b/examples/snippets/README.md @@ -1,11 +1,33 @@ -# RivetKit Snippets +# Code Snippets for RivetKit -This directory contains the full source code for examples shown in the documentation. +Example project demonstrating various RivetKit patterns and integrations with [RivetKit](https://rivetkit.org). -These snippets are not intended to be complete examples. +[Learn More →](https://github.com/rivet-gg/rivetkit) -Each example has these files in a single folder: -- `actor-json.ts` - Server implementation with JavaScript state -- `actor-sqlite.ts` - Server implementation with SQLite state -- `Registry.tsx` - React client implementation +[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/snippets +npm install +``` + +### Development + +```sh +npm run dev +``` + +Browse the individual snippet folders to see different implementation patterns with JavaScript state and SQLite state examples. + +## License + +Apache 2.0 \ No newline at end of file diff --git a/examples/trpc/README.md b/examples/trpc/README.md new file mode 100644 index 000000000..26264a02b --- /dev/null +++ b/examples/trpc/README.md @@ -0,0 +1,37 @@ +# tRPC Integration for RivetKit + +Example project demonstrating tRPC 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/trpc +npm install +``` + +### Development + +```sh +npm run dev +``` + +Run the client script to interact with the tRPC server: + +```sh +npm run client +``` + +## License + +Apache 2.0 \ No newline at end of file diff --git a/examples/trpc/package.json b/examples/trpc/package.json new file mode 100644 index 000000000..45aacad42 --- /dev/null +++ b/examples/trpc/package.json @@ -0,0 +1,27 @@ +{ + "name": "example-trpc", + "version": "0.9.0-rc.1", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx --watch src/server.ts", + "client": "tsx scripts/client.ts", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@types/node": "^22.13.9", + "rivetkit": "workspace:*", + "tsx": "^3.12.7", + "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" +} \ No newline at end of file diff --git a/examples/trpc/scripts/client.ts b/examples/trpc/scripts/client.ts new file mode 100644 index 000000000..fbbee233c --- /dev/null +++ b/examples/trpc/scripts/client.ts @@ -0,0 +1,34 @@ +import { createTRPCClient, httpBatchLink } from "@trpc/client"; +import type { AppRouter } from "../src/server.js"; + +// Create tRPC client +const client = createTRPCClient({ + links: [ + httpBatchLink({ + url: "http://localhost:3001", + }), + ], +}); + +async function main() { + console.log("🚀 tRPC Client Demo"); + + try { + // Increment counter + console.log("Incrementing counter 'demo'..."); + const result = await client.increment.mutate({ name: "demo" }); + console.log("New count:", result); + + // Increment again + console.log("Incrementing counter 'demo' again..."); + const result2 = await client.increment.mutate({ name: "demo" }); + console.log("New count:", result2); + + console.log("✅ Demo completed!"); + } catch (error) { + console.error("❌ Error:", error); + process.exit(1); + } +} + +main().catch(console.error); diff --git a/examples/trpc/src/registry.ts b/examples/trpc/src/registry.ts new file mode 100644 index 000000000..11f62e9b9 --- /dev/null +++ b/examples/trpc/src/registry.ts @@ -0,0 +1,20 @@ +import { worker, setup } from "rivetkit"; + +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; \ No newline at end of file diff --git a/examples/trpc/src/server.ts b/examples/trpc/src/server.ts new file mode 100644 index 000000000..5d3dfb1a5 --- /dev/null +++ b/examples/trpc/src/server.ts @@ -0,0 +1,37 @@ +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"); diff --git a/examples/trpc/tsconfig.json b/examples/trpc/tsconfig.json new file mode 100644 index 000000000..64426049a --- /dev/null +++ b/examples/trpc/tsconfig.json @@ -0,0 +1,41 @@ +{ + "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 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/**/*", "scripts/**/*"] +} \ No newline at end of file diff --git a/package.json b/package.json index 8eb3ec7ef..5dbe7dd23 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,8 @@ "hono": "^4.7.0" }, "dependencies": { + "@hono/node-server": "^1.14.4", + "@hono/node-ws": "^1.1.7", "esbuild": "^0.25.1" }, "packageManager": "pnpm@10.7.1+sha512.2d92c86b7928dc8284f53494fb4201f983da65f0fb4f0d40baafa5cf628fa31dae3e5968f12466f17df7e97310e30f343a648baea1b9b350685dafafffdf5808" diff --git a/packages/core/README.md b/packages/core/README.md index 9ce6ff61b..3c1319e0f 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,26 +1,11 @@ # RivetKit -_The Stateful Serverless Framework_ +_Lightweight Libraries for Backends_ -Build AI agents, realtime apps, game servers, and more. +[Learn More →](https://github.com/rivet-gg/rivetkit) -Supports Rivet, Cloudflare Workers, Bun, and Node.js. - -## Resources - -- [Quickstart](https://rivetkit.org/introduction) -- [Documentation](https://rivetkit.org/) -- [Examples](https://github.com/rivet-gg/rivetkit/tree/main/examples) - -## Community & Support - -- Join our [Discord](https://rivet.gg/discord) -- Follow us on [X](https://x.com/rivet_gg) -- Follow us on [Bluesky](https://bsky.app/profile/rivet.gg) -- File bug reports in [GitHub Issues](https://github.com/rivet-gg/rivetkit/issues) -- Post questions & ideas in [GitHub Discussions](https://github.com/rivet-gg/rivetkit/discussions) +[Discord](https://rivet.gg/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-gg/rivetkit/issues) ## License -Apache 2.0 - +Apache 2.0 \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index eb51e4b84..88c8bc8f9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,13 +2,18 @@ "name": "rivetkit", "version": "0.9.0-rc.1", "license": "Apache-2.0", - "files": [ - "dist", - "src", - "deno.json", - "bun.json", - "package.json" + "keywords": [ + "rivetkit", + "stateful", + "serverless", + "workers", + "agents", + "realtime", + "websocket", + "actors", + "framework" ], + "files": ["dist", "src", "deno.json", "bun.json", "package.json"], "type": "module", "exports": { ".": { @@ -161,7 +166,9 @@ }, "peerDependencies": { "eventsource": "^3.0.5", - "ws": "^8.0.0" + "ws": "^8.0.0", + "@hono/node-server": "^1.14.0", + "@hono/node-ws": "^1.1.1" }, "peerDependenciesMeta": { "eventsource": { @@ -169,6 +176,12 @@ }, "ws": { "optional": true + }, + "@hono/node-server": { + "optional": true + }, + "@hono/node-ws": { + "optional": true } }, "stableVersion": "0.8.0" diff --git a/packages/core/scripts/dump-openapi.ts b/packages/core/scripts/dump-openapi.ts index d5f35a6bd..b9670e193 100644 --- a/packages/core/scripts/dump-openapi.ts +++ b/packages/core/scripts/dump-openapi.ts @@ -1,7 +1,6 @@ import { createManagerRouter } from "@/manager/router"; import { RegistryConfig, RegistryConfigSchema, Encoding, setup } from "@/mod"; import { ConnectionHandlers } from "@/worker/router-endpoints"; -import { DriverConfig } from "@/driver-helpers/config"; import { TestGlobalState, TestWorkerDriver, @@ -16,19 +15,27 @@ import { WorkerQuery } from "@/manager/protocol/query"; import { ToServer } from "@/worker/protocol/message/to-server"; import { EventSource } from "eventsource"; import { Context } from "hono"; +import { + DriverConfig, + RunConfig, + RunConfigSchema, +} from "@/registry/run-config"; function main() { - const registryConfig: RegistryConfig = RegistryConfigSchema.parse({ workers: {} }); + const registryConfig: RegistryConfig = RegistryConfigSchema.parse({ + workers: {}, + }); const registry = setup(registryConfig); const memoryState = new TestGlobalState(); - const driverConfig: DriverConfig = { - drivers: { + const driverConfig: RunConfig = RunConfigSchema.parse({ + driver: { + topology: "standalone", worker: new TestWorkerDriver(memoryState), - manager: new TestManagerDriver(registry, memoryState), + manager: new TestManagerDriver(memoryState), }, getUpgradeWebSocket: () => () => unimplemented(), - }; + }); const sharedConnectionHandlers: ConnectionHandlers = { onConnectWebSocket: async () => { diff --git a/packages/core/src/client/http-client-driver.ts b/packages/core/src/client/http-client-driver.ts index ea714ac1d..9425d7fe9 100644 --- a/packages/core/src/client/http-client-driver.ts +++ b/packages/core/src/client/http-client-driver.ts @@ -160,7 +160,8 @@ export function createHttpClientDriver(managerEndpoint: string): ClientDriver { assertUnreachable(encodingKind); } - return ws; + // Node & web WebSocket types not compatible + return ws as any; }, connectSse: async ( diff --git a/packages/core/src/common/utils.ts b/packages/core/src/common/utils.ts index 3270094cf..d2bb1374e 100644 --- a/packages/core/src/common/utils.ts +++ b/packages/core/src/common/utils.ts @@ -204,7 +204,10 @@ export function deconstructError( export function stringifyError(error: unknown): string { if (error instanceof Error) { - if (process.env._RIVETKIT_ERROR_STACK === "1") { + if ( + typeof process !== "undefined" && + process.env._RIVETKIT_ERROR_STACK === "1" + ) { return `${error.name}: ${error.message}${error.stack ? `\n${error.stack}` : ""}`; } else { return `${error.name}: ${error.message}`; diff --git a/packages/core/src/common/websocket.ts b/packages/core/src/common/websocket.ts index 10ba790ea..8d93803fe 100644 --- a/packages/core/src/common/websocket.ts +++ b/packages/core/src/common/websocket.ts @@ -1,5 +1,4 @@ import { logger } from "@/client/log"; -import type { WebSocket } from "ws"; // Global singleton promise that will be reused for subsequent calls let webSocketPromise: Promise | null = null; @@ -14,9 +13,9 @@ export async function importWebSocket(): Promise { webSocketPromise = (async () => { let _WebSocket: typeof WebSocket; - if (typeof global.WebSocket !== "undefined") { + if (typeof WebSocket !== "undefined") { // Browser environment - _WebSocket = global.WebSocket as unknown as typeof WebSocket; + _WebSocket = WebSocket as unknown as typeof WebSocket; logger().debug("using native websocket"); } else { // Node.js environment diff --git a/packages/core/src/driver-helpers/config.ts b/packages/core/src/driver-helpers/config.ts deleted file mode 100644 index 7df67882b..000000000 --- a/packages/core/src/driver-helpers/config.ts +++ /dev/null @@ -1,37 +0,0 @@ -//! These configs configs hold anything specific to the driver. -//! -//! This should only include parameters that affect the low-level infrastructure and does not affect behavior of workers. Configuring parameters in this block should not tweak how workers behave at all. -//! -//! For example, Rivet doesn't expose this functionality to the user at all and is completely configured automatically. - -import { z } from "zod"; -import type { - Hono, - Context as HonoContext, - Handler as HonoHandler, -} from "hono"; -import type { CoordinateDriver } from "@/topologies/coordinate/driver"; -import type { ManagerDriver } from "@/manager/driver"; -import type { WorkerDriver } from "@/worker/driver"; -import { UpgradeWebSocket } from "@/utils"; - -export const TopologySchema = z.enum(["standalone", "partition", "coordinate"]); -export type Topology = z.infer; - -export type GetUpgradeWebSocket = (router: Hono) => UpgradeWebSocket; - -/** Base config used for the worker config across all platforms. */ -export const DriverConfigSchema = z.object({ - topology: TopologySchema.optional(), // Default value depends on the platform selected - drivers: z - .object({ - manager: z.custom().optional(), - worker: z.custom().optional(), - coordinate: z.custom().optional(), - }) - .optional() - .default({}), - // This is dynamic since NodeJS requires a reference to the router to initialize WebSockets - getUpgradeWebSocket: z.custom().optional(), -}); -export type DriverConfig = z.infer; diff --git a/packages/core/src/driver-helpers/mod.ts b/packages/core/src/driver-helpers/mod.ts index f5bf79f29..0a50c71a3 100644 --- a/packages/core/src/driver-helpers/mod.ts +++ b/packages/core/src/driver-helpers/mod.ts @@ -1,4 +1,3 @@ -export { type DriverConfig, DriverConfigSchema } from "./config"; export type { WorkerInstance, AnyWorkerInstance } from "@/worker/instance"; export { AttemptAcquireLease, @@ -27,3 +26,4 @@ export { HEADER_CONN_ID, HEADER_CONN_TOKEN, } from "@/worker/router-endpoints"; +export { RunConfigSchema, DriverConfigSchema } from "@/registry/run-config"; diff --git a/packages/core/src/driver-test-suite/mod.ts b/packages/core/src/driver-test-suite/mod.ts index 48b5b6727..426f2dfe8 100644 --- a/packages/core/src/driver-test-suite/mod.ts +++ b/packages/core/src/driver-test-suite/mod.ts @@ -1,14 +1,14 @@ import { serve as honoServe } from "@hono/node-server"; -import { - WorkerDriver, - CoordinateDriver, - DriverConfig, - ManagerDriver, -} from "@/driver-helpers/mod"; import { runWorkerDriverTests } from "./tests/worker-driver"; import { runManagerDriverTests } from "./tests/manager-driver"; import { describe } from "vitest"; -import { CoordinateTopology, StandaloneTopology, Registry } from "@/mod"; +import { + CoordinateTopology, + StandaloneTopology, + Registry, + RunConfig, + DriverConfig, +} from "@/mod"; import { createNodeWebSocket, type NodeWebSocket } from "@hono/node-ws"; import invariant from "invariant"; import { bundleRequire } from "bundle-require"; @@ -22,6 +22,7 @@ import { runWorkerConnStateTests } from "./tests/worker-conn-state"; import { runWorkerMetadataTests } from "./tests/worker-metadata"; import { runWorkerErrorHandlingTests } from "./tests/worker-error-handling"; import { runWorkerAuthTests } from "./tests/worker-auth"; +import { RunConfigSchema } from "@/registry/run-config"; export interface DriverTestConfig { /** Deploys an registry and returns the connection endpoint. */ @@ -105,9 +106,7 @@ export function runDriverTests( export async function createTestRuntime( registryPath: string, driverFactory: (registry: Registry) => Promise<{ - workerDriver: WorkerDriver; - managerDriver: ManagerDriver; - coordinateDriver?: CoordinateDriver; + driver: DriverConfig; cleanup?: () => Promise; }>, ): Promise { @@ -122,32 +121,24 @@ export async function createTestRuntime( registry.config.test.enabled = true; // Build drivers - const { - workerDriver, - managerDriver, - coordinateDriver, - cleanup: driverCleanup, - } = await driverFactory(registry); + const { driver, cleanup: driverCleanup } = await driverFactory(registry); // Build driver config let injectWebSocket: NodeWebSocket["injectWebSocket"] | undefined; - const config: DriverConfig = { - drivers: { - worker: workerDriver, - manager: managerDriver, - coordinate: coordinateDriver, - }, - getUpgradeWebSocket: (router) => { + const config: RunConfig = RunConfigSchema.parse({ + driver, + getUpgradeWebSocket: (router: any) => { const webSocket = createNodeWebSocket({ app: router }); injectWebSocket = webSocket.injectWebSocket; return webSocket.upgradeWebSocket; }, - }; + }); // Build topology - const topology = coordinateDriver - ? new CoordinateTopology(registry.config, config) - : new StandaloneTopology(registry.config, config); + const topology = + config.driver.topology === "coordinate" + ? new CoordinateTopology(registry.config, config) + : new StandaloneTopology(registry.config, config); if (!injectWebSocket) throw new Error("injectWebSocket not defined"); // Start server diff --git a/packages/core/src/driver-test-suite/test-inline-client-driver.ts b/packages/core/src/driver-test-suite/test-inline-client-driver.ts index e273a1aa8..0a08328f8 100644 --- a/packages/core/src/driver-test-suite/test-inline-client-driver.ts +++ b/packages/core/src/driver-test-suite/test-inline-client-driver.ts @@ -85,10 +85,11 @@ export function createTestInlineClientDriver( logger().debug("connecting to websocket", { url: finalWsUrl }); // Create and return the WebSocket + // Node & browser WebSocket types are incompatible return new WebSocket(finalWsUrl, [ // HACK: See packages/platforms/cloudflare-workers/src/websocket.ts "rivetkit", - ]); + ]) as any; }, connectSse: async ( diff --git a/packages/core/src/driver-test-suite/tests/worker-conn-state.ts b/packages/core/src/driver-test-suite/tests/worker-conn-state.ts index 97cd8ca70..0054aceab 100644 --- a/packages/core/src/driver-test-suite/tests/worker-conn-state.ts +++ b/packages/core/src/driver-test-suite/tests/worker-conn-state.ts @@ -6,11 +6,7 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { describe("Worker Connection State Tests", () => { describe("Connection State Initialization", () => { test("should retrieve connection state", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - - ); + const { client } = await setupDriverTest(c, driverTestConfig); // Connect to the worker const connection = client.connStateWorker.getOrCreate().connect(); @@ -30,11 +26,7 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { }); test("should initialize connection state with custom parameters", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - - ); + const { client } = await setupDriverTest(c, driverTestConfig); // Connect with custom parameters const connection = client.connStateWorker @@ -60,11 +52,7 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { describe("Connection State Management", () => { test("should maintain unique state for each connection", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - - ); + const { client } = await setupDriverTest(c, driverTestConfig); // Create multiple connections const conn1 = client.connStateWorker @@ -99,11 +87,7 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { }); test("should track connections in shared state", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - - ); + const { client } = await setupDriverTest(c, driverTestConfig); // Create two connections const handle = client.connStateWorker.getOrCreate(); @@ -128,11 +112,7 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { }); test("should identify different connections in the same worker", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - - ); + const { client } = await setupDriverTest(c, driverTestConfig); // Create two connections to the same worker const handle = client.connStateWorker.getOrCreate(); @@ -158,11 +138,7 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { describe("Connection Lifecycle", () => { test("should track connection and disconnection events", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - - ); + const { client } = await setupDriverTest(c, driverTestConfig); // Create a connection const handle = client.connStateWorker.getOrCreate(); @@ -196,11 +172,7 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { }); test("should update connection state", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - - ); + const { client } = await setupDriverTest(c, driverTestConfig); // Create a connection const conn = client.connStateWorker.getOrCreate().connect(); @@ -231,11 +203,7 @@ export function runWorkerConnStateTests(driverTestConfig: DriverTestConfig) { describe("Connection Communication", () => { test("should send messages to specific connections", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - - ); + const { client } = await setupDriverTest(c, driverTestConfig); // Create two connections const handle = client.connStateWorker.getOrCreate(); diff --git a/packages/core/src/globals.d.ts b/packages/core/src/globals.d.ts index 48f1eb352..f83d4f46e 100644 --- a/packages/core/src/globals.d.ts +++ b/packages/core/src/globals.d.ts @@ -1,3 +1,6 @@ -declare const Deno: any; -declare const navigator: any; +declare global { + const Deno: any; + const navigator: any; +} +export {}; diff --git a/packages/core/src/inline-client-driver/mod.ts b/packages/core/src/inline-client-driver/mod.ts index a9f068e54..2ff18e0ca 100644 --- a/packages/core/src/inline-client-driver/mod.ts +++ b/packages/core/src/inline-client-driver/mod.ts @@ -199,7 +199,8 @@ export function createInlineClientDriver( params, ); - return ws; + // Node & browser WebSocket types are incompatible + return ws as any; } else { assertUnreachable(routingHandler); } diff --git a/packages/core/src/manager/router.ts b/packages/core/src/manager/router.ts index c414bd574..7df8d6936 100644 --- a/packages/core/src/manager/router.ts +++ b/packages/core/src/manager/router.ts @@ -36,7 +36,6 @@ import { deconstructError, stringifyError, } from "@/common/utils"; -import type { DriverConfig } from "@/driver-helpers/config"; import { Hono, type Context as HonoContext, type Next } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; import { z } from "@hono/zod-openapi"; @@ -60,6 +59,8 @@ 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"; type ManagerRouterHandler = { // onConnectInspector?: ManagerInspectorConnHandler; @@ -114,56 +115,52 @@ function buildOpenApiResponses(schema: T) { export function createManagerRouter( registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, inlineClientDriver: ClientDriver, handler: ManagerRouterHandler, ) { - if (!driverConfig.drivers?.manager) { - // FIXME move to config schema - throw new Error("config.drivers.manager is not defined."); - } - const driver = driverConfig.drivers.manager; + const driver = runConfig.driver.manager; const router = new OpenAPIHono(); - const upgradeWebSocket = driverConfig.getUpgradeWebSocket?.( + const upgradeWebSocket = runConfig.getUpgradeWebSocket?.( router as unknown as Hono, ); router.use("*", loggerMiddleware(logger())); - if (registryConfig.cors) { - const corsConfig = registryConfig.cors; + if (runConfig.cors) { + const corsConfig = runConfig.cors; router.use("*", async (c, next) => { - const path = c.req.path; - // Don't apply to WebSocket routes - if (path === "/workers/connect/websocket" || path === "/inspect") { + // HACK: This could be insecure if we had a varargs path. We have to check the path suffix for WS since we don't know the path that this router was mounted. + const path = c.req.path; + if ( + path.endsWith("/workers/connect/websocket") || + path.endsWith("/inspect") + ) { return next(); } return cors({ ...corsConfig, allowHeaders: [ - ...(registryConfig.cors?.allowHeaders ?? []), + ...(corsConfig?.allowHeaders ?? []), ...ALL_PUBLIC_HEADERS, + "Content-Type", + "User-Agent", ], })(c, next); }); } // GET / - router.get("/", (c) => { + router.get("/", (c: HonoContext) => { return c.text( - "This is an RivetKit server.\n\nLearn more at https://rivetkit.org", + "This is an RivetKit registry.\n\nLearn more at https://rivetkit.org", ); }); - // GET /health - router.get("/health", (c) => { - return c.text("ok"); - }); - // POST /workers/resolve { const ResolveQuerySchema = z @@ -207,6 +204,23 @@ export function createManagerRouter( // GET /workers/connect/websocket { + // HACK: WebSockets don't work with mounts, so we need to dynamically match the trailing path + router.use("*", (c, next) => { + if (c.req.path.endsWith("/workers/connect/websocket")) { + return handleWebSocketConnectRequest( + c, + upgradeWebSocket, + registryConfig, + runConfig, + driver, + handler, + ); + } + + return next(); + }); + + // This route is a noop, just used to generate docs const wsRoute = createRoute({ method: "get", path: "/workers/connect/websocket", @@ -217,16 +231,9 @@ export function createManagerRouter( }, }); - router.openapi(wsRoute, (c) => - handleWebSocketConnectRequest( - c, - upgradeWebSocket, - registryConfig, - driverConfig, - driver, - handler, - ), - ); + router.openapi(wsRoute, () => { + throw new Error("Should be unreachable"); + }); } // GET /workers/connect/sse @@ -254,7 +261,7 @@ export function createManagerRouter( }); router.openapi(sseRoute, (c) => - handleSseConnectRequest(c, registryConfig, driverConfig, driver, handler), + handleSseConnectRequest(c, registryConfig, runConfig, driver, handler), ); } @@ -309,7 +316,7 @@ export function createManagerRouter( }); router.openapi(actionRoute, (c) => - handleActionRequest(c, registryConfig, driverConfig, driver, handler), + handleActionRequest(c, registryConfig, runConfig, driver, handler), ); } @@ -349,7 +356,7 @@ export function createManagerRouter( }); router.openapi(messageRoute, (c) => - handleMessageRequest(c, registryConfig, handler), + handleMessageRequest(c, registryConfig, runConfig, handler), ); } @@ -399,7 +406,7 @@ export function createManagerRouter( if (upgradeWebSocket) { router.get( ".test/inline-driver/connect-websocket", - upgradeWebSocket(async (c) => { + upgradeWebSocket(async (c: any) => { const { workerQuery: workerQueryRaw, params: paramsRaw, @@ -555,6 +562,8 @@ export function createManagerRouter( }, }); + router.get("/foo", (c) => c.text("foo")); + router.notFound(handleRouteNotFound); router.onError(handleRouteError.bind(undefined, {})); @@ -643,7 +652,7 @@ export async function queryWorker( async function handleSseConnectRequest( c: HonoContext, registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, driver: ManagerDriver, handler: ManagerRouterHandler, ): Promise { @@ -694,7 +703,7 @@ async function handleSseConnectRequest( return await handleSseConnect( c, registryConfig, - driverConfig, + runConfig, handler.routingHandler.inline.handlers.onConnectSse, workerId, authData, @@ -784,7 +793,7 @@ async function handleWebSocketConnectRequest( ) => (c: HonoContext, next: Next) => Promise) | undefined, registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, driver: ManagerDriver, handler: ManagerRouterHandler, ): Promise { @@ -890,6 +899,7 @@ async function handleWebSocketConnectRequest( return handleWebSocketConnect( c, registryConfig, + runConfig, onConnectWebSocket, workerId, params.data.encoding, @@ -972,6 +982,7 @@ async function handleWebSocketConnectRequest( async function handleMessageRequest( c: HonoContext, registryConfig: RegistryConfig, + runConfig: RunConfig, handler: ManagerRouterHandler, ): Promise { logger().debug("connection message request received"); @@ -1013,6 +1024,7 @@ async function handleMessageRequest( return handleConnectionMessage( c, registryConfig, + runConfig, handler.routingHandler.inline.handlers.onConnMessage, connId, connToken as string, @@ -1057,7 +1069,7 @@ async function handleMessageRequest( async function handleActionRequest( c: HonoContext, registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, driver: ManagerDriver, handler: ManagerRouterHandler, ): Promise { @@ -1105,7 +1117,7 @@ async function handleActionRequest( return handleAction( c, registryConfig, - driverConfig, + runConfig, handler.routingHandler.inline.handlers.onAction, actionName, workerId, diff --git a/packages/core/src/registry/config.ts b/packages/core/src/registry/config.ts index 8c0446823..6bfed9437 100644 --- a/packages/core/src/registry/config.ts +++ b/packages/core/src/registry/config.ts @@ -1,50 +1,13 @@ //! These configs configs hold anything that's not platform-specific about running workers. +import { AnyWorkerDefinition, WorkerDefinition } from "@/worker/definition"; import { z } from "zod"; -import type { cors } from "hono/cors"; -import { WorkerDefinition, AnyWorkerDefinition } from "@/worker/definition"; -// Define CORS options schema -type CorsOptions = NonNullable[0]>; - -export const WorkerPeerConfigSchema = z.object({ - /** - * How long the worker leader holds a lease for. - * - * Milliseconds - **/ - leaseDuration: z.number().optional().default(3000), - /** - * How long before the lease will expire to issue the renew command. - * - * Milliseconds - */ - renewLeaseGrace: z.number().optional().default(1500), - /** - * How frequently the followers check if the leader is still active. - * - * Milliseconds - */ - checkLeaseInterval: z.number().optional().default(1000), - /** - * Positive jitter for check lease interval - * - * Milliseconds - */ - checkLeaseJitter: z.number().optional().default(500), - /** - * How long to wait for a message ack. - * - * Milliseconds - */ - messageAckTimeout: z.number().optional().default(1000), -}); -export type WorkerPeerConfig = z.infer; export const WorkersSchema = z.record( z.string(), z.custom>(), ); -export type Workers = z.infer; +export type RegistryWorkers = z.infer; export const TestConfigSchema = z.object({ enabled: z.boolean() }); export type TestConfig = z.infer; @@ -53,27 +16,8 @@ export type TestConfig = z.infer; export const RegistryConfigSchema = z.object({ workers: z.record(z.string(), z.custom()), - /** CORS configuration for the router. Uses Hono's CORS middleware options. */ - cors: z.custom().optional(), - - /** Base path used to build URLs from. This is specifically used when returning the endpoint to connect to for workers. */ - basePath: z.string().optional(), - - /** This goes in the URL so it needs to be short. */ - maxConnParamLength: z.number().optional().default(8_192), - - maxIncomingMessageSize: z.number().optional().default(65_536), - - /** How long to wait for the WebSocket to send an init message before closing it. */ - webSocketInitTimeout: z.number().optional().default(5_000), - - /** Peer configuration for coordinated topology. */ - workerPeer: WorkerPeerConfigSchema.optional().default({}), - - // inspector: InspectorConfigSchema.optional().default({ enabled: false }), - // TODO: Find a better way of passing around the test config - /** + /** * Test configuration. * * DO NOT MANUALLY ENABLE. THIS IS USED INTERNALLY. @@ -81,7 +25,7 @@ export const RegistryConfigSchema = z.object({ test: TestConfigSchema.optional().default({ enabled: false }), }); export type RegistryConfig = z.infer; -export type RegistryConfigInput = Omit< +export type RegistryConfigInput = Omit< z.input, "workers" > & { workers: A }; diff --git a/packages/core/src/registry/mod.ts b/packages/core/src/registry/mod.ts index 47acebd51..f3876cc1f 100644 --- a/packages/core/src/registry/mod.ts +++ b/packages/core/src/registry/mod.ts @@ -1,11 +1,28 @@ +import { Client, ClientDriver, createClientWithDriver } from "@/client/client"; import { - type Workers, + type RegistryWorkers, type RegistryConfig, type RegistryConfigInput, RegistryConfigSchema, } from "./config"; +import { + RunConfigSchema, + type DriverConfig, + type RunConfig, + type RunConfigInput, +} from "./run-config"; +import { StandaloneTopology } from "@/topologies/standalone/mod"; +import invariant from "invariant"; +import type { Hono } from "hono"; +import { assertUnreachable } from "@/utils"; + +interface RunOutput> { + client: Client; + hono: Hono; + handler: (req: Request) => Promise; +} -export class Registry { +export class Registry { #config: RegistryConfig; public get config(): RegistryConfig { @@ -15,14 +32,49 @@ export class Registry { constructor(config: RegistryConfig) { this.#config = config; } + + public run(inputConfig: RunConfigInput): RunOutput { + const config = RunConfigSchema.parse(inputConfig); + + // Setup topology + let hono: Hono; + let clientDriver: ClientDriver; + if (config.driver.topology === "standalone") { + const topology = new StandaloneTopology(this.#config, config); + hono = topology.router; + clientDriver = topology.clientDriver; + } else if (config.driver.topology === "partition") { + // TODO: + invariant(false, "foo"); + } else if (config.driver.topology === "coordinate") { + const topology = new StandaloneTopology(this.#config, config); + hono = topology.router; + clientDriver = topology.clientDriver; + } else { + assertUnreachable(config.driver.topology); + } + + // Create client + const client = createClientWithDriver(clientDriver); + + return { + client, + hono, + handler: async (req: Request) => await hono.fetch(req), + }; + } + + // public runAndServe(): RunOutput { + // // TODO: + // } } -export function setup( +export function setup( input: RegistryConfigInput, ): Registry { const config = RegistryConfigSchema.parse(input); return new Registry(config); } -export type { RegistryConfig }; +export type { RegistryConfig, RegistryWorkers, RunConfig, DriverConfig }; export { RegistryConfigSchema }; diff --git a/packages/core/src/registry/run-config.ts b/packages/core/src/registry/run-config.ts new file mode 100644 index 000000000..37e7ba383 --- /dev/null +++ b/packages/core/src/registry/run-config.ts @@ -0,0 +1,78 @@ +import { z } from "zod"; +import type { Hono } from "hono"; +import type { CoordinateDriver } from "@/topologies/coordinate/driver"; +import type { ManagerDriver } from "@/manager/driver"; +import type { WorkerDriver } from "@/worker/driver"; +import type { UpgradeWebSocket } from "@/utils"; +import type { cors } from "hono/cors"; + +type CorsOptions = NonNullable[0]>; + +export const WorkerPeerConfigSchema = z.object({ + /** + * How long the worker leader holds a lease for. + * + * Milliseconds + **/ + leaseDuration: z.number().optional().default(3000), + /** + * How long before the lease will expire to issue the renew command. + * + * Milliseconds + */ + renewLeaseGrace: z.number().optional().default(1500), + /** + * How frequently the followers check if the leader is still active. + * + * Milliseconds + */ + checkLeaseInterval: z.number().optional().default(1000), + /** + * Positive jitter for check lease interval + * + * Milliseconds + */ + checkLeaseJitter: z.number().optional().default(500), + /** + * How long to wait for a message ack. + * + * Milliseconds + */ + messageAckTimeout: z.number().optional().default(1000), +}); +export type WorkerPeerConfig = z.infer; + +export const TopologySchema = z.enum(["standalone", "partition", "coordinate"]); +export type Topology = z.infer; + +export type GetUpgradeWebSocket = (router: Hono) => UpgradeWebSocket; + +export const DriverConfigSchema = z.object({ + topology: TopologySchema, + manager: z.custom(), + worker: z.custom(), + coordinate: z.custom().optional(), +}); + +export type DriverConfig = z.infer; + +/** Base config used for the worker config across all platforms. */ +export const RunConfigSchema = z.object({ + driver: DriverConfigSchema, + + // 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(), + + maxIncomingMessageSize: z.number().optional().default(65_536), + + /** Peer configuration for coordinated topology. */ + workerPeer: WorkerPeerConfigSchema.optional().default({}), + + // inspector: InspectorConfigSchema.optional().default({ enabled: false }), +}); + +export type RunConfig = z.infer; +export type RunConfigInput = z.input; diff --git a/packages/core/src/test/config.ts b/packages/core/src/test/config.ts index 04f91d84b..d74f16d8d 100644 --- a/packages/core/src/test/config.ts +++ b/packages/core/src/test/config.ts @@ -1,7 +1,7 @@ -import { DriverConfigSchema } from "@/driver-helpers/mod"; +import { RunConfigSchema } from "@/registry/run-config"; import { z } from "zod"; -export const ConfigSchema = DriverConfigSchema.extend({ +export const ConfigSchema = RunConfigSchema.extend({ hostname: z .string() .optional() @@ -10,5 +10,5 @@ export const ConfigSchema = DriverConfigSchema.extend({ .number() .optional() .default(Number.parseInt(process.env.PORT ?? "6420")), -}).default({}); +}).partial({ driver: true }); export type InputConfig = z.input; diff --git a/packages/core/src/test/driver/manager.ts b/packages/core/src/test/driver/manager.ts index a3fe5720c..12d83f1c9 100644 --- a/packages/core/src/test/driver/manager.ts +++ b/packages/core/src/test/driver/manager.ts @@ -8,7 +8,6 @@ import type { import { WorkerAlreadyExists } from "@/worker/errors"; import type { TestGlobalState } from "./global-state"; import * as crypto from "node:crypto"; -import type { Registry } from "@/registry/mod"; import { WorkerOutput } from "@/manager/driver"; export class TestManagerDriver implements ManagerDriver { @@ -20,7 +19,6 @@ export class TestManagerDriver implements ManagerDriver { // }); constructor( - private readonly registry: Registry, state: TestGlobalState, ) { this.#state = state; diff --git a/packages/core/src/test/driver/mod.ts b/packages/core/src/test/driver/mod.ts index e0e4b10d0..33d6d88e2 100644 --- a/packages/core/src/test/driver/mod.ts +++ b/packages/core/src/test/driver/mod.ts @@ -1,3 +1,17 @@ +import { DriverConfig } from "@/mod"; +import { TestGlobalState } from "./global-state"; +import { TestManagerDriver } from "./manager"; +import { TestWorkerDriver } from "./worker"; + export { TestGlobalState } from "./global-state"; export { TestWorkerDriver } from "./worker"; export { TestManagerDriver } from "./manager"; + +export function createTestDriver(): DriverConfig { + const state = new TestGlobalState(); + return { + topology: "standalone", + manager: new TestManagerDriver(state), + worker: new TestWorkerDriver(state), + }; +} diff --git a/packages/core/src/test/mod.ts b/packages/core/src/test/mod.ts index bc207091e..b1742dbec 100644 --- a/packages/core/src/test/mod.ts +++ b/packages/core/src/test/mod.ts @@ -14,6 +14,7 @@ import { type InputConfig, ConfigSchema } from "./config"; import { type TestContext, vi } from "vitest"; import { type Client, createClient } from "@/client/mod"; import { createServer } from "node:net"; +import { RunConfigSchema } from "@/registry/run-config"; function createRouter( registry: Registry, @@ -25,15 +26,13 @@ function createRouter( const config = ConfigSchema.parse(inputConfig); // Configure default configuration - if (!config.topology) config.topology = "standalone"; - if (!config.drivers.manager || !config.drivers.worker) { + if (!config.driver) { const memoryState = new TestGlobalState(); - if (!config.drivers.manager) { - config.drivers.manager = new TestManagerDriver(registry, memoryState); - } - if (!config.drivers.worker) { - config.drivers.worker = new TestWorkerDriver(memoryState); - } + config.driver = { + topology: "standalone", + manager: new TestManagerDriver(memoryState), + worker: new TestWorkerDriver(memoryState), + }; } // Setup WebSocket routing for Node @@ -49,18 +48,19 @@ function createRouter( } // Setup topology - if (config.topology === "standalone") { - const topology = new StandaloneTopology(registry.config, config); + const runConfig = RunConfigSchema.parse(inputConfig); + if (config.driver.topology === "standalone") { + const topology = new StandaloneTopology(registry.config, runConfig); if (!injectWebSocket) throw new Error("injectWebSocket not defined"); return { router: topology.router, injectWebSocket }; - } else if (config.topology === "partition") { + } else if (config.driver.topology === "partition") { throw new Error("Node.js only supports standalone & coordinate topology."); - } else if (config.topology === "coordinate") { - const topology = new CoordinateTopology(registry.config, config); + } else if (config.driver.topology === "coordinate") { + const topology = new CoordinateTopology(registry.config, runConfig); if (!injectWebSocket) throw new Error("injectWebSocket not defined"); return { router: topology.router, injectWebSocket }; } else { - assertUnreachable(config.topology); + assertUnreachable(config.driver.topology); } } diff --git a/packages/core/src/topologies/coordinate/conn/mod.ts b/packages/core/src/topologies/coordinate/conn/mod.ts index 7c6d9bac1..08e7c6980 100644 --- a/packages/core/src/topologies/coordinate/conn/mod.ts +++ b/packages/core/src/topologies/coordinate/conn/mod.ts @@ -7,9 +7,9 @@ import { WorkerPeer } from "../worker-peer"; import { publishMessageToLeader } from "../node/message"; import { generateConnId, generateConnToken } from "@/worker/connection"; import type { WorkerDriver } from "@/worker/driver"; -import { DriverConfig } from "@/driver-helpers/config"; import { RegistryConfig } from "@/registry/config"; import { unknown } from "zod"; +import { RunConfig } from "@/registry/run-config"; export interface RelayConnDriver { sendMessage(message: messageToClient.ToClient): void; @@ -21,7 +21,7 @@ export interface RelayConnDriver { */ export class RelayConn { #registryConfig: RegistryConfig; - #driverConfig: DriverConfig; + #runConfig: RunConfig; #coordinateDriver: CoordinateDriver; #workerDriver: WorkerDriver; #globalState: GlobalState; @@ -51,7 +51,7 @@ export class RelayConn { constructor( registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, workerDriver: WorkerDriver, CoordinateDriver: CoordinateDriver, globalState: GlobalState, @@ -61,7 +61,7 @@ export class RelayConn { authData: unknown, ) { this.#registryConfig = registryConfig; - this.#driverConfig = driverConfig; + this.#runConfig = runConfig; this.#coordinateDriver = CoordinateDriver; this.#workerDriver = workerDriver; this.#driver = driver; @@ -88,7 +88,7 @@ export class RelayConn { // Create worker peer this.#workerPeer = await WorkerPeer.acquire( this.#registryConfig, - this.#driverConfig, + this.#runConfig, this.#workerDriver, this.#coordinateDriver, this.#globalState, @@ -101,7 +101,7 @@ export class RelayConn { // Publish connection open await publishMessageToLeader( this.#registryConfig, - this.#driverConfig, + this.#runConfig, this.#coordinateDriver, this.#globalState, this.#workerId, @@ -151,7 +151,7 @@ export class RelayConn { // Publish connection close await publishMessageToLeader( this.#registryConfig, - this.#driverConfig, + this.#runConfig, this.#coordinateDriver, this.#globalState, this.#workerId, diff --git a/packages/core/src/topologies/coordinate/node/message.ts b/packages/core/src/topologies/coordinate/node/message.ts index f98a297a4..4c31a4ede 100644 --- a/packages/core/src/topologies/coordinate/node/message.ts +++ b/packages/core/src/topologies/coordinate/node/message.ts @@ -3,8 +3,8 @@ import { logger } from "../log"; import pRetry, { AbortError } from "p-retry"; import type { CoordinateDriver } from "../driver"; import type { NodeMessage } from "./protocol"; -import { DriverConfig } from "@/driver-helpers/config"; import { RegistryConfig } from "@/registry/config"; +import { DriverConfig, RunConfig } from "@/registry/run-config"; /** * Publishes a message and waits for an ack. If no ack is received, then retries accordingly. @@ -13,7 +13,7 @@ import { RegistryConfig } from "@/registry/config"; */ export async function publishMessageToLeader( registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, CoordinateDriver: CoordinateDriver, globalState: GlobalState, workerId: string, @@ -32,7 +32,7 @@ export async function publishMessageToLeader( () => publishMessageToLeaderInner( registryConfig, - driverConfig, + runConfig, CoordinateDriver, globalState, workerId, @@ -56,7 +56,7 @@ export async function publishMessageToLeader( async function publishMessageToLeaderInner( registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, CoordinateDriver: CoordinateDriver, globalState: GlobalState, workerId: string, @@ -92,7 +92,7 @@ async function publishMessageToLeaderInner( // Throw error on timeout const timeoutId = setTimeout( () => ackReject(new Error("Ack timed out")), - registryConfig.workerPeer.messageAckTimeout, + runConfig.workerPeer.messageAckTimeout, ); try { diff --git a/packages/core/src/topologies/coordinate/router/sse.ts b/packages/core/src/topologies/coordinate/router/sse.ts index 739c32a08..779745ce8 100644 --- a/packages/core/src/topologies/coordinate/router/sse.ts +++ b/packages/core/src/topologies/coordinate/router/sse.ts @@ -4,13 +4,13 @@ import { encodeDataToString, serialize } from "@/worker/protocol/serde"; import type { CoordinateDriver } from "../driver"; import { RelayConn } from "../conn/mod"; import type { WorkerDriver } from "@/worker/driver"; -import { DriverConfig } from "@/driver-helpers/config"; import { RegistryConfig } from "@/registry/config"; import { ConnectSseOpts, ConnectSseOutput } from "@/worker/router-endpoints"; +import { RunConfig } from "@/registry/run-config"; export async function serveSse( registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, workerDriver: WorkerDriver, CoordinateDriver: CoordinateDriver, globalState: GlobalState, @@ -22,7 +22,7 @@ export async function serveSse( onOpen: async (stream) => { conn = new RelayConn( registryConfig, - driverConfig, + runConfig, workerDriver, CoordinateDriver, globalState, diff --git a/packages/core/src/topologies/coordinate/router/websocket.ts b/packages/core/src/topologies/coordinate/router/websocket.ts index a8950a133..8a38234e3 100644 --- a/packages/core/src/topologies/coordinate/router/websocket.ts +++ b/packages/core/src/topologies/coordinate/router/websocket.ts @@ -8,16 +8,16 @@ import type { CoordinateDriver } from "../driver"; import { RelayConn } from "../conn/mod"; import { publishMessageToLeader } from "../node/message"; import type { WorkerDriver } from "@/worker/driver"; -import type { DriverConfig } from "@/driver-helpers/config"; import type { RegistryConfig } from "@/registry/config"; import { ConnectWebSocketOpts, ConnectWebSocketOutput, } from "@/worker/router-endpoints"; +import { DriverConfig, RunConfig } from "@/registry/run-config"; export async function serveWebSocket( registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, workerDriver: WorkerDriver, CoordinateDriver: CoordinateDriver, globalState: GlobalState, @@ -29,7 +29,7 @@ export async function serveWebSocket( onOpen: async (ws: WSContext) => { conn = new RelayConn( registryConfig, - driverConfig, + runConfig, workerDriver, CoordinateDriver, globalState, @@ -55,7 +55,7 @@ export async function serveWebSocket( await publishMessageToLeader( registryConfig, - driverConfig, + runConfig, CoordinateDriver, globalState, workerId, diff --git a/packages/core/src/topologies/coordinate/topology.ts b/packages/core/src/topologies/coordinate/topology.ts index 18d695e70..f7e931309 100644 --- a/packages/core/src/topologies/coordinate/topology.ts +++ b/packages/core/src/topologies/coordinate/topology.ts @@ -6,7 +6,6 @@ import { publishMessageToLeader } from "./node/message"; import type { RelayConn } from "./conn/mod"; import { Hono } from "hono"; import { handleRouteError, handleRouteNotFound } from "@/common/router"; -import type { DriverConfig } from "@/driver-helpers/config"; import type { RegistryConfig } from "@/registry/config"; import { createManagerRouter } from "@/manager/router"; import type { @@ -25,6 +24,7 @@ import { serveWebSocket } from "./router/websocket"; import { serveSse } from "./router/sse"; import { ClientDriver } from "@/client/client"; import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { DriverConfig, RunConfig } from "@/registry/run-config"; export interface GlobalState { nodeId: string; @@ -40,13 +40,11 @@ export class CoordinateTopology { public readonly clientDriver: ClientDriver; public readonly router: Hono; - constructor(registryConfig: RegistryConfig, driverConfig: DriverConfig) { - if (!driverConfig.drivers) throw new Error("config.drivers not defined."); + constructor(registryConfig: RegistryConfig, runConfig: RunConfig) { const { worker: workerDriver, coordinate: CoordinateDriver } = - driverConfig.drivers; - if (!workerDriver) throw new Error("config.drivers.worker not defined."); + runConfig.driver; if (!CoordinateDriver) - throw new Error("config.drivers.coordinate not defined."); + throw new Error("config.driver.coordinate not defined."); // Allow usage of a lot of AbortSignals (which are EventEmitters) //events.defaultMaxListeners = 100_000; @@ -65,7 +63,7 @@ export class CoordinateTopology { // Build router const router = new Hono(); - const upgradeWebSocket = driverConfig.getUpgradeWebSocket?.(router); + const upgradeWebSocket = runConfig.getUpgradeWebSocket?.(router); // Share connection handlers for both routers const connectionHandlers: ConnectionHandlers = { @@ -74,7 +72,7 @@ export class CoordinateTopology { ): Promise => { return await serveWebSocket( registryConfig, - driverConfig, + runConfig, workerDriver, CoordinateDriver, globalState, @@ -85,7 +83,7 @@ export class CoordinateTopology { onConnectSse: async (opts: ConnectSseOpts): Promise => { return await serveSse( registryConfig, - driverConfig, + runConfig, workerDriver, CoordinateDriver, globalState, @@ -100,7 +98,7 @@ export class CoordinateTopology { onConnMessage: async (opts: ConnsMessageOpts): Promise => { await publishMessageToLeader( registryConfig, - driverConfig, + runConfig, CoordinateDriver, globalState, opts.workerId, @@ -124,14 +122,14 @@ export class CoordinateTopology { }; // Create driver - const managerDriver = driverConfig.drivers.manager; + const managerDriver = runConfig.driver.manager; invariant(managerDriver, "missing manager driver"); this.clientDriver = createInlineClientDriver(managerDriver, routingHandler); // Build manager router const managerRouter = createManagerRouter( registryConfig, - driverConfig, + runConfig, this.clientDriver, { routingHandler, diff --git a/packages/core/src/topologies/coordinate/worker-peer.ts b/packages/core/src/topologies/coordinate/worker-peer.ts index 39b290ef6..6e8d1ee7f 100644 --- a/packages/core/src/topologies/coordinate/worker-peer.ts +++ b/packages/core/src/topologies/coordinate/worker-peer.ts @@ -8,12 +8,12 @@ import { CONN_DRIVER_COORDINATE_RELAY, createCoordinateRelayDriver, } from "./conn/driver"; -import { DriverConfig } from "@/driver-helpers/config"; import { RegistryConfig, RegistryConfigSchema } from "@/registry/config"; +import { DriverConfig, RunConfig } from "@/registry/run-config"; export class WorkerPeer { #registryConfig: RegistryConfig; - #driverConfig: DriverConfig; + #runConfig: RunConfig; #coordinateDriver: CoordinateDriver; #workerDriver: WorkerDriver; #globalState: GlobalState; @@ -44,14 +44,14 @@ export class WorkerPeer { constructor( registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, CoordinateDriver: CoordinateDriver, workerDriver: WorkerDriver, globalState: GlobalState, workerId: string, ) { this.#registryConfig = registryConfig; - this.#driverConfig = driverConfig; + this.#runConfig = runConfig; this.#coordinateDriver = CoordinateDriver; this.#workerDriver = workerDriver; this.#globalState = globalState; @@ -61,7 +61,7 @@ export class WorkerPeer { /** Acquires a `WorkerPeer` for a connection and includes the connection ID in the references. */ static async acquire( registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, workerDriver: WorkerDriver, CoordinateDriver: CoordinateDriver, globalState: GlobalState, @@ -74,7 +74,7 @@ export class WorkerPeer { if (!peer) { peer = new WorkerPeer( registryConfig, - driverConfig, + runConfig, CoordinateDriver, workerDriver, globalState, @@ -133,7 +133,7 @@ export class WorkerPeer { const { worker } = await this.#coordinateDriver.startWorkerAndAcquireLease( this.#workerId, this.#globalState.nodeId, - this.#registryConfig.workerPeer.leaseDuration, + this.#runConfig.workerPeer.leaseDuration, ); // Log logger().debug("starting worker peer", { @@ -189,13 +189,13 @@ export class WorkerPeer { let hbTimeout: number; if (this.#isLeader) { hbTimeout = - this.#registryConfig.workerPeer.leaseDuration - - this.#registryConfig.workerPeer.renewLeaseGrace; + this.#runConfig.workerPeer.leaseDuration - + this.#runConfig.workerPeer.renewLeaseGrace; } else { // TODO: Add jitter hbTimeout = - this.#registryConfig.workerPeer.checkLeaseInterval + - Math.random() * this.#registryConfig.workerPeer.checkLeaseJitter; + this.#runConfig.workerPeer.checkLeaseInterval + + Math.random() * this.#runConfig.workerPeer.checkLeaseJitter; } if (hbTimeout < 0) throw new Error("Worker peer heartbeat timeout is negative, check config"); @@ -240,7 +240,7 @@ export class WorkerPeer { const { leaseValid } = await this.#coordinateDriver.extendLease( this.#workerId, this.#globalState.nodeId, - this.#registryConfig.workerPeer.leaseDuration, + this.#runConfig.workerPeer.leaseDuration, ); if (leaseValid) { logger().debug("lease is valid", { workerId: this.#workerId }); @@ -260,7 +260,7 @@ export class WorkerPeer { await this.#coordinateDriver.attemptAcquireLease( this.#workerId, this.#globalState.nodeId, - this.#registryConfig.workerPeer.leaseDuration, + this.#runConfig.workerPeer.leaseDuration, ); // Check if the lease was successfully acquired and promoted to leader diff --git a/packages/core/src/topologies/partition/topology.ts b/packages/core/src/topologies/partition/topology.ts index cf7530bb4..463f807f1 100644 --- a/packages/core/src/topologies/partition/topology.ts +++ b/packages/core/src/topologies/partition/topology.ts @@ -21,7 +21,6 @@ import { } from "../common/generic-conn-driver"; import type { ConnDriver } from "@/worker/driver"; import type { WorkerKey } from "@/common/utils"; -import type { DriverConfig } from "@/driver-helpers/config"; import type { RegistryConfig } from "@/registry/config"; import { createManagerRouter } from "@/manager/router"; import type { @@ -45,6 +44,7 @@ import { } from "@/worker/conn-routing-handler"; import invariant from "invariant"; import type { WebSocket } from "ws"; +import type { DriverConfig, RunConfig } from "@/registry/run-config"; export type SendRequestHandler = ( workerRequest: Request, @@ -62,20 +62,20 @@ export class PartitionTopologyManager { constructor( registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, customRoutingHandlers: ConnRoutingHandlerCustom, ) { const routingHandler: ConnRoutingHandler = { custom: customRoutingHandlers, }; - const managerDriver = driverConfig.drivers.manager; + const managerDriver = runConfig.driver.manager; invariant(managerDriver, "missing manager driver"); this.clientDriver = createInlineClientDriver(managerDriver, routingHandler); this.router = createManagerRouter( registryConfig, - driverConfig, + runConfig, this.clientDriver, { routingHandler, @@ -113,7 +113,7 @@ export class PartitionTopologyWorker { router: Hono; #registryConfig: RegistryConfig; - #driverConfig: DriverConfig; + #runConfig: RunConfig; #connDrivers: Record; #worker?: AnyWorkerInstance; @@ -127,15 +127,15 @@ export class PartitionTopologyWorker { **/ #workerStartedPromise?: PromiseWithResolvers = Promise.withResolvers(); - constructor(registryConfig: RegistryConfig, driverConfig: DriverConfig) { + constructor(registryConfig: RegistryConfig, runConfig: RunConfig) { this.#registryConfig = registryConfig; - this.#driverConfig = driverConfig; + this.#runConfig = runConfig; const genericConnGlobalState = new GenericConnGlobalState(); this.#connDrivers = createGenericConnDrivers(genericConnGlobalState); // TODO: Store this worker router globally so we're not re-initializing it for every DO - this.router = createWorkerRouter(registryConfig, driverConfig, { + this.router = createWorkerRouter(registryConfig, runConfig, { getWorkerId: async () => { if (this.#workerStartedPromise) await this.#workerStartedPromise.promise; @@ -332,8 +332,7 @@ export class PartitionTopologyWorker { } async start(id: string, name: string, key: WorkerKey, region: string) { - const workerDriver = this.#driverConfig.drivers?.worker; - if (!workerDriver) throw new Error("config.drivers.worker not defined."); + const workerDriver = this.#runConfig.driver.worker; // Find worker prototype const definition = this.#registryConfig.workers[name]; diff --git a/packages/core/src/topologies/partition/worker-router.ts b/packages/core/src/topologies/partition/worker-router.ts index 7cfe0b4df..21f3bba4c 100644 --- a/packages/core/src/topologies/partition/worker-router.ts +++ b/packages/core/src/topologies/partition/worker-router.ts @@ -6,7 +6,6 @@ import { handleRouteNotFound, loggerMiddleware, } from "@/common/router"; -import type { DriverConfig } from "@/driver-helpers/config"; import type { RegistryConfig } from "@/registry/config"; import { type ConnectWebSocketOpts, @@ -30,6 +29,7 @@ import { } from "@/worker/router-endpoints"; import invariant from "invariant"; import { EncodingSchema } from "@/worker/protocol/serde"; +import { DriverConfig, RunConfig } from "@/registry/run-config"; export type { ConnectWebSocketOpts, @@ -55,39 +55,15 @@ export interface WorkerRouterHandler { */ export function createWorkerRouter( registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, handler: WorkerRouterHandler, ): Hono { const router = new Hono(); - const upgradeWebSocket = driverConfig.getUpgradeWebSocket?.(router); + const upgradeWebSocket = runConfig.getUpgradeWebSocket?.(router); router.use("*", loggerMiddleware(logger())); - // Apply CORS middleware if configured - // - //This is only relevant if the worker is exposed directly publicly - if (registryConfig.cors) { - const corsConfig = registryConfig.cors; - - router.use("*", async (c, next) => { - const path = c.req.path; - - // Don't apply to WebSocket routes, see https://hono.dev/docs/helpers/websocket#upgradewebsocket - if (path === "/connect/websocket" || path === "/inspect") { - return next(); - } - - return cors({ - ...corsConfig, - allowHeaders: [ - ...(registryConfig.cors?.allowHeaders ?? []), - ...ALL_PUBLIC_HEADERS, - ], - })(c, next); - }); - } - router.get("/", (c) => { return c.text( "This is an RivetKit server.\n\nLearn more at https://rivetkit.org", @@ -119,6 +95,7 @@ export function createWorkerRouter( return handleWebSocketConnect( c as HonoContext, registryConfig, + runConfig, handlers.onConnectWebSocket!, workerId, encoding, @@ -151,7 +128,7 @@ export function createWorkerRouter( return handleSseConnect( c, registryConfig, - driverConfig, + runConfig, handlers.onConnectSse, workerId, authData, @@ -174,7 +151,7 @@ export function createWorkerRouter( return handleAction( c, registryConfig, - driverConfig, + runConfig, handlers.onAction, actionName, workerId, @@ -195,6 +172,7 @@ export function createWorkerRouter( return handleConnectionMessage( c, registryConfig, + runConfig, handlers.onConnMessage, connId, connToken, diff --git a/packages/core/src/topologies/standalone/topology.ts b/packages/core/src/topologies/standalone/topology.ts index c60f1ec7d..bc2c67f52 100644 --- a/packages/core/src/topologies/standalone/topology.ts +++ b/packages/core/src/topologies/standalone/topology.ts @@ -18,7 +18,6 @@ import { type GenericWebSocketDriverState, } from "../common/generic-conn-driver"; import { ActionContext } from "@/worker/action"; -import type { DriverConfig } from "@/driver-helpers/config"; import type { RegistryConfig } from "@/registry/config"; import { createManagerRouter } from "@/manager/router"; import type { @@ -35,6 +34,7 @@ import { createInlineClientDriver } from "@/inline-client-driver/mod"; import invariant from "invariant"; import { ClientDriver } from "@/client/client"; import { ConnRoutingHandler } from "@/worker/conn-routing-handler"; +import { DriverConfig, RunConfig } from "@/mod"; class WorkerHandler { /** Will be undefined if not yet loaded. */ @@ -55,7 +55,7 @@ export class StandaloneTopology { router: Hono; #registryConfig: RegistryConfig; - #driverConfig: DriverConfig; + #runConfig: RunConfig; #workers = new Map(); async #getWorker( @@ -76,14 +76,8 @@ export class StandaloneTopology { handler = new WorkerHandler(); this.#workers.set(workerId, handler); - // Validate config - if (!this.#driverConfig.drivers?.worker) - throw new Error("config.drivers.worker is not defined."); - if (!this.#driverConfig.drivers?.manager) - throw new Error("config.drivers.manager is not defined."); - // Load worker meta - const workerMetadata = await this.#driverConfig.drivers.manager.getForId({ + const workerMetadata = await this.#runConfig.driver.manager.getForId({ workerId, }); if (!workerMetadata) throw new Error(`No worker found for ID ${workerId}`); @@ -105,7 +99,7 @@ export class StandaloneTopology { // Start worker await handler.worker.start( connDrivers, - this.#driverConfig.drivers.worker, + this.#runConfig.driver.worker, workerId, workerMetadata.name, workerMetadata.key, @@ -119,17 +113,14 @@ export class StandaloneTopology { return { handler, worker }; } - constructor(registryConfig: RegistryConfig, driverConfig: DriverConfig) { + constructor(registryConfig: RegistryConfig, runConfig: RunConfig) { this.#registryConfig = registryConfig; - this.#driverConfig = driverConfig; - - if (!driverConfig.drivers?.worker) - throw new Error("config.drivers.worker not defined."); + this.#runConfig = runConfig; // Build router const router = new Hono(); - const upgradeWebSocket = driverConfig.getUpgradeWebSocket?.(router); + const upgradeWebSocket = runConfig.getUpgradeWebSocket?.(router); // Create shared connection handlers that will be used by both manager and worker routers const sharedConnectionHandlers: ConnectionHandlers = { @@ -217,7 +208,10 @@ export class StandaloneTopology { const { worker } = await this.#getWorker(opts.workerId); // Create conn - const connState = await worker.prepareConn(opts.params, opts.req?.raw); + const connState = await worker.prepareConn( + opts.params, + opts.req?.raw, + ); conn = await worker.createConn( generateConnId(), generateConnToken(), @@ -268,38 +262,43 @@ export class StandaloneTopology { }; // Build client driver - const managerDriver = this.#driverConfig.drivers.manager; + const managerDriver = this.#runConfig.driver.manager; invariant(managerDriver, "missing manager driver"); this.clientDriver = createInlineClientDriver(managerDriver, routingHandler); // Build manager router - const managerRouter = createManagerRouter(registryConfig, driverConfig, 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); - // } - // }, - // }; - // }, - }); + 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); diff --git a/packages/core/src/worker/conn-routing-handler.ts b/packages/core/src/worker/conn-routing-handler.ts index c64277ab8..ac80d3e8a 100644 --- a/packages/core/src/worker/conn-routing-handler.ts +++ b/packages/core/src/worker/conn-routing-handler.ts @@ -2,7 +2,6 @@ import type { UpgradeWebSocket } from "@/utils"; import type { Encoding } from "./protocol/serde"; import type { ConnectionHandlers as ConnHandlers } from "./router-endpoints"; import type { Context as HonoContext } from "hono"; -import type { WebSocket } from "ws"; /** * Deterines how requests to workers should be routed. diff --git a/packages/core/src/worker/router-endpoints.ts b/packages/core/src/worker/router-endpoints.ts index e0c1b7874..38beff692 100644 --- a/packages/core/src/worker/router-endpoints.ts +++ b/packages/core/src/worker/router-endpoints.ts @@ -16,7 +16,7 @@ import type { InputData } from "@/worker/protocol/serde"; import { assertUnreachable } from "./utils"; import { deconstructError, stringifyError } from "@/common/utils"; import type { RegistryConfig } from "@/registry/config"; -import type { DriverConfig } from "@/driver-helpers/config"; +import { DriverConfig, RunConfig } from "@/registry/run-config"; export interface ConnectWebSocketOpts { req?: HonoRequest; @@ -84,6 +84,7 @@ export interface ConnectionHandlers { export function handleWebSocketConnect( context: HonoContext, registryConfig: RegistryConfig, + runConfig: RunConfig, handler: (opts: ConnectWebSocketOpts) => Promise, workerId: string, encoding: Encoding, @@ -140,7 +141,7 @@ export function handleWebSocketConnect( const value = evt.data.valueOf() as InputData; const message = await parseMessage(value, { encoding: encoding, - maxIncomingMessageSize: registryConfig.maxIncomingMessageSize, + maxIncomingMessageSize: runConfig.maxIncomingMessageSize, }); await wsHandler.onMessage(message); @@ -216,13 +217,13 @@ export function handleWebSocketConnect( export async function handleSseConnect( c: HonoContext, registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, handler: (opts: ConnectSseOpts) => Promise, workerId: string, authData: unknown, ) { const encoding = getRequestEncoding(c.req); - const parameters = getRequestConnParams(c.req, registryConfig, driverConfig); + const parameters = getRequestConnParams(c.req, registryConfig, runConfig); const sseHandler = await handler({ req: c.req, @@ -274,14 +275,14 @@ export async function handleSseConnect( export async function handleAction( c: HonoContext, registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, handler: (opts: ActionOpts) => Promise, actionName: string, workerId: string, authData: unknown, ) { const encoding = getRequestEncoding(c.req); - const parameters = getRequestConnParams(c.req, registryConfig, driverConfig); + const parameters = getRequestConnParams(c.req, registryConfig, runConfig); logger().debug("handling action", { actionName, encoding }); @@ -359,6 +360,7 @@ export async function handleAction( export async function handleConnectionMessage( c: HonoContext, registryConfig: RegistryConfig, + runConfig: RunConfig, handler: (opts: ConnsMessageOpts) => Promise, connId: string, connToken: string, @@ -380,7 +382,7 @@ export async function handleConnectionMessage( const uint8Array = new Uint8Array(value); message = await parseMessage(uint8Array as unknown as InputData, { encoding, - maxIncomingMessageSize: registryConfig.maxIncomingMessageSize, + maxIncomingMessageSize: runConfig.maxIncomingMessageSize, }); } catch (err) { throw new errors.InvalidRequest( @@ -481,7 +483,7 @@ export const ALL_PUBLIC_HEADERS = [ export function getRequestConnParams( req: HonoRequest, registryConfig: RegistryConfig, - driverConfig: DriverConfig, + runConfig: RunConfig, ): unknown { const paramsParam = req.header(HEADER_CONN_PARAMS); if (!paramsParam) { diff --git a/packages/core/tests/driver-test-suite.test.ts b/packages/core/tests/driver-test-suite.test.ts index da8becd01..dd2c241c6 100644 --- a/packages/core/tests/driver-test-suite.test.ts +++ b/packages/core/tests/driver-test-suite.test.ts @@ -1,17 +1,16 @@ import { runDriverTests, createTestRuntime } from "@/driver-test-suite/mod"; -import { TestGlobalState } from "@/test/driver/global-state"; -import { TestWorkerDriver } from "@/test/driver/worker"; -import { TestManagerDriver } from "@/test/driver/manager"; +import { createTestDriver } from "@/test/driver/mod"; import { join } from "node:path"; runDriverTests({ async start(projectPath: string) { - return await createTestRuntime(join(projectPath, "registry.ts"), async (registry) => { - const memoryState = new TestGlobalState(); - return { - workerDriver: new TestWorkerDriver(memoryState), - managerDriver: new TestManagerDriver(registry, memoryState), - }; - }); + return await createTestRuntime( + join(projectPath, "registry.ts"), + async () => { + return { + driver: createTestDriver(), + }; + }, + ); }, }); diff --git a/packages/drivers/file-system/README.md b/packages/drivers/file-system/README.md new file mode 100644 index 000000000..2f27893c3 --- /dev/null +++ b/packages/drivers/file-system/README.md @@ -0,0 +1,11 @@ +# RivetKit File System 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/file-system/package.json b/packages/drivers/file-system/package.json index c8b192232..1bd900f02 100644 --- a/packages/drivers/file-system/package.json +++ b/packages/drivers/file-system/package.json @@ -1,6 +1,7 @@ { "name": "@rivetkit/file-system", "version": "0.9.0-rc.1", + "keywords": ["rivetkit", "driver", "filesystem", "storage", "development", "persistence"], "files": [ "src", "dist", diff --git a/packages/drivers/memory/README.md b/packages/drivers/memory/README.md new file mode 100644 index 000000000..40e452832 --- /dev/null +++ b/packages/drivers/memory/README.md @@ -0,0 +1,11 @@ +# 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 index 50460548e..112cccfe9 100644 --- a/packages/drivers/memory/package.json +++ b/packages/drivers/memory/package.json @@ -1,6 +1,7 @@ { "name": "@rivetkit/memory", "version": "0.9.0-rc.1", + "keywords": ["rivetkit", "driver", "memory", "storage", "development", "testing"], "files": [ "src", "dist", diff --git a/packages/drivers/memory/src/manager.ts b/packages/drivers/memory/src/manager.ts index 8e97b2528..ca9ad294e 100644 --- a/packages/drivers/memory/src/manager.ts +++ b/packages/drivers/memory/src/manager.ts @@ -9,7 +9,6 @@ import type { import { WorkerAlreadyExists } from "rivetkit/errors"; import type { MemoryGlobalState } from "./global-state"; import * as crypto from "node:crypto"; -import type { Registry } from "rivetkit"; export class MemoryManagerDriver implements ManagerDriver { #state: MemoryGlobalState; @@ -19,10 +18,7 @@ export class MemoryManagerDriver implements ManagerDriver { // getAllTypesOfWorkers: () => Object.keys(this.registry.config.workers), // }); - constructor( - private readonly registry: Registry, - state: MemoryGlobalState, - ) { + constructor(state: MemoryGlobalState) { this.#state = state; } diff --git a/packages/drivers/memory/src/mod.ts b/packages/drivers/memory/src/mod.ts index 0a3bccc44..d94cdb853 100644 --- a/packages/drivers/memory/src/mod.ts +++ b/packages/drivers/memory/src/mod.ts @@ -1,3 +1,13 @@ -export { MemoryGlobalState } from "./global-state"; -export { MemoryWorkerDriver } from "./worker"; -export { MemoryManagerDriver } from "./manager"; +import type { DriverConfig } from "rivetkit"; +import { MemoryManagerDriver } from "./manager"; +import { MemoryGlobalState } from "./global-state"; +import { MemoryWorkerDriver } from "./worker"; + +export function createMemoryDriver(): DriverConfig { + const state = new MemoryGlobalState(); + return { + topology: "standalone", + manager: new MemoryManagerDriver(state), + worker: new MemoryWorkerDriver(state), + }; +} diff --git a/packages/drivers/memory/tests/driver-tests.test.ts b/packages/drivers/memory/tests/driver-tests.test.ts index 21c434e18..387519373 100644 --- a/packages/drivers/memory/tests/driver-tests.test.ts +++ b/packages/drivers/memory/tests/driver-tests.test.ts @@ -1,17 +1,12 @@ +import { MemoryGlobalState } from "@/global-state"; +import { createMemoryDriver } from "@/mod"; import { runDriverTests, createTestRuntime } from "rivetkit/driver-test-suite"; -import { - MemoryWorkerDriver, - MemoryManagerDriver, - MemoryGlobalState, -} from "../src/mod"; runDriverTests({ async start(appPath: string) { - return await createTestRuntime(appPath, async (registry) => { - const memoryState = new MemoryGlobalState(); + return await createTestRuntime(appPath, async () => { return { - workerDriver: new MemoryWorkerDriver(memoryState), - managerDriver: new MemoryManagerDriver(registry, memoryState), + driver: createMemoryDriver(), }; }); }, diff --git a/packages/drivers/redis/README.md b/packages/drivers/redis/README.md new file mode 100644 index 000000000..91bc55c57 --- /dev/null +++ b/packages/drivers/redis/README.md @@ -0,0 +1,11 @@ +# RivetKit Redis 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/redis/package.json b/packages/drivers/redis/package.json index 1b11e3f8f..e915063d8 100644 --- a/packages/drivers/redis/package.json +++ b/packages/drivers/redis/package.json @@ -1,6 +1,7 @@ { "name": "@rivetkit/redis", "version": "0.9.0-rc.1", + "keywords": ["rivetkit", "driver", "redis", "storage", "production", "coordination", "persistence"], "files": [ "src", "dist", diff --git a/packages/frameworks/framework-base/README.md b/packages/frameworks/framework-base/README.md new file mode 100644 index 000000000..81b8867ee --- /dev/null +++ b/packages/frameworks/framework-base/README.md @@ -0,0 +1,11 @@ +# RivetKit Framework Base + +_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/frameworks/framework-base/lib/mod.ts b/packages/frameworks/framework-base/lib/mod.ts index ea5bcf08f..3fcbb364b 100644 --- a/packages/frameworks/framework-base/lib/mod.ts +++ b/packages/frameworks/framework-base/lib/mod.ts @@ -7,6 +7,7 @@ import type { WorkerHandle, } from "rivetkit/client"; +// biome-ignore lint/suspicious/noExplicitAny: its a generic worker registry export type AnyWorkerRegistry = Registry; interface WorkerStateReference { @@ -104,7 +105,11 @@ export interface WorkerOptions< enabled?: boolean; } +// biome-ignore lint/suspicious/noExplicitAny: worker name can be anything +export type AnyWorkerOptions = WorkerOptions; + export interface CreateRivetKitOptions { + // biome-ignore lint/suspicious/noExplicitAny: worker name can be anything hashFunction?: (opts: WorkerOptions) => string; } @@ -131,6 +136,7 @@ export function createRivetKit< create: () => void; addEventListener?: ( event: string, + // biome-ignore lint/suspicious/noExplicitAny: need any specific type here handler: (...args: any[]) => void, ) => void; } @@ -174,29 +180,36 @@ export function createRivetKit< await handle.resolve(/*{ signal: AbortSignal.timeout(0) }*/); store.setState((prev) => { - const prevWorker = prev.workers[key]; - prev.workers[key] = { - ...prevWorker, - isConnected: true, - isConnecting: false, - handle: handle as WorkerHandle, - connection: connection as WorkerConn, - isError: false, - error: null, + return { + ...prev, + workers: { + ...prev.workers, + [key]: { + ...prev.workers[key], + isConnected: true, + isConnecting: false, + handle: handle as WorkerHandle, + connection: connection as WorkerConn, + isError: false, + error: null, + }, + }, }; - return prev; }); } catch (error) { store.setState((prev) => { - const prevWorker = prev.workers[key]; - prev.workers[key] = { - ...prevWorker, - isError: true, - isConnecting: false, - error: error as Error, + return { + ...prev, + workers: { + ...prev.workers, + [key]: { + ...prev.workers[key], + isError: true, + isConnecting: false, + error: error as Error, + }, + }, }; - - return prev; }); } } @@ -215,7 +228,6 @@ export function createRivetKit< fn: () => { // check if prev state is different from current state // do a shallow comparison - const worker = store.state.workers[key]; const isSame = @@ -239,17 +251,22 @@ export function createRivetKit< if (prev.workers[key]) { return prev; } - prev.workers[key] = { - hash: key, - isConnected: false, - isConnecting: false, - connection: null, - handle: null, - isError: false, - error: null, - opts, + return { + ...prev, + workers: { + ...prev.workers, + [key]: { + hash: key, + isConnected: false, + isConnecting: false, + connection: null, + handle: null, + isError: false, + error: null, + opts, + }, + }, }; - return prev; }); function setState(updater: Updater) { @@ -259,13 +276,21 @@ export function createRivetKit< throw new Error(`Worker with key "${key}" does not exist.`); } + let newState: RivetKitStore["workers"][string]; + if (typeof updater === "function") { - prev.workers[key] = updater(worker); + newState = updater(worker); } else { // If updater is a direct value, we assume it replaces the entire worker state - prev.workers[key] = updater; + newState = updater; } - return prev; + return { + ...prev, + workers: { + ...prev.workers, + [key]: newState, + }, + }; }); } @@ -308,6 +333,6 @@ export function createRivetKit< }; } -function defaultHashFunction({ name, key, params }: WorkerOptions) { +function defaultHashFunction({ name, key, params }: AnyWorkerOptions) { return JSON.stringify({ name, key, params }); } diff --git a/packages/frameworks/framework-base/package.json b/packages/frameworks/framework-base/package.json index 4e9b73c01..e751e3460 100644 --- a/packages/frameworks/framework-base/package.json +++ b/packages/frameworks/framework-base/package.json @@ -2,6 +2,7 @@ "name": "@rivetkit/framework-base", "version": "0.9.0-rc.1", "license": "Apache-2.0", + "keywords": ["rivetkit", "framework", "base", "utilities", "integration"], "sideEffects": false, "type": "module", "files": [ diff --git a/packages/frameworks/framework-base/src/main.ts b/packages/frameworks/framework-base/src/main.ts deleted file mode 100644 index 766b6e5e0..000000000 --- a/packages/frameworks/framework-base/src/main.ts +++ /dev/null @@ -1 +0,0 @@ -console.log("hell2222o"); diff --git a/packages/frameworks/framework-base/src/mod.ts b/packages/frameworks/framework-base/src/mod.ts deleted file mode 100644 index a7e179ef5..000000000 --- a/packages/frameworks/framework-base/src/mod.ts +++ /dev/null @@ -1,148 +0,0 @@ -//import type { -// WorkerConn, -// WorkerAccessor, -// ExtractAppFromClient, -// ExtractWorkersFromApp, -// ClientRaw, -// AnyWorkerDefinition, -//} from "rivetkit/client"; -// -///** -// * Shallow compare objects. -// * Copied from https://github.com/TanStack/query/blob/3c5d8e348cc53e46aea6c74767f3181fc77c2308/packages/query-core/src/utils.ts#L298-L299 -// */ -//export function shallowEqualObjects< -// // biome-ignore lint/suspicious/noExplicitAny: we do not care about the shape -// T extends Record, -//>(a: T | undefined, b: T | undefined): boolean { -// if (a === undefined && b === undefined) { -// return true; -// } -// if (!a || !b || Object.keys(a).length !== Object.keys(b).length) { -// return false; -// } -// -// for (const key in a) { -// if (a[key] !== b[key]) { -// if (typeof a[key] === "object" && typeof b[key] === "object") { -// return shallowEqualObjects(a[key], b[key]); -// } -// return false; -// } -// } -// -// return true; -//} -// -//namespace State { -// export type Value = -// | { state: "init"; worker: undefined; isLoading: false } -// | { state: "creating"; worker: undefined; isLoading: true } -// | { state: "created"; worker: WorkerConn; isLoading: false } -// | { state: "error"; error: unknown; worker: undefined; isLoading: false }; -// -// export const INIT = (): Value => ({ -// state: "init", -// worker: undefined, -// isLoading: false, -// }); -// export const CREATING = (): Value => ({ -// state: "creating", -// worker: undefined, -// isLoading: true, -// }); -// export const CREATED = ( -// worker: WorkerConn, -// ): Value => ({ -// state: "created", -// worker, -// isLoading: false, -// }); -// export const ERRORED = ( -// error: unknown, -// ): Value => ({ -// state: "error", -// worker: undefined, -// error, -// isLoading: false, -// }); -//} -// -//export class WorkerManager< -// C extends ClientRaw, -// Registry extends ExtractAppFromClient, -// Registry extends ExtractWorkersFromRegistry, -// WorkerName extends keyof Registry, -// AD extends Registry[WorkerName], -//> { -// #client: C; -// #name: Exclude; -// #options: Parameters["connect"]>; -// -// #listeners: (() => void)[] = []; -// -// #state: State.Value = State.INIT(); -// -// #createPromise: Promise> | null = null; -// -// constructor( -// client: C, -// name: Exclude, -// options: Parameters["connect"]>, -// ) { -// this.#client = client; -// this.#name = name; -// this.#options = options; -// } -// -// setOptions(options: Parameters["connect"]>) { -// if (shallowEqualObjects(options, this.#options)) { -// if (!this.#state.worker) { -// this.create(); -// } -// return; -// } -// -// this.#state.worker?.dispose(); -// -// this.#state = { ...State.INIT() }; -// this.#options = options; -// this.#update(); -// this.create(); -// } -// -// async create() { -// if (this.#createPromise) { -// return this.#createPromise; -// } -// this.#state = { ...State.CREATING() }; -// this.#update(); -// try { -// this.#createPromise = this.#client.connect(this.#name, ...this.#options); -// const worker = (await this.#createPromise) as WorkerConn; -// this.#state = { ...State.CREATED(worker) }; -// this.#createPromise = null; -// } catch (e) { -// this.#state = { ...State.ERRORED(e) }; -// } finally { -// this.#update(); -// } -// } -// -// getState() { -// return this.#state; -// } -// -// subscribe(cb: () => void) { -// this.#listeners.push(cb); -// return () => { -// this.#listeners = this.#listeners.filter((l) => l !== cb); -// }; -// } -// -// #update() { -// for (const cb of this.#listeners) { -// cb(); -// } -// } -//} diff --git a/packages/frameworks/framework-base/vite.config.ts b/packages/frameworks/framework-base/vite.config.ts index 98e5b0d92..c0be04cc1 100644 --- a/packages/frameworks/framework-base/vite.config.ts +++ b/packages/frameworks/framework-base/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ lib: { entry: resolve(__dirname, "lib/mod.ts"), fileName: "mod", - formats: ["es"], + formats: ["cjs", "es"], }, }, }); diff --git a/packages/frameworks/react/README.md b/packages/frameworks/react/README.md index 3b4aeb30c..bd8da28af 100644 --- a/packages/frameworks/react/README.md +++ b/packages/frameworks/react/README.md @@ -1,83 +1,11 @@ # RivetKit React -🎭 React integration for [RivetKit](https://rivetkit.org/) +_Lightweight Libraries for Backends_ -> [!NOTE] -> Looking for the integration with your favorite framework? Let us know by creating an issue on GitHub, or on [Discord](https://rivet.gg/discord). -> If you want to contribute, check out the [contribution guide](../../../CONTRIBUTING.md). +[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) -## Installation +## License -1. First install the package: - -```bash -# npm -npm add @rivetkit/react - -# pnpm -pnpm add @rivetkit/react - -# Yarn -yarn add @rivetkit/react - -# Bun -bun add @rivetkit/react -``` - -## Quick Start - -```tsx -import { createClient, createRivetKit } from "@rivetkit/react"; -import type { Registry } from "../counter/src/index"; -import React, { useState } from "react"; - -// Create a client -const client = createClient("http://your-rivetkit-server.com"); - -// Create React hooks for your workers -const { useWorker } = createRivetKit(client); - -function ReactApp() { - return ( - <> - - - ); -} - -function Counter() { - // Get or create a Worker - // This will create a new worker if it doesn't exist - // using the hook with the same parameters will return the same worker without creating a new one - const worker = useWorker({ - name: "counter", - }); - - return ( -
- - -
- ); -} - -function CounterValue({ worker }) { - const [count, setCount] = useState(0); - - // Listen to events - worker.useEvent("newCount", (newCount) => { - setCount(newCount); - }); - - return count; -} - -render(, document.getElementById("root")); -``` +Apache 2.0 \ No newline at end of file diff --git a/packages/frameworks/react/lib/mod.tsx b/packages/frameworks/react/lib/mod.tsx deleted file mode 100644 index 97be1da21..000000000 --- a/packages/frameworks/react/lib/mod.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useStore } from "@tanstack/react-store"; -import { - type AnyWorkerRegistry, - type CreateRivetKitOptions, - type WorkerOptions, - createRivetKit as createVanillaRivetKit, -} from "@rivetkit/framework-base"; -import type { Client, ExtractWorkersFromRegistry } from "rivetkit/client"; -import { useEffect } from "react"; - -export { createClient } from "rivetkit/client"; - -export function createRivetKit( - client: Client, - opts: CreateRivetKitOptions = {}, -) { - const { getOrCreateWorker } = createVanillaRivetKit< - Registry, - ExtractWorkersFromRegistry, - keyof ExtractWorkersFromRegistry - >(client, opts); - - /** - * Hook to connect to a worker and retrieve its state. Using this hook with the same options - * will return the same worker instance. This simplifies passing around the worker state in your components. - * It also provides a method to listen for events emitted by the worker. - * @param opts - Options for the worker, including its name, key, and parameters. - * @returns An object containing the worker's state and a method to listen for events. - */ - function useWorker>( - opts: WorkerOptions, - ) { - const { mount, setState, state } = getOrCreateWorker(opts); - - useEffect(() => { - setState((prev) => { - prev.opts = { - ...prev.opts, - ...opts, - params: { - ...prev.opts.params, - ...opts.params, - }, - enabled: opts.enabled ?? true, - }; - return prev; - }); - }, [opts, setState]); - - useEffect(() => { - return mount(); - }, [mount]); - - const workerState = useStore(state) || {}; - - /** - * Hook to listen for events emitted by the worker. - * This hook allows you to subscribe to specific events emitted by the worker and execute a handler function - * when the event occurs. - * It uses the `useEffect` hook to set up the event listener when the worker connection is established. - * It cleans up the listener when the component unmounts or when the worker connection changes. - * @param eventName The name of the event to listen for. - * @param handler The function to call when the event is emitted. - */ - const useEvent = (eventName: string, handler: (data: any) => void) => { - useEffect(() => { - if (!workerState?.connection) return; - - const connection = workerState.connection; - return connection.on(eventName, handler); - }, [workerState.connection, eventName, handler]); - }; - return { - ...workerState, - useEvent, - }; - } - - return { - useWorker, - }; -} diff --git a/packages/frameworks/react/lib/vite-env.d.ts b/packages/frameworks/react/lib/vite-env.d.ts deleted file mode 100644 index 11f02fe2a..000000000 --- a/packages/frameworks/react/lib/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/packages/frameworks/react/package.json b/packages/frameworks/react/package.json index ac4dfe3e4..f90ef93ef 100644 --- a/packages/frameworks/react/package.json +++ b/packages/frameworks/react/package.json @@ -2,11 +2,9 @@ "name": "@rivetkit/react", "version": "0.9.0-rc.1", "license": "Apache-2.0", + "keywords": ["rivetkit", "react", "hooks", "components", "frontend", "ui"], "sideEffects": false, - "files": [ - "dist", - "package.json" - ], + "files": ["dist", "package.json"], "exports": { ".": { "import": { @@ -20,8 +18,7 @@ } }, "scripts": { - "dev": "vite", - "build": "tsc -p ./tsconfig.build.json && vite build", + "build": "tsup src/mod.tsx", "check-types": "tsc --noEmit" }, "dependencies": { @@ -36,12 +33,9 @@ "devDependencies": { "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.2", "rivetkit": "workspace:^", - "typescript": "^5.5.2", - "vite": "^6.3.5", - "vite-plugin-dts": "^4.5.4", - "vitest": "^3.1.1" + "tsup": "^8.4.0", + "typescript": "^5.5.2" }, "stableVersion": "0.8.0" } diff --git a/packages/frameworks/react/src/App.tsx b/packages/frameworks/react/src/App.tsx deleted file mode 100644 index 7b53985e6..000000000 --- a/packages/frameworks/react/src/App.tsx +++ /dev/null @@ -1,96 +0,0 @@ -// import { useState } from "react"; -// import type { Registry } from "../../../../examples/chat-room/workers/app"; -// import { createClient, createRivetKit } from "../lib/mod"; -// -// const client = createClient("http://localhost:6420", { -// encoding: "json", -// }); -// -// const { useWorker } = createRivetKit(client); -// -// function App() { -// const [state, setState] = useState(0); -// -// return ( -// <> -// -//

Rivet Kit + React

-//
-// -// -// -// -// -// -// -// -// -// -// -// -// -//
-// -// ); -// } -// -// function WorkerReceiver({ key, params }: { key: string; params: any } = {}) { -// const worker = useWorker({ -// name: "chatRoom", -// key, -// params, -// }); -// -// const { connection, handle, ...rest } = worker || {}; -// -// worker.useEvent("newMessage", (...args) => { -// console.log("Received message from worker:", ...args); -// }); -// -// return ( -//
-//

Worker Component

-//
{JSON.stringify(rest, null, 2)}
-//
-// ); -// } -// -// function WorkerWriter({ key, params }: { key: string; params: any }) { -// const worker = useWorker({ -// name: "chatRoom", -// key, -// params, -// }); -// return ( -//
-//

Worker Writer Component

-//
{ -// e.preventDefault(); -// const formData = new FormData(e.target as HTMLFormElement); -// const message = formData.get("message") as string; -// worker.connection?.sendMessage("username", message); -// (e.target as HTMLFormElement).reset(); -// }} -// > -// -// -//
-//
-// ); -// } -// -// export default App; diff --git a/packages/frameworks/react/src/main.tsx b/packages/frameworks/react/src/main.tsx deleted file mode 100644 index f9cc37fcc..000000000 --- a/packages/frameworks/react/src/main.tsx +++ /dev/null @@ -1,9 +0,0 @@ -// import { StrictMode } from "react"; -// import { createRoot } from "react-dom/client"; -// import App from "./App"; -// -// createRoot(document.getElementById("root")!).render( -// -// -// , -// ); diff --git a/packages/frameworks/react/src/mod.tsx b/packages/frameworks/react/src/mod.tsx index 0001db620..e32167eac 100644 --- a/packages/frameworks/react/src/mod.tsx +++ b/packages/frameworks/react/src/mod.tsx @@ -1,77 +1,82 @@ -//"use client"; -//import type { -// ActorAccessor, -// ActorConn, -// ExtractAppFromClient, -// ExtractActorsFromApp, -// ClientRaw, -//} from "rivetkit/client"; -//import { ActorManager } from "@rivetkit/framework-base"; -//import { -// useCallback, -// useEffect, -// useRef, -// useState, -// useSyncExternalStore, -//} from "react"; -// -//export function createReactRivetKit(client: Client) { -// type Registry = ExtractAppFromClient; -// type Registry = ExtractActorsFromRegistry; -// return { -// useActor: function useActor< -// N extends keyof Registry, -// AD extends Registry[N], -// >( -// name: Exclude, -// ...options: Parameters["connect"]> -// ) { -// const [manager] = useState( -// () => -// new ActorManager(client, name, options), -// ); -// -// const state = useSyncExternalStore( -// useCallback( -// (onUpdate) => { -// return manager.subscribe(onUpdate); -// }, -// [manager], -// ), -// () => manager.getState(), -// () => manager.getState(), -// ); -// -// useEffect(() => { -// manager.setOptions(options); -// }, [options, manager]); -// -// return [state] as const; -// }, -// useActorEvent( -// opts: { actor: ActorConn | undefined; event: string }, -// cb: (...args: unknown[]) => void, -// ) { -// const ref = useRef(cb); -// -// useEffect(() => { -// ref.current = cb; -// }, [cb]); -// -// useEffect(() => { -// if (!opts.actor) { -// return noop; -// } -// const unsub = opts.actor.on(opts.event, (...args: unknown[]) => { -// ref.current(...args); -// }); -// -// return unsub; -// }, [opts.actor, opts.event]); -// }, -// }; -//} -// -//function noop() { -// // noop -//} +import { useStore } from "@tanstack/react-store"; +import { + type AnyWorkerRegistry, + type CreateRivetKitOptions, + type WorkerOptions, + createRivetKit as createVanillaRivetKit, +} from "@rivetkit/framework-base"; +import type { Client, ExtractWorkersFromRegistry } from "rivetkit/client"; +import { useEffect } from "react"; + +export { createClient } from "rivetkit/client"; + +export function createRivetKit( + client: Client, + opts: CreateRivetKitOptions = {}, +) { + const { getOrCreateWorker } = createVanillaRivetKit< + Registry, + ExtractWorkersFromRegistry, + keyof ExtractWorkersFromRegistry + >(client, opts); + + /** + * Hook to connect to a worker and retrieve its state. Using this hook with the same options + * will return the same worker instance. This simplifies passing around the worker state in your components. + * It also provides a method to listen for events emitted by the worker. + * @param opts - Options for the worker, including its name, key, and parameters. + * @returns An object containing the worker's state and a method to listen for events. + */ + function useWorker< + WorkerName extends keyof ExtractWorkersFromRegistry, + >(opts: WorkerOptions) { + const { mount, setState, state } = getOrCreateWorker(opts); + + useEffect(() => { + setState((prev) => { + prev.opts = { + ...opts, + enabled: opts.enabled ?? true, + }; + return prev; + }); + }, [opts, setState]); + + useEffect(() => { + return mount(); + }, [mount]); + + const workerState = useStore(state) || {}; + + /** + * Hook to listen for events emitted by the worker. + * This hook allows you to subscribe to specific events emitted by the worker and execute a handler function + * when the event occurs. + * It uses the `useEffect` hook to set up the event listener when the worker connection is established. + * It cleans up the listener when the component unmounts or when the worker connection changes. + * @param eventName The name of the event to listen for. + * @param handler The function to call when the event is emitted. + */ + const useEvent = ( + eventName: string, + // biome-ignore lint/suspicious/noExplicitAny: strong typing of handler is not supported yet + handler: (...args: any[]) => void, + ) => { + // biome-ignore lint/correctness/useExhaustiveDependencies: it's okay to not include all dependencies here + useEffect(() => { + if (!workerState?.connection) return; + + const connection = workerState.connection; + return connection.on(eventName, handler); + }, [workerState.connection, workerState.isConnected, eventName, handler]); + }; + return { + ...workerState, + useEvent, + }; + } + + return { + useWorker, + }; +} diff --git a/packages/frameworks/react/src/vite-env.d.ts b/packages/frameworks/react/src/vite-env.d.ts deleted file mode 100644 index 11f02fe2a..000000000 --- a/packages/frameworks/react/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/packages/frameworks/react/tsconfig.build.json b/packages/frameworks/react/tsconfig.build.json deleted file mode 100644 index e0a84f7e8..000000000 --- a/packages/frameworks/react/tsconfig.build.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["lib"] -} diff --git a/packages/frameworks/react/tsconfig.json b/packages/frameworks/react/tsconfig.json index efa4736ee..828bf0283 100644 --- a/packages/frameworks/react/tsconfig.json +++ b/packages/frameworks/react/tsconfig.json @@ -20,6 +20,5 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src", "lib"], - "references": [{ "path": "./tsconfig.node.json" }] + "include": ["src", "lib"] } diff --git a/packages/frameworks/react/tsconfig.node.json b/packages/frameworks/react/tsconfig.node.json deleted file mode 100644 index 42872c59f..000000000 --- a/packages/frameworks/react/tsconfig.node.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/packages/frameworks/react/tsup.config.ts b/packages/frameworks/react/tsup.config.ts new file mode 100644 index 000000000..677cffb7b --- /dev/null +++ b/packages/frameworks/react/tsup.config.ts @@ -0,0 +1,4 @@ +import defaultConfig from "../../../tsup.base.ts"; +import { defineConfig } from "tsup"; + +export default defineConfig(defaultConfig); diff --git a/packages/frameworks/react/vite.config.ts b/packages/frameworks/react/vite.config.ts deleted file mode 100644 index d9177d7d3..000000000 --- a/packages/frameworks/react/vite.config.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; -import dts from "vite-plugin-dts"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -export default defineConfig({ - plugins: [react(), dts({ include: ["lib"] })], - build: { - lib: { - entry: resolve(__dirname, "lib/mod.ts"), - }, - rollupOptions: { - external: ["react", "react/jsx-runtime"], - }, - }, -}); diff --git a/packages/misc/docs-middleware/README.md b/packages/misc/docs-middleware/README.md new file mode 100644 index 000000000..bcea776e8 --- /dev/null +++ b/packages/misc/docs-middleware/README.md @@ -0,0 +1,11 @@ +# RivetKit Docs Middleware + +_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/misc/docs-middleware/package.json b/packages/misc/docs-middleware/package.json index 81e003640..19deb2cfb 100644 --- a/packages/misc/docs-middleware/package.json +++ b/packages/misc/docs-middleware/package.json @@ -2,6 +2,7 @@ "name": "docs-middleware", "version": "0.9.0-rc.1", "private": true, + "keywords": ["rivetkit", "docs", "middleware", "documentation", "utilities"], "scripts": { "deploy": "wrangler deploy", "dev": "wrangler dev", diff --git a/packages/platforms/bun/README.md b/packages/platforms/bun/README.md index 208ecb830..5fd4b42c9 100644 --- a/packages/platforms/bun/README.md +++ b/packages/platforms/bun/README.md @@ -1,13 +1,11 @@ -# RivetKit for Bun +# RivetKit Bun Adapter -_The Stateful Serverless Framework_ +_Lightweight Libraries for Backends_ -## Resources +[Learn More →](https://github.com/rivet-gg/rivetkit) -- [Bun Quickstart](https://rivetkit.org/platforms/bun) -- [RivetKit Package](http://npmjs.com/rivetkit) +[Discord](https://rivet.gg/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-gg/rivetkit/issues) ## License -Apache 2.0 - +Apache 2.0 \ No newline at end of file diff --git a/packages/platforms/bun/package.json b/packages/platforms/bun/package.json index c354b04d0..295b7fca7 100644 --- a/packages/platforms/bun/package.json +++ b/packages/platforms/bun/package.json @@ -1,6 +1,7 @@ { "name": "@rivetkit/bun", "version": "0.9.0-rc.1", + "keywords": ["rivetkit", "bun", "platform", "runtime", "fast"], "files": [ "src", "dist", diff --git a/packages/platforms/bun/src/mod.ts b/packages/platforms/bun/src/mod.ts index fe678cbaa..552c92af7 100644 --- a/packages/platforms/bun/src/mod.ts +++ b/packages/platforms/bun/src/mod.ts @@ -35,22 +35,22 @@ export function createRouter( // Configure default configuration if (!config.topology) config.topology = "standalone"; - if (!config.drivers.manager || !config.drivers.worker) { + if (!config.driver.manager || !config.driver.worker) { if (config.mode === "file-system") { const fsState = new FileSystemGlobalState(); - if (!config.drivers.manager) { - config.drivers.manager = new FileSystemManagerDriver(registry, fsState); + if (!config.driver.manager) { + config.driver.manager = new FileSystemManagerDriver(registry, fsState); } - if (!config.drivers.worker) { - config.drivers.worker = new FileSystemWorkerDriver(fsState); + if (!config.driver.worker) { + config.driver.worker = new FileSystemWorkerDriver(fsState); } } else if (config.mode === "memory") { const memoryState = new MemoryGlobalState(); - if (!config.drivers.manager) { - config.drivers.manager = new MemoryManagerDriver(registry, memoryState); + if (!config.driver.manager) { + config.driver.manager = new MemoryManagerDriver(registry, memoryState); } - if (!config.drivers.worker) { - config.drivers.worker = new MemoryWorkerDriver(memoryState); + if (!config.driver.worker) { + config.driver.worker = new MemoryWorkerDriver(memoryState); } } else { assertUnreachable(config.mode); diff --git a/packages/platforms/cloudflare-workers/README.md b/packages/platforms/cloudflare-workers/README.md index 6eacea0df..8adff7d71 100644 --- a/packages/platforms/cloudflare-workers/README.md +++ b/packages/platforms/cloudflare-workers/README.md @@ -1,13 +1,11 @@ -# RivetKit for Cloudflare Workers +# RivetKit Cloudflare Workers Adapter -_The Stateful Serverless Framework_ +_Lightweight Libraries for Backends_ -## Resources +[Learn More →](https://github.com/rivet-gg/rivetkit) -- [Cloudflare Workers Quickstart](https://rivetkit.org/platforms/cloudflare-workers) -- [RivetKit Package](http://npmjs.com/rivetkit) +[Discord](https://rivet.gg/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-gg/rivetkit/issues) ## License -Apache 2.0 - +Apache 2.0 \ No newline at end of file diff --git a/packages/platforms/cloudflare-workers/package.json b/packages/platforms/cloudflare-workers/package.json index 322af4cfa..3b92b9894 100644 --- a/packages/platforms/cloudflare-workers/package.json +++ b/packages/platforms/cloudflare-workers/package.json @@ -1,6 +1,7 @@ { "name": "@rivetkit/cloudflare-workers", "version": "0.9.0-rc.1", + "keywords": ["rivetkit", "cloudflare", "workers", "edge", "platform", "serverless"], "files": [ "src", "dist", diff --git a/packages/platforms/cloudflare-workers/src/handler.ts b/packages/platforms/cloudflare-workers/src/handler.ts index 705276d6e..753071fbf 100644 --- a/packages/platforms/cloudflare-workers/src/handler.ts +++ b/packages/platforms/cloudflare-workers/src/handler.ts @@ -20,7 +20,6 @@ import { upgradeWebSocket } from "./websocket"; import invariant from "invariant"; import { AsyncLocalStorage } from "node:async_hooks"; import { InternalError } from "rivetkit/errors"; -import type { WebSocket } from "ws"; /** Cloudflare Workers env */ export interface Bindings { @@ -82,9 +81,8 @@ export function createRouter( // Configure drivers // // Worker driver will get set in `WorkerHandler` - if (!driverConfig.drivers) driverConfig.drivers = {}; - if (!driverConfig.drivers.manager) - driverConfig.drivers.manager = new CloudflareWorkersManagerDriver(); + if (!driverConfig.driver.manager) + driverConfig.driver.manager = new CloudflareWorkersManagerDriver(); // Setup WebSockets if (!driverConfig.getUpgradeWebSocket) diff --git a/packages/platforms/cloudflare-workers/src/worker-handler-do.ts b/packages/platforms/cloudflare-workers/src/worker-handler-do.ts index b3459a931..954b1b20f 100644 --- a/packages/platforms/cloudflare-workers/src/worker-handler-do.ts +++ b/packages/platforms/cloudflare-workers/src/worker-handler-do.ts @@ -102,8 +102,8 @@ export function createWorkerDurableObject( // Create topology if (!config.drivers) config.drivers = {}; - if (!config.drivers.worker) { - config.drivers.worker = new CloudflareWorkersWorkerDriver(globalState); + if (!config.driver.worker) { + config.driver.worker = new CloudflareWorkersWorkerDriver(globalState); } const workerTopology = new PartitionTopologyWorker(registry.config, config); diff --git a/packages/platforms/nodejs/README.md b/packages/platforms/nodejs/README.md deleted file mode 100644 index 35ebcb778..000000000 --- a/packages/platforms/nodejs/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# RivetKit for Node.js - -_The Stateful Serverless Framework_ - -## Resources - -- [Node.js Quickstart](https://rivetkit.org/platforms/nodejs) -- [RivetKit Package](http://npmjs.com/rivetkit) - -## License - -Apache 2.0 - diff --git a/packages/platforms/nodejs/package.json b/packages/platforms/nodejs/package.json index 63dce41ae..961562c67 100644 --- a/packages/platforms/nodejs/package.json +++ b/packages/platforms/nodejs/package.json @@ -1,6 +1,13 @@ { "name": "@rivetkit/nodejs", "version": "0.9.0-rc.1", + "keywords": [ + "rivetkit", + "nodejs", + "platform", + "server", + "runtime" + ], "files": [ "src", "dist", @@ -17,8 +24,7 @@ "types": "./dist/mod.d.cts", "default": "./dist/mod.cjs" } - }, - "./tsconfig": "./dist/tsconfig.json" + } }, "sideEffects": false, "scripts": { @@ -33,13 +39,13 @@ "devDependencies": { "@rivetkit/file-system": "workspace:*", "@rivetkit/memory": "workspace:*", - "hono": "^4.7.0", + "hono": "^4.8.0", "rivetkit": "workspace:*", "tsup": "^8.4.0", "typescript": "^5.5.2" }, "dependencies": { - "@hono/node-server": "^1.13.8", + "@hono/node-server": "^1.14.4", "@hono/node-ws": "^1.0.8", "@types/node": "^24.0.3", "zod": "^3.24.2" diff --git a/packages/platforms/nodejs/src/config.ts b/packages/platforms/nodejs/src/config.ts deleted file mode 100644 index 55b30fe8f..000000000 --- a/packages/platforms/nodejs/src/config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { DriverConfigSchema } from "rivetkit/driver-helpers"; -import { z } from "zod"; - -export const ConfigSchema = DriverConfigSchema.extend({ - mode: z.enum(["file-system", "memory"]).optional().default("file-system"), - hostname: z - .string() - .optional() - .default(process.env.HOSTNAME ?? "127.0.0.1"), - port: z - .number() - .optional() - .default(Number.parseInt(process.env.PORT ?? "6420")), -}).default({}); -export type InputConfig = z.input; diff --git a/packages/platforms/nodejs/src/mod.ts b/packages/platforms/nodejs/src/mod.ts index 043d1ef44..82575b75e 100644 --- a/packages/platforms/nodejs/src/mod.ts +++ b/packages/platforms/nodejs/src/mod.ts @@ -1,104 +1,61 @@ import { serve as honoServe, type ServerType } from "@hono/node-server"; -import { createNodeWebSocket, type NodeWebSocket } from "@hono/node-ws"; -import { assertUnreachable } from "rivetkit/utils"; -import { CoordinateTopology } from "rivetkit/topologies/coordinate"; import { logger } from "./log"; -import type { Hono } from "hono"; -import { StandaloneTopology, type Registry } from "rivetkit"; -import { - MemoryGlobalState, - MemoryManagerDriver, - MemoryWorkerDriver, -} from "@rivetkit/memory"; -import { type InputConfig, ConfigSchema } from "./config"; -import { - FileSystemWorkerDriver, - FileSystemGlobalState, - FileSystemManagerDriver, -} from "@rivetkit/file-system"; - -export { InputConfig as Config } from "./config"; - -export function createRouter( - registry: Registry, - inputConfig?: InputConfig, -): { - router: Hono; - injectWebSocket: NodeWebSocket["injectWebSocket"]; -} { - const config = ConfigSchema.parse(inputConfig); - - // Configure default configuration - if (!config.topology) config.topology = "standalone"; - if (!config.drivers.manager || !config.drivers.worker) { - if (config.mode === "file-system") { - const fsState = new FileSystemGlobalState(); - if (!config.drivers.manager) { - config.drivers.manager = new FileSystemManagerDriver(registry, fsState); - } - if (!config.drivers.worker) { - config.drivers.worker = new FileSystemWorkerDriver(fsState); - } - } else if (config.mode === "memory") { - const memoryState = new MemoryGlobalState(); - if (!config.drivers.manager) { - config.drivers.manager = new MemoryManagerDriver(registry, memoryState); - } - if (!config.drivers.worker) { - config.drivers.worker = new MemoryWorkerDriver(memoryState); - } - } else { - assertUnreachable(config.mode); - } - } +import type { Registry } from "rivetkit"; +import { z } from "zod"; +import type { Client } from "rivetkit/client"; +import { createNodeWebSocket, type NodeWebSocket } from "@hono/node-ws"; +import { RunConfigSchema } from "rivetkit/driver-helpers"; +import { RegistryWorkers } from "rivetkit"; +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 (!config.getUpgradeWebSocket) { - config.getUpgradeWebSocket = (router) => { + if (!runConfig.getUpgradeWebSocket) { + runConfig.getUpgradeWebSocket = (router) => { const webSocket = createNodeWebSocket({ app: router }); injectWebSocket = webSocket.injectWebSocket; return webSocket.upgradeWebSocket; }; } - // Setup topology - if (config.topology === "standalone") { - const topology = new StandaloneTopology(registry.config, config); - if (!injectWebSocket) throw new Error("injectWebSocket not defined"); - return { router: topology.router, injectWebSocket }; - } else if (config.topology === "partition") { - throw new Error("Node.js only supports standalone & coordinate topology."); - } else if (config.topology === "coordinate") { - const topology = new CoordinateTopology(registry.config, config); - if (!injectWebSocket) throw new Error("injectWebSocket not defined"); - return { router: topology.router, injectWebSocket }; - } else { - assertUnreachable(config.topology); - } -} - -export function serve( - registry: Registry, - inputConfig?: InputConfig, -): ServerType { - const config = ConfigSchema.parse(inputConfig); + const { client, hono: rawHono } = registry.run(runConfig); - const { router, injectWebSocket } = createRouter(registry, config); + const hono = new Hono().route(runConfig.basePath, rawHono); const server = honoServe({ - fetch: router.fetch, - hostname: config.hostname, - port: config.port, + fetch: hono.fetch, + hostname: runConfig.hostname, + port: runConfig.port, }); + if (!injectWebSocket) throw new Error("missing injectWebSocket"); injectWebSocket(server); logger().info("rivetkit started", { - hostname: config.hostname, - port: config.port, + hostname: runConfig.hostname, + port: runConfig.port, }); - return server; + return { server, client }; } diff --git a/packages/platforms/nodejs/tsconfig.json b/packages/platforms/nodejs/tsconfig.json index accb9677a..043f499c0 100644 --- a/packages/platforms/nodejs/tsconfig.json +++ b/packages/platforms/nodejs/tsconfig.json @@ -5,5 +5,5 @@ "@/*": ["./src/*"] } }, - "include": ["src/**/*"] + "include": ["src/**/*", "tests/**/*"] } diff --git a/packages/platforms/rivet/README.md b/packages/platforms/rivet/README.md index dc22536b1..7dbf0d601 100644 --- a/packages/platforms/rivet/README.md +++ b/packages/platforms/rivet/README.md @@ -1,13 +1,11 @@ -# RivetKit for Rivet +# RivetKit Rivet Adapter -_The Stateful Serverless Framework_ +_Lightweight Libraries for Backends_ -## Resources +[Learn More →](https://github.com/rivet-gg/rivetkit) -- [Rivet Quickstart](https://rivetkit.org/platforms/rivet) -- [RivetKit Package](http://npmjs.com/rivetkit) +[Discord](https://rivet.gg/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-gg/rivetkit/issues) ## License -Apache 2.0 - +Apache 2.0 \ No newline at end of file diff --git a/packages/platforms/rivet/package.json b/packages/platforms/rivet/package.json index f4c34f4aa..6247d168a 100644 --- a/packages/platforms/rivet/package.json +++ b/packages/platforms/rivet/package.json @@ -1,6 +1,7 @@ { "name": "@rivetkit/rivet", "version": "0.9.0-rc.1", + "keywords": ["rivetkit", "rivet", "platform", "cloud", "managed", "scaling"], "files": [ "src", "dist", diff --git a/packages/platforms/rivet/src/manager.ts b/packages/platforms/rivet/src/manager.ts index 99b654259..454027dd0 100644 --- a/packages/platforms/rivet/src/manager.ts +++ b/packages/platforms/rivet/src/manager.ts @@ -82,8 +82,8 @@ export async function startManager( // Setup manager driver if (!driverConfig.drivers) driverConfig.drivers = {}; - if (!driverConfig.drivers.manager) { - driverConfig.drivers.manager = new RivetManagerDriver(clientConfig); + if (!driverConfig.driver.manager) { + driverConfig.driver.manager = new RivetManagerDriver(clientConfig); } // Setup WebSocket routing for Node diff --git a/packages/platforms/rivet/src/worker.ts b/packages/platforms/rivet/src/worker.ts index 9b53ae007..b792cd08e 100644 --- a/packages/platforms/rivet/src/worker.ts +++ b/packages/platforms/rivet/src/worker.ts @@ -61,8 +61,8 @@ async function startWorker( // Setup worker driver if (!driverConfig.drivers) driverConfig.drivers = {}; - if (!driverConfig.drivers.worker) { - driverConfig.drivers.worker = new RivetWorkerDriver(ctx); + if (!driverConfig.driver.worker) { + driverConfig.driver.worker = new RivetWorkerDriver(ctx); } // Setup WebSocket upgrader diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 025a2c054..38a182bd4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,12 @@ importers: .: 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) esbuild: specifier: ^0.25.1 version: 0.25.5 @@ -112,6 +118,145 @@ importers: specifier: ^3.1.1 version: 3.2.4(@types/node@22.15.32)(tsx@3.14.0)(yaml@2.8.0) + 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 + elysia: + specifier: ^1.3.5 + version: 1.3.5(exact-mirror@0.1.2(@sinclair/typebox@0.34.35))(file-type@21.0.0)(typescript@5.8.3) + react: + specifier: ^18.3 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.3.1) + devDependencies: + '@types/node': + specifier: ^22.13.9 + version: 22.15.32 + rivetkit: + specifier: workspace:* + version: link:../../packages/core + typescript: + specifier: ^5.5.2 + version: 5.8.3 + + 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 + express: + specifier: ^5.1.0 + version: 5.1.0 + react: + specifier: ^18.3 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.3.1) + devDependencies: + '@types/express': + specifier: ^4.17.21 + version: 4.17.23 + '@types/node': + specifier: ^22.13.9 + version: 22.15.32 + rivetkit: + specifier: workspace:* + version: link:../../packages/core + tsx: + specifier: ^3.12.7 + version: 3.14.0 + typescript: + specifier: ^5.5.2 + version: 5.8.3 + + examples/hono: + dependencies: + '@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 + devDependencies: + '@types/node': + specifier: ^22.13.9 + version: 22.15.32 + rivetkit: + specifier: workspace:* + version: link:../../packages/core + tsx: + specifier: ^3.12.7 + version: 3.14.0 + typescript: + specifier: ^5.5.2 + version: 5.8.3 + + examples/hono-react: + dependencies: + '@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 + hono: + specifier: ^4.7.0 + version: 4.8.0 + react: + specifier: ^18.3 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.3.1) + devDependencies: + '@types/node': + specifier: ^22.13.9 + version: 22.15.32 + '@types/react': + specifier: ^18.2.0 + version: 18.3.23 + '@types/react-dom': + specifier: ^18.2.0 + version: 18.3.7(@types/react@18.3.23) + '@vitejs/plugin-react': + specifier: ^4.2.0 + version: 4.5.2(vite@5.4.19(@types/node@22.15.32)) + concurrently: + specifier: ^8.2.2 + version: 8.2.2 + rivetkit: + specifier: workspace:* + version: link:../../packages/core + tsx: + specifier: ^3.12.7 + version: 3.14.0 + typescript: + specifier: ^5.5.2 + version: 5.8.3 + vite: + specifier: ^5.0.0 + version: 5.4.19(@types/node@22.15.32) + vitest: + specifier: ^3.1.1 + version: 3.2.4(@types/node@22.15.32)(tsx@3.14.0)(yaml@2.8.0) + examples/linear-coding-agent: dependencies: '@ai-sdk/anthropic': @@ -182,6 +327,77 @@ 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: + '@types/node': + specifier: ^22.13.9 + version: 22.15.32 + rivetkit: + specifier: workspace:* + version: link:../../packages/core + 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 + react: + specifier: ^18.3 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.3.1) + devDependencies: + '@types/node': + specifier: ^22.13.9 + version: 22.15.32 + '@types/react': + specifier: ^18.2.0 + version: 18.3.23 + '@types/react-dom': + specifier: ^18.2.0 + version: 18.3.7(@types/react@18.3.23) + '@vitejs/plugin-react': + specifier: ^4.2.0 + version: 4.5.2(vite@5.4.19(@types/node@22.15.32)) + concurrently: + specifier: ^8.2.2 + version: 8.2.2 + rivetkit: + specifier: workspace:* + version: link:../../packages/core + tsx: + specifier: ^3.12.7 + version: 3.14.0 + typescript: + specifier: ^5.5.2 + version: 5.8.3 + vite: + specifier: ^5.0.0 + version: 5.4.19(@types/node@22.15.32) + vitest: + specifier: ^3.1.1 + version: 3.2.4(@types/node@22.15.32)(tsx@3.14.0)(yaml@2.8.0) + examples/resend-streaks: dependencies: '@date-fns/tz': @@ -213,6 +429,34 @@ importers: specifier: ^3.1.1 version: 3.2.4(@types/node@22.15.32)(tsx@3.14.0)(yaml@2.8.0) + 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) + '@trpc/server': + specifier: ^11.4.2 + version: 11.4.2(typescript@5.8.3) + zod: + specifier: ^3.24.1 + version: 3.25.67 + devDependencies: + '@types/node': + specifier: ^22.13.9 + version: 22.15.32 + rivetkit: + specifier: workspace:* + version: link:../../packages/core + tsx: + specifier: ^3.12.7 + version: 3.14.0 + typescript: + specifier: ^5.5.2 + version: 5.8.3 + packages/core: dependencies: '@hono/zod-openapi': @@ -407,24 +651,15 @@ importers: '@types/react-dom': specifier: ^19.1.6 version: 19.1.6(@types/react@19.1.8) - '@vitejs/plugin-react': - specifier: ^4.5.2 - version: 4.5.2(vite@6.3.5(@types/node@24.0.3)(tsx@4.20.3)(yaml@2.8.0)) rivetkit: specifier: workspace:^ version: link:../../core + 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 - vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@24.0.3)(tsx@4.20.3)(yaml@2.8.0) - vite-plugin-dts: - specifier: ^4.5.4 - version: 4.5.4(@types/node@24.0.3)(rollup@4.44.0)(typescript@5.8.3)(vite@6.3.5(@types/node@24.0.3)(tsx@4.20.3)(yaml@2.8.0)) - vitest: - specifier: ^3.1.1 - version: 3.2.4(@types/node@24.0.3)(tsx@4.20.3)(yaml@2.8.0) packages/misc/docs-middleware: dependencies: @@ -488,7 +723,7 @@ importers: packages/platforms/nodejs: dependencies: '@hono/node-server': - specifier: ^1.13.8 + specifier: ^1.14.4 version: 1.14.4(hono@4.8.0) '@hono/node-ws': specifier: ^1.0.8 @@ -675,6 +910,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.27.6': + resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -873,6 +1112,12 @@ packages: peerDependencies: esbuild: '*' + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.5': resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} engines: {node: '>=18'} @@ -891,6 +1136,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.5': resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} engines: {node: '>=18'} @@ -909,6 +1160,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.5': resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} engines: {node: '>=18'} @@ -927,6 +1184,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.5': resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} engines: {node: '>=18'} @@ -945,6 +1208,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.5': resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} engines: {node: '>=18'} @@ -963,6 +1232,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.5': resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} engines: {node: '>=18'} @@ -981,6 +1256,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.5': resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} engines: {node: '>=18'} @@ -999,6 +1280,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.5': resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} engines: {node: '>=18'} @@ -1017,6 +1304,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.5': resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} engines: {node: '>=18'} @@ -1035,6 +1328,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.5': resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} engines: {node: '>=18'} @@ -1053,6 +1352,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.5': resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} engines: {node: '>=18'} @@ -1071,6 +1376,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.5': resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} engines: {node: '>=18'} @@ -1089,6 +1400,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.5': resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} engines: {node: '>=18'} @@ -1107,6 +1424,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.5': resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} engines: {node: '>=18'} @@ -1125,6 +1448,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.5': resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} engines: {node: '>=18'} @@ -1143,6 +1472,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.5': resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} engines: {node: '>=18'} @@ -1161,6 +1496,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.5': resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} engines: {node: '>=18'} @@ -1185,6 +1526,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.5': resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} engines: {node: '>=18'} @@ -1209,6 +1556,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.5': resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} engines: {node: '>=18'} @@ -1227,6 +1580,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.5': resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} engines: {node: '>=18'} @@ -1245,6 +1604,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.5': resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} engines: {node: '>=18'} @@ -1263,6 +1628,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.5': resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} engines: {node: '>=18'} @@ -1281,6 +1652,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.5': resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} engines: {node: '>=18'} @@ -1702,6 +2079,9 @@ packages: '@selderee/plugin-htmlparser2@0.11.0': resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@sinclair/typebox@0.34.35': + resolution: {integrity: sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==} + '@tanstack/react-store@0.7.1': resolution: {integrity: sha512-qUTEKdId6QPWGiWyKAPf/gkN29scEsz6EUSJ0C3HgLMgaqTAyBsQ2sMCfGVcqb+kkhEXAdjleCgH6LAPD6f2sA==} peerDependencies: @@ -1711,6 +2091,24 @@ packages: '@tanstack/store@0.7.1': resolution: {integrity: sha512-PjUQKXEXhLYj2X5/6c1Xn/0/qKY0IVFxTJweopRfF26xfjVyb14yALydJrHupDh3/d+1WKmfEgZPBVCmDkzzwg==} + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@trpc/client@11.4.2': + resolution: {integrity: sha512-Eep1rorAsATs9bxgaXf+BV34CRs4lAKQmwumUL4CNdNDkJItyfuWUr3xWx0np1w3EzUDVA0YDMK93iKDBBA0KQ==} + peerDependencies: + '@trpc/server': 11.4.2 + typescript: '>=5.7.2' + + '@trpc/server@11.4.2': + resolution: {integrity: sha512-THyq/V5bSFDHeWEAk6LqHF0IVTGk6voGwWsFEipzRRKOWWMIZINCsKZ4cISG6kWO2X9jBfMWv/S2o9hnC0zQ0w==} + peerDependencies: + typescript: '>=5.7.2' + '@types/argparse@1.0.38': resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} @@ -1751,9 +2149,15 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@4.19.6': + resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + '@types/express-serve-static-core@5.0.6': resolution: {integrity: sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==} + '@types/express@4.17.23': + resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + '@types/express@5.0.3': resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==} @@ -1787,6 +2191,9 @@ packages: '@types/prompts@2.4.9': resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + '@types/ps-tree@1.1.6': resolution: {integrity: sha512-PtrlVaOaI44/3pl3cvnlK+GxOM3re2526TJvPvh7W+keHIXdV4TE0ylpPBAcvFQCbGitaTXwL9u+RF7qtVeazQ==} @@ -1796,11 +2203,19 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + '@types/react-dom@19.1.6': resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} peerDependencies: '@types/react': ^19.0.0 + '@types/react@18.3.23': + resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==} + '@types/react@19.1.8': resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} @@ -2122,6 +2537,11 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concurrently@8.2.2: + resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} + engines: {node: ^14.13.0 || >=16.0.0} + hasBin: true + concurrently@9.1.2: resolution: {integrity: sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==} engines: {node: '>=18'} @@ -2175,6 +2595,10 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2189,6 +2613,10 @@ packages: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} @@ -2294,6 +2722,13 @@ packages: electron-to-chromium@1.5.171: resolution: {integrity: sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==} + elysia@1.3.5: + resolution: {integrity: sha512-XVIKXlKFwUT7Sta8GY+wO5reD9I0rqAEtaz1Z71UgJb61csYt8Q3W9al8rtL5RgumuRR8e3DNdzlUN9GkC4KDw==} + peerDependencies: + exact-mirror: '>= 0.0.9' + file-type: '>= 20.0.0' + typescript: '>= 5.0.0' + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2341,6 +2776,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.25.5: resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} engines: {node: '>=18'} @@ -2389,6 +2829,14 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} + exact-mirror@0.1.2: + resolution: {integrity: sha512-wFCPCDLmHbKGUb8TOi/IS7jLsgR8WVDGtDK3CzcB4Guf/weq7G+I+DkXiRSZfbemBFOxOINKpraM6ml78vo8Zw==} + peerDependencies: + '@sinclair/typebox': ^0.34.15 + peerDependenciesMeta: + '@sinclair/typebox': + optional: true + exit-hook@2.2.1: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} @@ -2408,6 +2856,9 @@ packages: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2433,6 +2884,10 @@ packages: fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-type@21.0.0: + resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==} + engines: {node: '>=20'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -2986,6 +3441,9 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + openapi3-ts@4.4.0: resolution: {integrity: sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==} @@ -3346,6 +3804,9 @@ packages: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead + spawn-command@0.0.2: + resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} + split@0.3.3: resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} @@ -3409,6 +3870,10 @@ packages: strip-literal@3.0.0: resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + strtok3@10.3.1: + resolution: {integrity: sha512-3JWEZM6mfix/GCJBBUrkA8p2Id2pBkyTkVCJKto55w080QBKZ+8R171fGrbiSp+yMO/u6F8/yUh7K4V9K+YCnw==} + engines: {node: '>=18'} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -3475,6 +3940,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + token-types@6.0.0: + resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} + engines: {node: '>=14.16'} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -3574,6 +4043,10 @@ packages: ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + uint8array-extras@1.4.0: + resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} + engines: {node: '>=18'} + undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -3643,6 +4116,37 @@ packages: vite: optional: true + vite@5.4.19: + resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + vite@6.3.5: resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -3997,6 +4501,8 @@ snapshots: '@babel/core': 7.27.4 '@babel/helper-plugin-utils': 7.27.1 + '@babel/runtime@7.27.6': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -4154,6 +4660,9 @@ snapshots: escape-string-regexp: 4.0.0 rollup-plugin-node-polyfills: 0.2.1 + '@esbuild/aix-ppc64@0.21.5': + optional: true + '@esbuild/aix-ppc64@0.25.5': optional: true @@ -4163,6 +4672,9 @@ snapshots: '@esbuild/android-arm64@0.18.20': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + '@esbuild/android-arm64@0.25.5': optional: true @@ -4172,6 +4684,9 @@ snapshots: '@esbuild/android-arm@0.18.20': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + '@esbuild/android-arm@0.25.5': optional: true @@ -4181,6 +4696,9 @@ snapshots: '@esbuild/android-x64@0.18.20': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + '@esbuild/android-x64@0.25.5': optional: true @@ -4190,6 +4708,9 @@ snapshots: '@esbuild/darwin-arm64@0.18.20': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + '@esbuild/darwin-arm64@0.25.5': optional: true @@ -4199,6 +4720,9 @@ snapshots: '@esbuild/darwin-x64@0.18.20': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + '@esbuild/darwin-x64@0.25.5': optional: true @@ -4208,6 +4732,9 @@ snapshots: '@esbuild/freebsd-arm64@0.18.20': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + '@esbuild/freebsd-arm64@0.25.5': optional: true @@ -4217,6 +4744,9 @@ snapshots: '@esbuild/freebsd-x64@0.18.20': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + '@esbuild/freebsd-x64@0.25.5': optional: true @@ -4226,6 +4756,9 @@ snapshots: '@esbuild/linux-arm64@0.18.20': optional: true + '@esbuild/linux-arm64@0.21.5': + optional: true + '@esbuild/linux-arm64@0.25.5': optional: true @@ -4235,6 +4768,9 @@ snapshots: '@esbuild/linux-arm@0.18.20': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + '@esbuild/linux-arm@0.25.5': optional: true @@ -4244,6 +4780,9 @@ snapshots: '@esbuild/linux-ia32@0.18.20': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + '@esbuild/linux-ia32@0.25.5': optional: true @@ -4253,6 +4792,9 @@ snapshots: '@esbuild/linux-loong64@0.18.20': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + '@esbuild/linux-loong64@0.25.5': optional: true @@ -4262,6 +4804,9 @@ snapshots: '@esbuild/linux-mips64el@0.18.20': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + '@esbuild/linux-mips64el@0.25.5': optional: true @@ -4271,6 +4816,9 @@ snapshots: '@esbuild/linux-ppc64@0.18.20': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + '@esbuild/linux-ppc64@0.25.5': optional: true @@ -4280,6 +4828,9 @@ snapshots: '@esbuild/linux-riscv64@0.18.20': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + '@esbuild/linux-riscv64@0.25.5': optional: true @@ -4289,6 +4840,9 @@ snapshots: '@esbuild/linux-s390x@0.18.20': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + '@esbuild/linux-s390x@0.25.5': optional: true @@ -4298,6 +4852,9 @@ snapshots: '@esbuild/linux-x64@0.18.20': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + '@esbuild/linux-x64@0.25.5': optional: true @@ -4310,6 +4867,9 @@ snapshots: '@esbuild/netbsd-x64@0.18.20': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + '@esbuild/netbsd-x64@0.25.5': optional: true @@ -4322,6 +4882,9 @@ snapshots: '@esbuild/openbsd-x64@0.18.20': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + '@esbuild/openbsd-x64@0.25.5': optional: true @@ -4331,6 +4894,9 @@ snapshots: '@esbuild/sunos-x64@0.18.20': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + '@esbuild/sunos-x64@0.25.5': optional: true @@ -4340,6 +4906,9 @@ snapshots: '@esbuild/win32-arm64@0.18.20': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + '@esbuild/win32-arm64@0.25.5': optional: true @@ -4349,6 +4918,9 @@ snapshots: '@esbuild/win32-ia32@0.18.20': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + '@esbuild/win32-ia32@0.25.5': optional: true @@ -4358,6 +4930,9 @@ snapshots: '@esbuild/win32-x64@0.18.20': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + '@esbuild/win32-x64@0.25.5': optional: true @@ -4836,6 +5411,9 @@ snapshots: domhandler: 5.0.3 selderee: 0.11.0 + '@sinclair/typebox@0.34.35': + optional: true + '@tanstack/react-store@0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/store': 0.7.1 @@ -4845,6 +5423,25 @@ snapshots: '@tanstack/store@0.7.1': {} + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.1 + fflate: 0.8.2 + token-types: 6.0.0 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + + '@trpc/client@11.4.2(@trpc/server@11.4.2(typescript@5.8.3))(typescript@5.8.3)': + dependencies: + '@trpc/server': 11.4.2(typescript@5.8.3) + typescript: 5.8.3 + + '@trpc/server@11.4.2(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + '@types/argparse@1.0.38': {} '@types/babel__core@7.20.5': @@ -4893,6 +5490,13 @@ snapshots: '@types/estree@1.0.8': {} + '@types/express-serve-static-core@4.19.6': + dependencies: + '@types/node': 22.15.32 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + '@types/express-serve-static-core@5.0.6': dependencies: '@types/node': 22.15.32 @@ -4900,6 +5504,13 @@ snapshots: '@types/range-parser': 1.2.7 '@types/send': 0.17.5 + '@types/express@4.17.23': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.6 + '@types/qs': 6.14.0 + '@types/serve-static': 1.15.8 + '@types/express@5.0.3': dependencies: '@types/body-parser': 1.19.6 @@ -4940,16 +5551,27 @@ snapshots: '@types/node': 22.15.32 kleur: 3.0.3 + '@types/prop-types@15.7.15': {} + '@types/ps-tree@1.1.6': {} '@types/qs@6.14.0': {} '@types/range-parser@1.2.7': {} + '@types/react-dom@18.3.7(@types/react@18.3.23)': + dependencies: + '@types/react': 18.3.23 + '@types/react-dom@19.1.6(@types/react@19.1.8)': dependencies: '@types/react': 19.1.8 + '@types/react@18.3.23': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.1.3 + '@types/react@19.1.8': dependencies: csstype: 3.1.3 @@ -4973,7 +5595,7 @@ snapshots: dependencies: '@types/node': 22.15.32 - '@vitejs/plugin-react@4.5.2(vite@6.3.5(@types/node@24.0.3)(tsx@4.20.3)(yaml@2.8.0))': + '@vitejs/plugin-react@4.5.2(vite@5.4.19(@types/node@22.15.32))': dependencies: '@babel/core': 7.27.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4) @@ -4981,7 +5603,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.11 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.3.5(@types/node@24.0.3)(tsx@4.20.3)(yaml@2.8.0) + vite: 5.4.19(@types/node@22.15.32) transitivePeerDependencies: - supports-color @@ -5319,6 +5941,18 @@ snapshots: concat-map@0.0.1: {} + concurrently@8.2.2: + dependencies: + chalk: 4.1.2 + date-fns: 2.30.0 + lodash: 4.17.21 + rxjs: 7.8.2 + shell-quote: 1.8.3 + spawn-command: 0.0.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + concurrently@9.1.2: dependencies: chalk: 4.1.2 @@ -5364,6 +5998,8 @@ snapshots: cookie@0.7.2: {} + cookie@1.0.2: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -5376,6 +6012,10 @@ snapshots: data-uri-to-buffer@4.0.1: {} + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.27.6 + date-fns@4.1.0: {} de-indent@1.0.2: {} @@ -5454,6 +6094,17 @@ snapshots: electron-to-chromium@1.5.171: {} + elysia@1.3.5(exact-mirror@0.1.2(@sinclair/typebox@0.34.35))(file-type@21.0.0)(typescript@5.8.3): + dependencies: + cookie: 1.0.2 + exact-mirror: 0.1.2(@sinclair/typebox@0.34.35) + fast-decode-uri-component: 1.0.1 + file-type: 21.0.0 + typescript: 5.8.3 + optionalDependencies: + '@sinclair/typebox': 0.34.35 + openapi-types: 12.1.3 + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -5531,6 +6182,32 @@ snapshots: '@esbuild/win32-ia32': 0.18.20 '@esbuild/win32-x64': 0.18.20 + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + esbuild@0.25.5: optionalDependencies: '@esbuild/aix-ppc64': 0.25.5 @@ -5595,6 +6272,10 @@ snapshots: dependencies: eventsource-parser: 3.0.2 + exact-mirror@0.1.2(@sinclair/typebox@0.34.35): + optionalDependencies: + '@sinclair/typebox': 0.34.35 + exit-hook@2.2.1: {} expect-type@1.2.1: {} @@ -5637,6 +6318,8 @@ snapshots: dependencies: is-extendable: 0.1.1 + fast-decode-uri-component@1.0.1: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -5662,6 +6345,15 @@ snapshots: fflate@0.8.2: {} + file-type@21.0.0: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.1 + token-types: 6.0.0 + uint8array-extras: 1.4.0 + transitivePeerDependencies: + - supports-color + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -6184,6 +6876,9 @@ snapshots: dependencies: wrappy: 1.0.2 + openapi-types@12.1.3: + optional: true + openapi3-ts@4.4.0: dependencies: yaml: 2.8.0 @@ -6574,6 +7269,8 @@ snapshots: sourcemap-codec@1.4.8: {} + spawn-command@0.0.2: {} + split@0.3.3: dependencies: through: 2.3.8 @@ -6633,6 +7330,10 @@ snapshots: dependencies: js-tokens: 9.0.1 + strtok3@10.3.1: + dependencies: + '@tokenizer/token': 0.3.0 + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -6692,6 +7393,11 @@ snapshots: toidentifier@1.0.1: {} + token-types@6.0.0: + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + totalist@3.0.1: {} tr46@0.0.3: {} @@ -6818,6 +7524,8 @@ snapshots: ufo@1.6.1: {} + uint8array-extras@1.4.0: {} + undici-types@5.26.5: {} undici-types@6.21.0: {} @@ -6952,6 +7660,15 @@ snapshots: - rollup - supports-color + vite@5.4.19(@types/node@22.15.32): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.44.0 + optionalDependencies: + '@types/node': 22.15.32 + fsevents: 2.3.3 + vite@6.3.5(@types/node@22.15.32)(tsx@3.14.0)(yaml@2.8.0): dependencies: esbuild: 0.25.5 diff --git a/tsup.base.ts b/tsup.base.ts index 9bf6961e6..93cf64444 100644 --- a/tsup.base.ts +++ b/tsup.base.ts @@ -6,7 +6,12 @@ export default { format: ["cjs", "esm"], sourcemap: true, clean: true, - dts: true, + dts: { + compilerOptions: { + skipLibCheck: true, + resolveJsonModule: true, + }, + }, minify: false, // IMPORTANT: Splitting is required to fix a bug with ESM (https://github.com/egoist/tsup/issues/992#issuecomment-1763540165) splitting: true,