From 67935e5b9eb39e10f096c32eafc0cfb21ebbece5 Mon Sep 17 00:00:00 2001 From: jog1t <39823706+jog1t@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:35:43 +0000 Subject: [PATCH 1/8] fix: rivetkit build (#4147) # Description This PR removes the unused `waitForNames` method from the `QueueManager` class and reorganizes imports across several files to improve code organization. It also fixes the Drizzle ORM exports by creating a new `sqlite-core.ts` file that re-exports specific functions from the Drizzle ORM package, rather than exporting everything directly. Additionally, the PR updates the `createFileSystemOrMemoryDriver` function to accept an options object instead of separate parameters, making the API more flexible and consistent. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [x] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? The changes have been tested with the existing test suite to ensure that the removal of the unused method and the reorganization of imports don't break any functionality. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes --- examples/sandbox-vercel/.gitignore | 4 + examples/sandbox-vercel/README.md | 44 + examples/sandbox-vercel/api/index.ts | 3 + examples/sandbox-vercel/frontend/App.tsx | 981 +++++++++++++++ examples/sandbox-vercel/frontend/main.tsx | 12 + examples/sandbox-vercel/frontend/page-data.ts | 1090 +++++++++++++++++ examples/sandbox-vercel/index.html | 787 ++++++++++++ examples/sandbox-vercel/package.json | 50 + examples/sandbox-vercel/src/actors.ts | 213 ++++ .../src/actors/actions/action-inputs.ts | 29 + .../src/actors/actions/action-timeout.ts | 58 + .../src/actors/actions/action-types.ts | 82 ++ .../src/actors/actions/error-handling.ts | 91 ++ .../sandbox-vercel/src/actors/ai/ai-agent.ts | 61 + .../sandbox-vercel/src/actors/ai/my-tools.ts | 11 + .../sandbox-vercel/src/actors/ai/types.ts | 5 + .../src/actors/connections/conn-state.ts | 112 ++ .../actors/connections/reject-connection.ts | 15 + .../src/actors/connections/request-access.ts | 153 +++ .../src/actors/counter/conn-params.ts | 27 + .../src/actors/counter/counter-conn.ts | 33 + .../src/actors/counter/counter.ts | 20 + .../src/actors/counter/lifecycle.ts | 34 + .../src/actors/http/raw-fetch-counter.ts | 43 + .../http/raw-http-request-properties.ts | 95 ++ .../src/actors/http/raw-http.ts | 124 ++ .../actors/http/raw-websocket-chat-room.ts | 73 ++ .../src/actors/http/raw-websocket.ts | 114 ++ .../actors/inter-actor/cross-actor-actions.ts | 176 +++ .../src/actors/lifecycle/destroy.ts | 43 + .../src/actors/lifecycle/hibernation.ts | 78 ++ .../src/actors/lifecycle/run.ts | 165 +++ .../src/actors/lifecycle/scheduled.ts | 76 ++ .../src/actors/lifecycle/sleep.ts | 196 +++ .../src/actors/queue/keep-awake.ts | 70 ++ .../src/actors/queue/multi-queue.ts | 31 + .../sandbox-vercel/src/actors/queue/queue.ts | 123 ++ .../src/actors/queue/self-sender.ts | 48 + .../sandbox-vercel/src/actors/queue/sender.ts | 36 + .../src/actors/queue/timeout.ts | 44 + .../sandbox-vercel/src/actors/queue/worker.ts | 45 + .../src/actors/state/actor-onstatechange.ts | 43 + .../sandbox-vercel/src/actors/state/kv.ts | 43 + .../src/actors/state/large-payloads.ts | 86 ++ .../src/actors/state/metadata.ts | 75 ++ .../sandbox-vercel/src/actors/state/vars.ts | 91 ++ .../src/actors/testing/inline-client.ts | 64 + .../src/actors/workflow/_helpers.ts | 12 + .../src/actors/workflow/approval.ts | 110 ++ .../src/actors/workflow/batch.ts | 125 ++ .../src/actors/workflow/dashboard.ts | 203 +++ .../src/actors/workflow/history-examples.ts | 627 ++++++++++ .../src/actors/workflow/order.ts | 89 ++ .../src/actors/workflow/payment.ts | 175 +++ .../src/actors/workflow/race.ts | 112 ++ .../src/actors/workflow/timer.ts | 69 ++ .../src/actors/workflow/workflow-fixtures.ts | 99 ++ .../sandbox-vercel/src/fdb-tuple-shim.mjs | 5 + examples/sandbox-vercel/src/server.ts | 8 + examples/sandbox-vercel/tsconfig.json | 27 + examples/sandbox-vercel/turbo.json | 6 + examples/sandbox-vercel/vercel.json | 9 + examples/sandbox-vercel/vite.config.ts | 6 + examples/sandbox/frontend/App.tsx | 727 ++++++----- examples/sandbox/frontend/page-data.ts | 406 +++--- examples/sandbox/index.html | 528 +++++++- examples/sandbox/package.json | 3 + examples/sandbox/src/actors.ts | 18 + .../src/actors/workflow/history-examples.ts | 627 ++++++++++ examples/sandbox/todo.md | 85 -- package.json | 3 +- pnpm-lock.yaml | 210 +++- .../packages/rivetkit/src/actor/definition.ts | 29 +- .../src/actor/instance/queue-manager.ts | 82 +- .../packages/rivetkit/src/client/mod.ts | 5 +- .../packages/rivetkit/src/db/drizzle/mod.ts | 2 +- .../rivetkit/src/db/drizzle/sqlite-core.ts | 22 + .../packages/rivetkit/src/db/mod.ts | 29 +- .../rivetkit/src/driver-helpers/mod.ts | 3 +- .../src/drivers/engine/actor-driver.ts | 2 +- .../src/drivers/file-system/global-state.ts | 9 +- .../rivetkit/src/drivers/file-system/mod.ts | 17 +- .../rivetkit/src/serve-test-suite/mod.ts | 19 +- .../packages/rivetkit/src/utils/node.ts | 2 +- 84 files changed, 9621 insertions(+), 786 deletions(-) create mode 100644 examples/sandbox-vercel/.gitignore create mode 100644 examples/sandbox-vercel/README.md create mode 100644 examples/sandbox-vercel/api/index.ts create mode 100644 examples/sandbox-vercel/frontend/App.tsx create mode 100644 examples/sandbox-vercel/frontend/main.tsx create mode 100644 examples/sandbox-vercel/frontend/page-data.ts create mode 100644 examples/sandbox-vercel/index.html create mode 100644 examples/sandbox-vercel/package.json create mode 100644 examples/sandbox-vercel/src/actors.ts create mode 100644 examples/sandbox-vercel/src/actors/actions/action-inputs.ts create mode 100644 examples/sandbox-vercel/src/actors/actions/action-timeout.ts create mode 100644 examples/sandbox-vercel/src/actors/actions/action-types.ts create mode 100644 examples/sandbox-vercel/src/actors/actions/error-handling.ts create mode 100644 examples/sandbox-vercel/src/actors/ai/ai-agent.ts create mode 100644 examples/sandbox-vercel/src/actors/ai/my-tools.ts create mode 100644 examples/sandbox-vercel/src/actors/ai/types.ts create mode 100644 examples/sandbox-vercel/src/actors/connections/conn-state.ts create mode 100644 examples/sandbox-vercel/src/actors/connections/reject-connection.ts create mode 100644 examples/sandbox-vercel/src/actors/connections/request-access.ts create mode 100644 examples/sandbox-vercel/src/actors/counter/conn-params.ts create mode 100644 examples/sandbox-vercel/src/actors/counter/counter-conn.ts create mode 100644 examples/sandbox-vercel/src/actors/counter/counter.ts create mode 100644 examples/sandbox-vercel/src/actors/counter/lifecycle.ts create mode 100644 examples/sandbox-vercel/src/actors/http/raw-fetch-counter.ts create mode 100644 examples/sandbox-vercel/src/actors/http/raw-http-request-properties.ts create mode 100644 examples/sandbox-vercel/src/actors/http/raw-http.ts create mode 100644 examples/sandbox-vercel/src/actors/http/raw-websocket-chat-room.ts create mode 100644 examples/sandbox-vercel/src/actors/http/raw-websocket.ts create mode 100644 examples/sandbox-vercel/src/actors/inter-actor/cross-actor-actions.ts create mode 100644 examples/sandbox-vercel/src/actors/lifecycle/destroy.ts create mode 100644 examples/sandbox-vercel/src/actors/lifecycle/hibernation.ts create mode 100644 examples/sandbox-vercel/src/actors/lifecycle/run.ts create mode 100644 examples/sandbox-vercel/src/actors/lifecycle/scheduled.ts create mode 100644 examples/sandbox-vercel/src/actors/lifecycle/sleep.ts create mode 100644 examples/sandbox-vercel/src/actors/queue/keep-awake.ts create mode 100644 examples/sandbox-vercel/src/actors/queue/multi-queue.ts create mode 100644 examples/sandbox-vercel/src/actors/queue/queue.ts create mode 100644 examples/sandbox-vercel/src/actors/queue/self-sender.ts create mode 100644 examples/sandbox-vercel/src/actors/queue/sender.ts create mode 100644 examples/sandbox-vercel/src/actors/queue/timeout.ts create mode 100644 examples/sandbox-vercel/src/actors/queue/worker.ts create mode 100644 examples/sandbox-vercel/src/actors/state/actor-onstatechange.ts create mode 100644 examples/sandbox-vercel/src/actors/state/kv.ts create mode 100644 examples/sandbox-vercel/src/actors/state/large-payloads.ts create mode 100644 examples/sandbox-vercel/src/actors/state/metadata.ts create mode 100644 examples/sandbox-vercel/src/actors/state/vars.ts create mode 100644 examples/sandbox-vercel/src/actors/testing/inline-client.ts create mode 100644 examples/sandbox-vercel/src/actors/workflow/_helpers.ts create mode 100644 examples/sandbox-vercel/src/actors/workflow/approval.ts create mode 100644 examples/sandbox-vercel/src/actors/workflow/batch.ts create mode 100644 examples/sandbox-vercel/src/actors/workflow/dashboard.ts create mode 100644 examples/sandbox-vercel/src/actors/workflow/history-examples.ts create mode 100644 examples/sandbox-vercel/src/actors/workflow/order.ts create mode 100644 examples/sandbox-vercel/src/actors/workflow/payment.ts create mode 100644 examples/sandbox-vercel/src/actors/workflow/race.ts create mode 100644 examples/sandbox-vercel/src/actors/workflow/timer.ts create mode 100644 examples/sandbox-vercel/src/actors/workflow/workflow-fixtures.ts create mode 100644 examples/sandbox-vercel/src/fdb-tuple-shim.mjs create mode 100644 examples/sandbox-vercel/src/server.ts create mode 100644 examples/sandbox-vercel/tsconfig.json create mode 100644 examples/sandbox-vercel/turbo.json create mode 100644 examples/sandbox-vercel/vercel.json create mode 100644 examples/sandbox-vercel/vite.config.ts create mode 100644 examples/sandbox/src/actors/workflow/history-examples.ts delete mode 100644 examples/sandbox/todo.md create mode 100644 rivetkit-typescript/packages/rivetkit/src/db/drizzle/sqlite-core.ts diff --git a/examples/sandbox-vercel/.gitignore b/examples/sandbox-vercel/.gitignore new file mode 100644 index 0000000000..e28f3d79db --- /dev/null +++ b/examples/sandbox-vercel/.gitignore @@ -0,0 +1,4 @@ +.actorcore +node_modules +dist +.vercel diff --git a/examples/sandbox-vercel/README.md b/examples/sandbox-vercel/README.md new file mode 100644 index 0000000000..f6374343f2 --- /dev/null +++ b/examples/sandbox-vercel/README.md @@ -0,0 +1,44 @@ +> **Note:** This is the Vercel-optimized version of the [sandbox](../sandbox) example. +> It uses the `hono/vercel` adapter and is configured for Vercel deployment. + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frivet-gg%2Frivet%2Ftree%2Fmain%2Fexamples%2Fsandbox-vercel&project-name=sandbox-vercel) + +# Sandbox + +Unified sandbox showcasing Rivet Actor features with a single registry, grouped navigation, and interactive demos. + +## Getting Started + +```bash +cd examples/sandbox +pnpm install +pnpm dev +``` + +## Features + +- Unified registry that aggregates actor fixtures and example actors +- Sidebar navigation grouped by core actor feature areas +- Action runner and event listener for quick experimentation +- Raw HTTP and WebSocket demos for handler-based actors +- Workflow and queue pattern coverage in a single sandbox + +## Prerequisites + +- OpenAI API key (set `OPENAI_API_KEY`) for the AI actor demo + +## Implementation + +The sandbox registry imports fixtures and example actors into one setup so each page can expose a curated subset. + +See the registry in [`src/actors.ts`](https://github.com/rivet-dev/rivet/tree/main/examples/sandbox/src/actors.ts) and the UI in [`frontend/App.tsx`](https://github.com/rivet-dev/rivet/tree/main/examples/sandbox/frontend/App.tsx). + +## Resources + +Read more about [Rivet Actors](https://rivet.dev/docs/actors), +[actions](https://rivet.dev/docs/actors/actions), and +[connections](https://rivet.dev/docs/actors/connections). + +## License + +MIT diff --git a/examples/sandbox-vercel/api/index.ts b/examples/sandbox-vercel/api/index.ts new file mode 100644 index 0000000000..07a830391f --- /dev/null +++ b/examples/sandbox-vercel/api/index.ts @@ -0,0 +1,3 @@ +import app from "../src/server.ts"; + +export default app; diff --git a/examples/sandbox-vercel/frontend/App.tsx b/examples/sandbox-vercel/frontend/App.tsx new file mode 100644 index 0000000000..44b7231e27 --- /dev/null +++ b/examples/sandbox-vercel/frontend/App.tsx @@ -0,0 +1,981 @@ +import { createRivetKit } from "@rivetkit/react"; +import mermaid from "mermaid"; +import { Highlight, themes } from "prism-react-renderer"; +import { + Code, + Compass, + Database, + FlaskConical, + GitBranch, + Globe, + List, + Network, + Radio, + RefreshCw, +} from "lucide-react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import type { registry } from "../src/actors.ts"; +import { + ACTION_TEMPLATES, + type ActionTemplate, + PAGE_GROUPS, + PAGE_INDEX, + type PageConfig, +} from "./page-data.ts"; + +type ActorName = (typeof registry)["config"]["use"] extends Record ? K & string : never; + +const GROUP_ICONS: Record> = { + compass: Compass, + code: Code, + database: Database, + radio: Radio, + globe: Globe, + "refresh-cw": RefreshCw, + list: List, + "git-branch": GitBranch, + network: Network, + "flask-conical": FlaskConical, +}; + +mermaid.initialize({ + startOnLoad: false, + theme: "dark", + themeVariables: { + darkMode: true, + background: "#0a0a0a", + primaryColor: "#1c1c1e", + primaryTextColor: "#ffffff", + primaryBorderColor: "#3a3a3c", + lineColor: "#3a3a3c", + secondaryColor: "#2c2c2e", + tertiaryColor: "#0f0f0f", + fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Roboto, sans-serif", + fontSize: "13px", + }, +}); + +function MermaidDiagram({ chart }: { chart: string }) { + const ref = useRef(null); + const [svg, setSvg] = useState(""); + + useEffect(() => { + let cancelled = false; + const id = `mermaid-${Math.random().toString(36).slice(2)}`; + mermaid.render(id, chart).then(({ svg: renderedSvg }) => { + if (!cancelled) setSvg(renderedSvg); + }); + return () => { cancelled = true; }; + }, [chart]); + + return
; +} + +const { useActor } = createRivetKit( + `${location.origin}/api/rivet`, +); + +type JsonResult = { ok: true; value: T } | { ok: false; error: string }; + +function parseJson(value: string): JsonResult { + try { + const parsed = JSON.parse(value) as T; + return { ok: true, value: parsed }; + } catch (error) { + return { + ok: false, + error: error instanceof Error ? error.message : "Invalid JSON", + }; + } +} + +function parseKey(value: string): JsonResult { + const trimmed = value.trim(); + if (trimmed.startsWith("[")) { + return parseJson(trimmed); + } + return { ok: true, value: trimmed || "demo" }; +} + +function formatJson(value: unknown) { + return JSON.stringify(value, null, 2); +} + +function usePersistedState(key: string, initial: T) { + const [state, setState] = useState(() => { + const stored = localStorage.getItem(key); + return stored ? (JSON.parse(stored) as T) : initial; + }); + + useEffect(() => { + localStorage.setItem(key, JSON.stringify(state)); + }, [key, state]); + + return [state, setState] as const; +} + +function resolvePage(pageId: string) { + return PAGE_INDEX.find((page) => page.id === pageId) ?? PAGE_INDEX[0]; +} + +function formatActorName(name: string) { + return name + .replace(/([a-z0-9])([A-Z])/g, "$1 $2") + .replace(/-/g, " ") + .replace(/_/g, " ") + .replace(/^\w/, (char) => char.toUpperCase()); +} + +function getStateAction(actorName: string): string | undefined { + const templates = ACTION_TEMPLATES[actorName] ?? []; + return templates.find(t => t.args.length === 0)?.action; +} + +// ── Main App ────────────────────────────────────── + +export function App() { + const [activePageId, setActivePageId] = usePersistedState( + "sandbox:page", + PAGE_GROUPS[0].pages[0].id, + ); + const activePage = resolvePage(activePageId); + + return ( +
+ + +
+
+

{activePage.title}

+

{activePage.description}

+
+ + {activePage.id === "welcome" ? ( + + ) : ( + + )} +
+
+ ); +} + +// ── Demo Panel Router ───────────────────────────── + +function DemoPanel({ page }: { page: PageConfig }) { + if (page.demo === "diagram") { + return ; + } + if (page.actors.length === 0) { + return ; + } + if (page.demo === "raw-http") { + return ; + } + if (page.demo === "raw-websocket") { + return ; + } + return ; +} + +// ── Actor Demo Panel (tabs + view + code) ───────── + +function ActorDemoPanel({ page }: { page: PageConfig }) { + const [selectedIdx, setSelectedIdx] = useState(0); + + useEffect(() => { setSelectedIdx(0); }, [page.id]); + + const actorName = page.actors[selectedIdx] ?? page.actors[0]; + + return ( +
+ {page.actors.length > 1 && ( +
+ {page.actors.map((name, idx) => ( + + ))} +
+ )} + + + +
+
+ Source +
+ +
+
+ ); +} + +// ── Actor View (two-column: controls | inspector) ─ + +function ActorView({ actorName, page }: { actorName: string; page: PageConfig }) { + const [keyInput, setKeyInput] = usePersistedState( + `sandbox:${page.id}:${actorName}:key`, + `demo-${page.id}`, + ); + const [paramsInput, setParamsInput] = usePersistedState( + `sandbox:${page.id}:${actorName}:params`, + "{}", + ); + const [createInput, setCreateInput] = usePersistedState( + `sandbox:${page.id}:${actorName}:input`, + "{}", + ); + + const parsedKey = useMemo(() => parseKey(keyInput), [keyInput]); + const parsedParams = useMemo( + () => parseJson>(paramsInput), + [paramsInput], + ); + const parsedInput = useMemo( + () => parseJson(createInput), + [createInput], + ); + + const resolvedParams = + parsedParams.ok && paramsInput.trim() !== "{}" + ? parsedParams.value + : undefined; + const resolvedInput = + parsedInput.ok && createInput.trim() !== "{}" + ? parsedInput.value + : undefined; + + const actor = useActor({ + name: actorName as ActorName, + key: parsedKey.ok ? parsedKey.value : "demo", + params: resolvedParams, + createWithInput: resolvedInput, + }); + + const templates = ACTION_TEMPLATES[actorName] ?? []; + const stateAction = getStateAction(actorName); + + const [stateRefreshCounter, setStateRefreshCounter] = useState(0); + const triggerStateRefresh = useCallback( + () => setStateRefreshCounter(c => c + 1), + [], + ); + + return ( +
+
+
+ {page.actors.length === 1 && ( + {formatActorName(actorName)} + )} +
+ + {actor.connStatus ?? "idle"} +
+
+
+
+ + setKeyInput(e.target.value)} + placeholder="demo" + /> +
+
+ + setParamsInput(e.target.value)} + placeholder="{}" + /> +
+
+ + setCreateInput(e.target.value)} + placeholder="{}" + /> +
+
+
+ +
+
+
Actions
+ +
+ +
+ {stateAction && ( + + )} + +
+
+
+ ); +} + +// ── State Panel ─────────────────────────────────── + +function StatePanel({ + actor, + stateAction, + refreshTrigger, +}: { + actor: ReturnType; + stateAction: string; + refreshTrigger: number; +}) { + const [state, setState] = useState(""); + const [isRefreshing, setIsRefreshing] = useState(false); + const handleRef = useRef(actor.handle); + handleRef.current = actor.handle; + + const refresh = useCallback(async () => { + const handle = handleRef.current; + if (!handle) return; + setIsRefreshing(true); + try { + const result = await handle.action({ + name: stateAction, + args: [], + }); + setState(formatJson(result)); + } catch (err) { + setState(`Error: ${err instanceof Error ? err.message : String(err)}`); + } finally { + setIsRefreshing(false); + } + }, [stateAction]); + + useEffect(() => { + if (actor.connStatus === "connected") { + refresh(); + } + }, [actor.connStatus, refresh]); + + useEffect(() => { + if (refreshTrigger > 0) { + refresh(); + } + }, [refreshTrigger, refresh]); + + return ( +
+
+ State + +
+
+ {actor.connStatus !== "connected" + ? "Connecting\u2026" + : state || "Loading\u2026"} +
+
+ ); +} + +// ── Events Panel ────────────────────────────────── + +function EventsPanel({ actor }: { actor: ReturnType }) { + const [eventName, setEventName] = useState(""); + const [events, setEvents] = useState>([]); + + useEffect(() => { + if (!eventName.trim() || !actor.connection) return; + + const stop = actor.connection.on(eventName, (...args: unknown[]) => { + const now = new Date(); + const time = [now.getHours(), now.getMinutes(), now.getSeconds()] + .map(n => n.toString().padStart(2, "0")) + .join(":"); + setEvents((prev) => [ + { time, data: formatJson(args.length === 1 ? args[0] : args) }, + ...prev.slice(0, 49), + ]); + }); + + return () => { stop(); }; + }, [actor.connection, eventName]); + + return ( +
+
+ Events +
+ setEventName(e.target.value)} + placeholder="event name" + className="inspector-input" + /> + {events.length > 0 && ( + + )} +
+
+
+ {events.length === 0 ? ( +
+ {eventName + ? "Waiting for events\u2026" + : "Enter an event name to listen"} +
+ ) : ( + events.map((entry, i) => ( +
+ {entry.time} + {entry.data} +
+ )) + )} +
+
+ ); +} + +// ── Code Block ──────────────────────────────────── + +function CodeBlock({ code }: { code: string }) { + return ( + + {({ tokens, getLineProps, getTokenProps }) => ( +
+					{tokens.map((line, i) => (
+						
+ {line.map((token, key) => ( + + ))} +
+ ))} +
+ )} +
+ ); +} + +// ── Action Runner ───────────────────────────────── + +function ActionRunner({ + actor, + templates, + onActionComplete, +}: { + actor: ReturnType; + templates: ActionTemplate[]; + onActionComplete?: () => void; +}) { + const [selectedIdx, setSelectedIdx] = useState(0); + const selectedTemplate = templates[selectedIdx]; + const [argsInput, setArgsInput] = useState( + selectedTemplate ? JSON.stringify(selectedTemplate.args) : "[]", + ); + const [result, setResult] = useState(""); + const [error, setError] = useState(""); + const [isRunning, setIsRunning] = useState(false); + + useEffect(() => { + setSelectedIdx(0); + if (templates[0]) { + setArgsInput(JSON.stringify(templates[0].args)); + } else { + setArgsInput("[]"); + } + }, [templates]); + + const selectTemplate = (idx: number) => { + setSelectedIdx(idx); + setArgsInput(JSON.stringify(templates[idx].args)); + setResult(""); + setError(""); + }; + + const parsedArgs = useMemo( + () => parseJson(argsInput), + [argsInput], + ); + + const runAction = async () => { + setError(""); + setResult(""); + const actionName = selectedTemplate?.action; + if (!actor.handle) { + setError("Actor handle is not ready."); + return; + } + if (!actionName) { + setError("Select an action to run."); + return; + } + if (!parsedArgs.ok) { + setError(parsedArgs.error); + return; + } + + setIsRunning(true); + try { + const response = await actor.handle.action({ + name: actionName, + args: parsedArgs.value, + }); + setResult(formatJson(response)); + onActionComplete?.(); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } finally { + setIsRunning(false); + } + }; + + if (templates.length === 0) { + return
No actions available for this actor.
; + } + + return ( +
+
+ {templates.map((template, idx) => ( + + ))} +
+ +
+
+ setArgsInput(event.target.value)} + placeholder="[]" + className="action-args-input" + /> + +
+ {!parsedArgs.ok &&
{parsedArgs.error}
} + {error &&
{error}
} + {result &&
{result}
} +
+
+ ); +} + +// ── Welcome / Diagram / Config ──────────────────── + +function WelcomePanel() { + return ( +
+

+ This sandbox lets you interact with every Rivet Actor feature + in one place. Pick a topic from the sidebar to connect to + live actors, invoke actions, and observe events in real time. +

+
+ ); +} + +function DiagramPanel({ page }: { page: PageConfig }) { + if (!page.diagram) return null; + + return ( +
+ +
+ ); +} + +function ConfigPlayground() { + const [jsonInput, setJsonInput] = useState( + '{\n "key": ["demo"],\n "params": {\n "region": "local"\n }\n}', + ); + const parsed = parseJson>(jsonInput); + + return ( +
+
+

Configuration Playground

+

+ Edit JSON to explore how actor configuration payloads are + shaped. +

+
+
+
+ +