diff --git a/.github/workflows/pkg-pr-new.yaml b/.github/workflows/pkg-pr-new.yaml index 8870acc9ed..61d775b4e7 100644 --- a/.github/workflows/pkg-pr-new.yaml +++ b/.github/workflows/pkg-pr-new.yaml @@ -14,5 +14,5 @@ jobs: with: node-version: '22' - run: pnpm install - - run: pnpm build -F '@rivetkit/*' -F '!@rivetkit/shared-data' -F '!@rivetkit/engine-frontend' -F '!@rivetkit/mcp-hub' + - run: npx turbo build:publish -F rivetkit -F '@rivetkit/*' -F '!@rivetkit/example-registry' -F '!@rivetkit/mcp-hub' - run: pnpm dlx pkg-pr-new publish 'shared/typescript/*' 'engine/sdks/typescript/runner/' 'engine/sdks/typescript/runner-protocol/' 'rivetkit-typescript/packages/*' --packageManager pnpm --template './examples/*' diff --git a/frontend/package.json b/frontend/package.json index 315b3e1048..f0ece8de0f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,7 @@ "dev:engine": "vite --config vite.engine.config.ts", "dev:cloud": "vite --config vite.cloud.config.ts", "check-types": "tsc --noEmit", - "build:inspector": "vite build --mode=production --config vite.inspector.config.ts", + "build:inspector": "vite build --mode=production --base=/ui/ --config vite.inspector.config.ts", "build:engine": "vite build --mode=production --config vite.engine.config.ts", "build:cloud": "vite build --mode=production --config vite.cloud.config.ts", "preview:inspector": "vite preview --config vite.inspector.config.ts", diff --git a/frontend/packages/example-registry/src/_gen.ts b/frontend/packages/example-registry/src/_gen.ts new file mode 100644 index 0000000000..0b5983b24f --- /dev/null +++ b/frontend/packages/example-registry/src/_gen.ts @@ -0,0 +1,666 @@ +// This file is auto-generated by scripts/build/index.ts +// Do not edit manually + +export interface Template { + name: string; + displayName: string; + description: string; + technologies: string[]; + tags: string[]; + noFrontend: boolean; + priority?: number; + providers: { + [key: string]: { + name: string; + deployUrl: string; + }; + } +} + +export const templates: Template[] = [ + { + "name": "hello-world", + "displayName": "Hello World", + "description": "A minimal example demonstrating RivetKit with a real-time counter shared across multiple clients.", + "technologies": [ + "rivet", + "react", + "typescript" + ], + "tags": [ + "starter" + ], + "noFrontend": false, + "priority": 100, + "providers": { + "vercel": { + "name": "hello-world-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fhello-world-vercel&project-name=hello-world-vercel" + } + } + }, + { + "name": "sandbox", + "displayName": "Sandbox", + "description": "Unified sandbox showcasing Rivet Actor features with a single registry, grouped navigation, and interactive demos.", + "technologies": [ + "rivet", + "react", + "typescript" + ], + "tags": [ + "starter" + ], + "noFrontend": false, + "priority": 100, + "providers": { + "vercel": { + "name": "sandbox-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fsandbox-vercel&project-name=sandbox-vercel" + } + } + }, + { + "name": "ai-agent", + "displayName": "AI Agent", + "description": "Example project demonstrating queue-driven Rivet Actor AI agents with streaming Vercel AI SDK responses.", + "technologies": [ + "rivet", + "react", + "typescript" + ], + "tags": [ + "ai", + "real-time" + ], + "noFrontend": false, + "priority": 120, + "providers": { + "vercel": { + "name": "ai-agent-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fai-agent-vercel&project-name=ai-agent-vercel" + } + } + }, + { + "name": "sandbox-coding-agent", + "displayName": "Sandbox Coding Agent", + "description": "Example project demonstrating queue-driven Rivet Actor sessions that control a Sandbox Agent coding runtime.", + "technologies": [ + "rivet", + "react", + "typescript" + ], + "tags": [ + "ai", + "real-time" + ], + "noFrontend": false, + "priority": 120, + "providers": { + "vercel": { + "name": "sandbox-coding-agent-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fsandbox-coding-agent-vercel&project-name=sandbox-coding-agent-vercel" + } + } + }, + { + "name": "chat-room", + "displayName": "Chat Room", + "description": "Example project demonstrating real-time messaging and actor state management.", + "technologies": [ + "rivet", + "react", + "typescript" + ], + "tags": [ + "real-time" + ], + "noFrontend": false, + "priority": 200, + "providers": { + "vercel": { + "name": "chat-room-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fchat-room-vercel&project-name=chat-room-vercel" + } + } + }, + { + "name": "collaborative-document", + "displayName": "Collaborative Document", + "description": "A shared text editor that uses Rivet Actors with Yjs for real-time CRDT sync and presence.", + "technologies": [ + "rivet", + "react", + "typescript" + ], + "tags": [ + "real-time" + ], + "noFrontend": false, + "priority": 200, + "providers": { + "vercel": { + "name": "collaborative-document-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fcollaborative-document-vercel&project-name=collaborative-document-vercel" + } + } + }, + { + "name": "per-tenant-database", + "displayName": "Per-Tenant Database", + "description": "Example project demonstrating per-company database isolation with Rivet Actor state.", + "technologies": [ + "rivet", + "react", + "typescript" + ], + "tags": [ + "database" + ], + "noFrontend": false, + "priority": 200, + "providers": { + "vercel": { + "name": "per-tenant-database-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fper-tenant-database-vercel&project-name=per-tenant-database-vercel" + } + } + }, + { + "name": "cursors", + "displayName": "Real-time Collaborative Cursors", + "description": "Example project demonstrating real-time cursor tracking and collaborative canvas.", + "technologies": [ + "rivet", + "react", + "typescript" + ], + "tags": [ + "real-time" + ], + "noFrontend": false, + "priority": 300, + "providers": { + "vercel": { + "name": "cursors-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fcursors-vercel&project-name=cursors-vercel" + } + } + }, + { + "name": "ai-and-user-generated-actors-freestyle", + "displayName": "User and AI Generated Actors Freestyle Deployer", + "description": "Shows how to deploy user or AI-generated Rivet Actor code using a sandboxed namespace and Freestyle", + "technologies": [ + "rivet", + "react", + "hono", + "typescript" + ], + "tags": [ + "ai" + ], + "noFrontend": false, + "priority": 400, + "providers": {} + }, + { + "name": "multiplayer-game", + "displayName": "Multiplayer Game", + "description": "A real-time Agar.io style arena showing a matchmaker coordinator and GameRoom data actors.", + "technologies": [ + "rivet", + "react", + "typescript" + ], + "tags": [ + "real-time" + ], + "noFrontend": false, + "priority": 500, + "providers": { + "vercel": { + "name": "multiplayer-game-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fmultiplayer-game-vercel&project-name=multiplayer-game-vercel" + } + } + }, + { + "name": "actor-actions", + "displayName": "Actor Actions", + "description": "Demonstrates how to define and call actions on Rivet Actors for RPC-style communication between actors and clients.", + "technologies": [ + "rivet", + "typescript" + ], + "tags": [], + "noFrontend": false, + "priority": 1000, + "providers": { + "vercel": { + "name": "actor-actions-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Factor-actions-vercel&project-name=actor-actions-vercel" + } + } + }, + { + "name": "cross-actor-actions", + "displayName": "Cross-Actor Actions", + "description": "Demonstrates how actors can call actions on other actors for inter-actor communication and coordination.", + "technologies": [ + "rivet", + "typescript" + ], + "tags": [], + "noFrontend": false, + "priority": 1000, + "providers": { + "vercel": { + "name": "cross-actor-actions-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fcross-actor-actions-vercel&project-name=cross-actor-actions-vercel" + } + } + }, + { + "name": "geo-distributed-database", + "displayName": "Geo-Distributed Database", + "description": "Store user session state in edge-local Rivet Actors so preferences and activity stay close to users.", + "technologies": [ + "rivet", + "react", + "typescript" + ], + "tags": [ + "database" + ], + "noFrontend": false, + "priority": 1000, + "providers": { + "vercel": { + "name": "geo-distributed-database-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fgeo-distributed-database-vercel&project-name=geo-distributed-database-vercel" + } + } + }, + { + "name": "raw-fetch-handler", + "displayName": "Raw Fetch Handler Example", + "description": "Example project demonstrating raw HTTP fetch handling with Hono integration.", + "technologies": [ + "rivet", + "typescript" + ], + "tags": [], + "noFrontend": false, + "priority": 1000, + "providers": { + "vercel": { + "name": "raw-fetch-handler-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fraw-fetch-handler-vercel&project-name=raw-fetch-handler-vercel" + } + } + }, + { + "name": "raw-websocket-handler", + "displayName": "Raw WebSocket Handler", + "description": "Demonstrates raw WebSocket handling with direct actor connections and real-time chat functionality.", + "technologies": [ + "rivet", + "websocket", + "typescript" + ], + "tags": [ + "real-time" + ], + "noFrontend": false, + "priority": 1000, + "providers": { + "vercel": { + "name": "raw-websocket-handler-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fraw-websocket-handler-vercel&project-name=raw-websocket-handler-vercel" + } + } + }, + { + "name": "scheduling", + "displayName": "Scheduling", + "description": "Demonstrates how to schedule tasks and execute code at specific times or intervals using Rivet Actors.", + "technologies": [ + "rivet", + "typescript" + ], + "tags": [], + "noFrontend": false, + "priority": 1000, + "providers": { + "vercel": { + "name": "scheduling-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fscheduling-vercel&project-name=scheduling-vercel" + } + } + }, + { + "name": "state", + "displayName": "State Management", + "description": "Demonstrates persistent state management in Rivet Actors with automatic state saving and restoration.", + "technologies": [ + "rivet", + "typescript" + ], + "tags": [], + "noFrontend": false, + "priority": 1000, + "providers": { + "vercel": { + "name": "state-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fstate-vercel&project-name=state-vercel" + } + } + }, + { + "name": "cloudflare-workers", + "displayName": "Cloudflare Workers", + "description": "Example project demonstrating Cloudflare Workers deployment.", + "technologies": [ + "rivet", + "cloudflare-workers", + "typescript" + ], + "tags": [], + "noFrontend": true, + "providers": {} + }, + { + "name": "cloudflare-workers-hono", + "displayName": "Cloudflare Workers with Hono", + "description": "Example project demonstrating Cloudflare Workers deployment with Hono router.", + "technologies": [ + "rivet", + "cloudflare-workers", + "hono", + "typescript" + ], + "tags": [], + "noFrontend": true, + "providers": {} + }, + { + "name": "cursors-raw-websocket", + "displayName": "Real-time Collaborative Cursors (Raw WebSocket)", + "description": "Demonstrates real-time cursor tracking and collaborative canvas using raw WebSocket handlers instead of RivetKit's higher-level WebSocket abstraction.", + "technologies": [ + "rivet", + "websocket", + "typescript" + ], + "tags": [ + "real-time" + ], + "noFrontend": false, + "providers": { + "vercel": { + "name": "cursors-raw-websocket-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fcursors-raw-websocket-vercel&project-name=cursors-raw-websocket-vercel" + } + } + }, + { + "name": "custom-serverless", + "displayName": "Custom Serverless", + "description": "Example demonstrating custom serverless actor deployment with automatic engine configuration.", + "technologies": [ + "rivet", + "typescript" + ], + "tags": [ + "starter" + ], + "noFrontend": true, + "providers": { + "vercel": { + "name": "custom-serverless-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fcustom-serverless-vercel&project-name=custom-serverless-vercel" + } + } + }, + { + "name": "elysia", + "displayName": "Elysia Integration", + "description": "Example project demonstrating Elysia web framework integration.", + "technologies": [ + "rivet", + "elysia", + "typescript" + ], + "tags": [], + "noFrontend": true, + "providers": { + "vercel": { + "name": "elysia-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Felysia-vercel&project-name=elysia-vercel" + } + } + }, + { + "name": "experimental-durable-streams-ai-agent", + "displayName": "AI Agent with Durable Streams (Experimental)", + "description": "Example project demonstrating how to build an AI agent that communicates through durable streams for reliable message delivery and persistence.", + "technologies": [ + "rivet", + "typescript", + "durable-streams" + ], + "tags": [ + "ai", + "real-time", + "experimental" + ], + "noFrontend": false, + "providers": { + "vercel": { + "name": "experimental-durable-streams-ai-agent-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fexperimental-durable-streams-ai-agent-vercel&project-name=experimental-durable-streams-ai-agent-vercel" + } + } + }, + { + "name": "hono", + "displayName": "Hono Integration", + "description": "Build type-safe HTTP APIs with Hono web framework and RivetKit Actors. Features lightweight routing, middleware support, and seamless actor integration.", + "technologies": [ + "rivet", + "hono", + "typescript" + ], + "tags": [], + "noFrontend": true, + "providers": { + "vercel": { + "name": "hono-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fhono-vercel&project-name=hono-vercel" + } + } + }, + { + "name": "hono-react", + "displayName": "Hono + React", + "description": "Example demonstrating full-stack Hono backend with React frontend integration.", + "technologies": [ + "rivet", + "hono", + "react", + "typescript" + ], + "tags": [ + "starter" + ], + "noFrontend": false, + "providers": { + "vercel": { + "name": "hono-react-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fhono-react-vercel&project-name=hono-react-vercel" + } + } + }, + { + "name": "kitchen-sink", + "displayName": "Kitchen Sink Example", + "description": "Example project demonstrating all RivetKit features.", + "technologies": [ + "rivet", + "typescript" + ], + "tags": [], + "noFrontend": false, + "providers": { + "vercel": { + "name": "kitchen-sink-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fkitchen-sink-vercel&project-name=kitchen-sink-vercel" + } + } + }, + { + "name": "native-websockets", + "displayName": "Native WebSockets", + "description": "Demonstrates native WebSocket integration with Rivet Actors for real-time bidirectional communication.", + "technologies": [ + "rivet", + "websocket", + "typescript" + ], + "tags": [ + "real-time" + ], + "noFrontend": false, + "providers": { + "vercel": { + "name": "native-websockets-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fnative-websockets-vercel&project-name=native-websockets-vercel" + } + } + }, + { + "name": "next-js", + "displayName": "Next.js", + "description": "Minimal Next.js example demonstrating basic actor state management and real-time updates.", + "technologies": [ + "rivet", + "next-js", + "react", + "typescript" + ], + "tags": [ + "starter" + ], + "noFrontend": false, + "providers": {} + }, + { + "name": "raw-websocket-handler-proxy", + "displayName": "Raw WebSocket Handler Proxy", + "description": "Demonstrates raw WebSocket handling using a proxy endpoint pattern for routing connections to actors.", + "technologies": [ + "rivet", + "websocket", + "typescript" + ], + "tags": [], + "noFrontend": false, + "providers": { + "vercel": { + "name": "raw-websocket-handler-proxy-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fraw-websocket-handler-proxy-vercel&project-name=raw-websocket-handler-proxy-vercel" + } + } + }, + { + "name": "react", + "displayName": "React Integration", + "description": "Demonstrates React frontend integration with Rivet Actors.", + "technologies": [ + "rivet", + "react", + "typescript" + ], + "tags": [], + "noFrontend": false, + "providers": { + "vercel": { + "name": "react-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Freact-vercel&project-name=react-vercel" + } + } + }, + { + "name": "sqlite-drizzle", + "displayName": "Drizzle Integration", + "description": "Demonstrates Drizzle ORM integration with Rivet Actors for type-safe database operations.", + "technologies": [ + "rivet", + "drizzle", + "typescript" + ], + "tags": [ + "database" + ], + "noFrontend": true, + "providers": {} + }, + { + "name": "sqlite-raw", + "displayName": "SQLite Raw Example", + "description": "This example demonstrates using the raw SQLite driver with RivetKit actors.", + "technologies": [ + "rivet", + "typescript" + ], + "tags": [ + "database" + ], + "noFrontend": true, + "providers": { + "vercel": { + "name": "sqlite-raw-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fsqlite-raw-vercel&project-name=sqlite-raw-vercel" + } + } + }, + { + "name": "stream", + "displayName": "Stream Processor", + "description": "Example project demonstrating real-time top-K stream processing.", + "technologies": [ + "rivet", + "typescript" + ], + "tags": [ + "real-time" + ], + "noFrontend": false, + "providers": { + "vercel": { + "name": "stream-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fstream-vercel&project-name=stream-vercel" + } + } + }, + { + "name": "trpc", + "displayName": "tRPC Integration", + "description": "Example project demonstrating tRPC integration.", + "technologies": [ + "rivet", + "trpc", + "typescript" + ], + "tags": [], + "noFrontend": true, + "providers": { + "vercel": { + "name": "trpc-vercel", + "deployUrl": "https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Ftrpc-vercel&project-name=trpc-vercel" + } + } + } +]; diff --git a/frontend/src/components/actors/actor-inspector-context.tsx b/frontend/src/components/actors/actor-inspector-context.tsx index 6c2001ac51..239bf5d682 100644 --- a/frontend/src/components/actors/actor-inspector-context.tsx +++ b/frontend/src/components/actors/actor-inspector-context.tsx @@ -17,7 +17,7 @@ import { type ToServer, TO_CLIENT_VERSIONED as toClient, TO_SERVER_VERSIONED as toServer, -} from "rivetkit/inspector"; +} from "rivetkit/inspector/client"; import { toast } from "sonner"; import { match } from "ts-pattern"; import z from "zod"; @@ -789,11 +789,12 @@ export const ActorInspectorProvider = ({ }, getDatabaseTableRows: async (table, limit, offset) => { - const { id, promise } = - actionsManager.current.createResolver({ - name: "getDatabaseTableRows", - timeoutMs: 10_000, - }); + const { id, promise } = actionsManager.current.createResolver< + unknown[] + >({ + name: "getDatabaseTableRows", + timeoutMs: 10_000, + }); sendMessage( serverMessage( diff --git a/frontend/src/components/actors/workflow/transform-workflow-history.ts b/frontend/src/components/actors/workflow/transform-workflow-history.ts index 5c20c98831..47996e20dc 100644 --- a/frontend/src/components/actors/workflow/transform-workflow-history.ts +++ b/frontend/src/components/actors/workflow/transform-workflow-history.ts @@ -1,12 +1,19 @@ import * as cbor from "cbor-x"; import type { TransportWorkflowHistory, - decodeWorkflowHistoryTransport, -} from "rivetkit/inspector"; -import type { WorkflowHistory, EntryKind, EntryStatus, Location, SleepState, BranchStatus, BranchStatusType, EntryKindType } from "./workflow-types"; +} from "rivetkit/inspector/client"; +import type { + BranchStatus, + BranchStatusType, + EntryKind, + EntryKindType, + EntryStatus, + Location, + SleepState, + WorkflowHistory, +} from "./workflow-types"; type TransportWorkflowEntry = TransportWorkflowHistory["entries"][number]; -type TransportWorkflowEntryMetadata = ReturnType; function decodeCborOrNull(data: ArrayBuffer | null): unknown { if (data === null) return undefined; @@ -62,7 +69,10 @@ function transformBranchStatusType(status: string): BranchStatusType { } function transformBranches( - branches: ReadonlyMap, + branches: ReadonlyMap< + string, + { status: string; output: ArrayBuffer | null; error: string | null } + >, ): Record { const result: Record = {}; for (const [name, branch] of branches) { @@ -165,7 +175,8 @@ function buildEntryKey( if (typeof segment === "number") { return nameRegistry[segment] ?? `unknown-${segment}`; } - const loopName = nameRegistry[segment.loop] ?? `loop-${segment.loop}`; + const loopName = + nameRegistry[segment.loop] ?? `loop-${segment.loop}`; return `${loopName}[${segment.iteration}]`; }) .join("/"); @@ -191,9 +202,14 @@ export function transformWorkflowHistory( location, kind: transformEntryKind(entry.kind), dirty: false, - status: meta ? transformEntryStatus(meta.status) : ("pending" as EntryStatus), + status: meta + ? transformEntryStatus(meta.status) + : ("pending" as EntryStatus), startedAt: meta ? Number(meta.createdAt) : undefined, - completedAt: meta?.completedAt != null ? Number(meta.completedAt) : undefined, + completedAt: + meta?.completedAt != null + ? Number(meta.completedAt) + : undefined, retryCount: meta ? meta.attempts : undefined, error: meta?.error ?? undefined, }, @@ -204,7 +220,9 @@ export function transformWorkflowHistory( const hasRunning = history.some((h) => h.entry.status === "running"); const hasFailed = history.some((h) => h.entry.status === "failed"); const hasPending = history.some((h) => h.entry.status === "pending"); - const allCompleted = history.length > 0 && history.every((h) => h.entry.status === "completed"); + const allCompleted = + history.length > 0 && + history.every((h) => h.entry.status === "completed"); let state: WorkflowHistory["state"] = "pending"; if (allCompleted) { @@ -213,7 +231,10 @@ export function transformWorkflowHistory( state = "failed"; } else if (hasRunning) { state = "running"; - } else if (hasPending && history.some((h) => h.entry.status === "completed")) { + } else if ( + hasPending && + history.some((h) => h.entry.status === "completed") + ) { state = "running"; } diff --git a/frontend/turbo.json b/frontend/turbo.json index 5a0eeaa05d..22755f5fc6 100644 --- a/frontend/turbo.json +++ b/frontend/turbo.json @@ -2,6 +2,10 @@ "$schema": "https://turbo.build/schema.json", "extends": ["//"], "tasks": { + "build": { + "dependsOn": ["build:inspector"], + "outputs": ["dist/**"] + }, "build:engine": { "dependsOn": ["^build"], "env": ["VITE_APP_*", "DEPLOYMENT_TYPE"], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ef935acf7..8a013f68ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4381,6 +4381,9 @@ importers: pino: specifier: ^9.5.0 version: 9.9.5 + tar: + specifier: ^7.5.0 + version: 7.5.7 uuid: specifier: ^12.0.0 version: 12.0.0 @@ -27409,6 +27412,7 @@ snapshots: dependencies: minipass: 7.1.2 + mitt@3.0.1: {} mkdirp-classic@0.5.3: optional: true @@ -29696,6 +29700,17 @@ snapshots: pump: 3.0.3 tar-stream: 2.2.0 optional: true + tar-fs@3.1.1: + dependencies: + pump: 3.0.3 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.5.2 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a tar-stream@2.2.0: dependencies: @@ -29705,6 +29720,14 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 optional: true + tar-stream@3.1.7: + dependencies: + b4a: 1.7.3 + fast-fifo: 1.3.2 + streamx: 2.23.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a tar@7.5.7: dependencies: diff --git a/rivetkit-typescript/packages/rivetkit/package.json b/rivetkit-typescript/packages/rivetkit/package.json index d806e15032..b53fc95c12 100644 --- a/rivetkit-typescript/packages/rivetkit/package.json +++ b/rivetkit-typescript/packages/rivetkit/package.json @@ -45,6 +45,10 @@ }, "./client": { "import": { + "browser": { + "types": "./dist/browser/client.d.ts", + "default": "./dist/browser/client.js" + }, "types": "./dist/tsup/client/mod.d.ts", "default": "./dist/tsup/client/mod.js" }, @@ -163,6 +167,12 @@ "default": "./dist/tsup/inspector/mod.cjs" } }, + "./inspector/client": { + "import": { + "types": "./dist/browser/inspector/client.d.ts", + "default": "./dist/browser/inspector/client.js" + } + }, "./db": { "import": { "types": "./dist/tsup/db/mod.d.ts", @@ -192,7 +202,8 @@ "./dist/tsup/chunk-*.cjs" ], "scripts": { - "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/common/websocket.ts src/actor/errors.ts src/topologies/coordinate/mod.ts src/topologies/partition/mod.ts src/utils.ts src/driver-helpers/mod.ts src/driver-test-suite/mod.ts src/serve-test-suite/mod.ts src/test/mod.ts src/inspector/mod.ts src/workflow/mod.ts src/db/mod.ts src/db/drizzle/mod.ts", + "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/common/websocket.ts src/actor/errors.ts src/topologies/coordinate/mod.ts src/topologies/partition/mod.ts src/utils.ts src/driver-helpers/mod.ts src/driver-test-suite/mod.ts src/serve-test-suite/mod.ts src/test/mod.ts src/inspector/mod.ts src/workflow/mod.ts src/db/mod.ts src/db/drizzle/mod.ts", + "build:browser": "tsup --config tsup.browser.config.ts", "build:schema": "./scripts/compile-bare.ts compile schemas/client-protocol/v1.bare -o dist/schemas/client-protocol/v1.ts && ./scripts/compile-bare.ts compile schemas/client-protocol/v2.bare -o dist/schemas/client-protocol/v2.ts && ./scripts/compile-bare.ts compile schemas/client-protocol/v3.bare -o dist/schemas/client-protocol/v3.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v1.bare -o dist/schemas/file-system-driver/v1.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v2.bare -o dist/schemas/file-system-driver/v2.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v3.bare -o dist/schemas/file-system-driver/v3.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v1.bare -o dist/schemas/actor-persist/v1.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v2.bare -o dist/schemas/actor-persist/v2.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v3.bare -o dist/schemas/actor-persist/v3.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v4.bare -o dist/schemas/actor-persist/v4.ts && ./scripts/compile-bare.ts compile schemas/persist/v1.bare -o dist/schemas/persist/v1.ts && ./scripts/compile-bare.ts compile schemas/transport/v1.bare -o dist/schemas/transport/v1.ts && ./scripts/compile-bare.ts compile schemas/actor-inspector/v1.bare -o dist/schemas/actor-inspector/v1.ts && ./scripts/compile-bare.ts compile schemas/actor-inspector/v2.bare -o dist/schemas/actor-inspector/v2.ts && ./scripts/compile-bare.ts compile schemas/actor-inspector/v3.bare -o dist/schemas/actor-inspector/v3.ts", "check-types": "tsc --noEmit", "lint": "biome check .", @@ -204,7 +215,8 @@ "manager-openapi-gen": "tsx scripts/manager-openapi-gen.ts", "dump-asyncapi": "tsx scripts/dump-asyncapi.ts", "registry-config-schema-gen": "tsx scripts/registry-config-schema-gen.ts", - "actor-config-schema-gen": "tsx scripts/actor-config-schema-gen.ts" + "actor-config-schema-gen": "tsx scripts/actor-config-schema-gen.ts", + "build:pack-inspector": "tsx scripts/pack-inspector.ts" }, "dependencies": { "@hono/standard-validator": "^0.1.3", @@ -225,6 +237,7 @@ "nanoevents": "^9.1.0", "p-retry": "^6.2.1", "pino": "^9.5.0", + "tar": "^7.5.0", "uuid": "^12.0.0", "vbare": "^0.0.4", "wa-sqlite": "^1.0.0", @@ -281,4 +294,3 @@ }, "stableVersion": "0.8.0" } - diff --git a/rivetkit-typescript/packages/rivetkit/scripts/pack-inspector.ts b/rivetkit-typescript/packages/rivetkit/scripts/pack-inspector.ts new file mode 100644 index 0000000000..488be3cfc0 --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/scripts/pack-inspector.ts @@ -0,0 +1,20 @@ +import { existsSync } from "node:fs"; +import { mkdir } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { create } from "tar"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const src = join(__dirname, "../../../../frontend/dist/inspector"); +const destDir = join(__dirname, "../dist"); +const destTar = join(destDir, "inspector.tar.gz"); + +if (!existsSync(src)) { + throw new Error( + `Inspector frontend not built yet. Run 'pnpm turbo build:inspector --filter=@rivetkit/engine-frontend' first.`, + ); +} + +await mkdir(destDir, { recursive: true }); +await create({ gzip: true, file: destTar, cwd: src }, ["."]); +console.log(`Packed inspector into ${destTar}`); diff --git a/rivetkit-typescript/packages/rivetkit/src/client/mod.browser.ts b/rivetkit-typescript/packages/rivetkit/src/client/mod.browser.ts new file mode 100644 index 0000000000..5a27f3f499 --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/src/client/mod.browser.ts @@ -0,0 +1,2 @@ +// Browser-safe client exports (all client functionality is browser-compatible) +export * from "./mod"; diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/mod.browser.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/mod.browser.ts new file mode 100644 index 0000000000..c18bcb704c --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/mod.browser.ts @@ -0,0 +1,8 @@ +// Browser-safe inspector exports (schemas and types only, no server runtime) +export * from "../schemas/actor-inspector/mod"; +export * from "../schemas/actor-inspector/versioned"; +export type { WorkflowHistory as TransportWorkflowHistory } from "../schemas/transport/mod"; +export { + decodeWorkflowHistoryTransport, + encodeWorkflowHistoryTransport, +} from "./transport"; diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/serve-ui.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/serve-ui.ts new file mode 100644 index 0000000000..58e35e6ec5 --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/serve-ui.ts @@ -0,0 +1,40 @@ + +import { extract } from "tar"; +import { getNodeFs, getNodeOs, getNodePath, getNodeUrl } from "@/utils/node"; + +let extractedDir: string | undefined; +let extractionPromise: Promise | undefined; + +export async function getInspectorDir(): Promise { + if (extractedDir !== undefined) return extractedDir; + if (extractionPromise !== undefined) return extractionPromise; + + const nodeFs = getNodeFs(); + const os = getNodeOs(); + const url = getNodeUrl(); + const path = getNodePath(); + + extractionPromise = (async () => { + const tarball = path.join( + path.dirname(url.fileURLToPath(import.meta.url)), + "../../dist/inspector.tar.gz", + ); + + try { + await nodeFs.access(tarball); + } catch { + throw new Error( + `Inspector tarball not found at ${tarball}. Run 'pnpm build:pack-inspector' first.`, + ); + } + + const dest = path.join(os.tmpdir(), "rivetkit-inspector"); + await nodeFs.mkdir(dest, { recursive: true }); + await extract({ file: tarball, cwd: dest }); + + extractedDir = dest; + return dest; + })(); + + return extractionPromise; +} diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts index 61d46802ea..1b78fcd453 100644 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts @@ -26,17 +26,7 @@ export function getInspectorUrl( ): string | undefined { if (!config.inspector.enabled) return undefined; - const url = new URL("https://inspect.rivet.dev"); - - // Only override endpoint if using non-default port or custom endpoint is set - const endpoint = - config.inspector.defaultEndpoint ?? - (config.managerPort !== 6420 - ? `http://127.0.0.1:${managerPort}` - : undefined); - if (endpoint) { - url.searchParams.set("u", endpoint); - } - - return url.href; + const base = + config.inspector.defaultEndpoint ?? `http://127.0.0.1:${managerPort}`; + return new URL("/ui/", base).href; } diff --git a/rivetkit-typescript/packages/rivetkit/src/manager/router.ts b/rivetkit-typescript/packages/rivetkit/src/manager/router.ts index 0310f9f26d..887bf1ff7c 100644 --- a/rivetkit-typescript/packages/rivetkit/src/manager/router.ts +++ b/rivetkit-typescript/packages/rivetkit/src/manager/router.ts @@ -1,13 +1,11 @@ +import { serveStatic } from "@hono/node-server/serve-static"; import { createRoute } from "@hono/zod-openapi"; import * as cbor from "cbor-x"; - import type { Hono } from "hono"; import invariant from "invariant"; import { z } from "zod/v4"; import { Forbidden, RestrictedFeature } from "@/actor/errors"; - import { deserializeActorKey, serializeActorKey } from "@/actor/keys"; - import type { Encoding } from "@/client/mod"; import { HEADER_RIVET_TOKEN, @@ -23,6 +21,7 @@ import type { TestInlineDriverCallRequest, TestInlineDriverCallResponse, } from "@/driver-test-suite/test-inline-client-driver"; +import { getInspectorDir } from "@/inspector/serve-ui"; import { ActorsCreateRequestSchema, type ActorsCreateResponse, @@ -95,9 +94,9 @@ export function buildManagerRouter( const actorIdsParsed = actor_ids ? actor_ids - .split(",") - .map((id) => id.trim()) - .filter((id) => id.length > 0) + .split(",") + .map((id) => id.trim()) + .filter((id) => id.length > 0) : undefined; const actors: ActorOutput[] = []; @@ -587,6 +586,33 @@ export function buildManagerRouter( }); } + if (config.inspector.enabled) { + let inspectorRoot: string | undefined; + + + router.get("/ui/*", async (c, next) => { + if (!inspectorRoot) { + inspectorRoot = await getInspectorDir(); + } + const root = inspectorRoot; + const rewrite = (path: string) => + path.replace(/^\/ui/, "") || "/"; + + return serveStatic({ + root, + rewriteRequestPath: rewrite, + onNotFound: async (_path, c) => { + await serveStatic({ root, path: "index.html" })( + c, + next, + ); + }, + })(c, next); + }); + + router.get("/ui", (c) => c.redirect("/ui/")); + } + router.get("/health", (c) => handleHealthRequest(c)); router.get("/metadata", (c) => diff --git a/rivetkit-typescript/packages/rivetkit/src/utils/node.ts b/rivetkit-typescript/packages/rivetkit/src/utils/node.ts index c60f2514d5..6ed67246bb 100644 --- a/rivetkit-typescript/packages/rivetkit/src/utils/node.ts +++ b/rivetkit-typescript/packages/rivetkit/src/utils/node.ts @@ -16,6 +16,7 @@ let nodePath: typeof import("node:path") | undefined; let nodeOs: typeof import("node:os") | undefined; let nodeChildProcess: typeof import("node:child_process") | undefined; let nodeStream: typeof import("node:stream/promises") | undefined; +let nodeUrl: typeof import("node:url") | undefined; let hasImportedDependencies = false; @@ -62,7 +63,7 @@ export function importNodeDependencies(): void { nodeStream = requireFn( /* webpackIgnore: true */ "node:stream/promises", ); - + nodeUrl = requireFn(/* webpackIgnore: true */ "node:url"); hasImportedDependencies = true; } catch (err) { console.warn( @@ -163,3 +164,15 @@ export function getNodeStream(): typeof import("node:stream/promises") { } return nodeStream; } + +/** + * Gets the Node.js url module lazily. + */ +export function getNodeUrl(): typeof import("node:url") { + if (!nodeUrl) { + throw new Error( + "Node url module not loaded. Ensure importNodeDependencies() has been called.", + ); + } + return nodeUrl; +} \ No newline at end of file diff --git a/rivetkit-typescript/packages/rivetkit/tsup.browser.config.ts b/rivetkit-typescript/packages/rivetkit/tsup.browser.config.ts new file mode 100644 index 0000000000..d1496c78e6 --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/tsup.browser.config.ts @@ -0,0 +1,49 @@ +/// + +import { defineConfig } from "tsup"; + +export default defineConfig({ + target: "esnext", + platform: "browser", + format: ["esm"], + sourcemap: true, + clean: false, // Don't clean since main build runs first + dts: { + compilerOptions: { + skipLibCheck: true, + resolveJsonModule: true, + lib: ["ESNext", "DOM"], + }, + }, + minify: false, + // CRITICAL: Disable code splitting to prevent shared chunks with Node.js code + splitting: false, + skipNodeModulesBundle: true, + external: [ + // Mark all Node.js-only packages as external + /^node:.*/, + "@hono/node-server", + "@hono/node-server/serve-static", + "@hono/node-ws", + "tar", + "module", + // Keep workspace packages external + "@rivetkit/traces", + "@rivetkit/traces/encoding", + "@rivetkit/traces/otlp", + "@rivetkit/workflow-engine", + ], + // No shims needed for browser builds + shims: false, + outDir: "dist/browser/", + entry: { + "inspector/client": "src/inspector/mod.browser.ts", + "client": "src/client/mod.browser.ts", + }, + define: { + "globalThis.CUSTOM_RIVETKIT_DEVTOOLS_URL": process.env + .CUSTOM_RIVETKIT_DEVTOOLS_URL + ? `"${process.env.CUSTOM_RIVETKIT_DEVTOOLS_URL}"` + : "false", + }, +}); diff --git a/rivetkit-typescript/packages/rivetkit/turbo.json b/rivetkit-typescript/packages/rivetkit/turbo.json index 647a65b2de..988a2fbfaa 100644 --- a/rivetkit-typescript/packages/rivetkit/turbo.json +++ b/rivetkit-typescript/packages/rivetkit/turbo.json @@ -39,12 +39,18 @@ "inputs": ["schemas/**/*.bare", "scripts/compile-bare.ts"], "outputs": ["dist/schemas/**/*.ts"] }, + "build:pack-inspector": { + "dependsOn": ["@rivetkit/engine-frontend#build:inspector"], + "inputs": ["scripts/pack-inspector.ts", "package.json"], + "outputs": ["dist/inspector.tar.gz"] + }, "build": { "dependsOn": [ "^build", "manager-openapi-gen", "dump-asyncapi", - "build:schema" + "build:schema", + "build:browser" ], "inputs": [ "src/**", @@ -52,9 +58,22 @@ "tsup.config.ts", "package.json" ], - "outputs": ["dist/**"], + "outputs": ["dist/tsup/**"], "env": ["FAST_BUILD", "CUSTOM_RIVETKIT_DEVTOOLS_URL"] }, + "build:browser": { + "dependsOn": ["build:schema"], + "inputs": [ + "src/client/mod.browser.ts", + "src/inspector/mod.browser.ts", + "src/**", + "tsconfig.json", + "tsup.browser.config.ts", + "package.json" + ], + "outputs": ["dist/browser/**"], + "env": ["CUSTOM_RIVETKIT_DEVTOOLS_URL"] + }, "test": { "dependsOn": ["^test", "build"], "inputs": ["src/**", "tests/**", "fixtures/**"] @@ -62,6 +81,10 @@ "check-types": { "dependsOn": ["^build", "build:schema"], "inputs": ["src/**", "schemas/**/*.bare", "tsconfig.json"] + }, + "build:publish": { + "dependsOn": ["build", "build:pack-inspector"], + "outputs": [] } } }