Architecture - UpstandPlatform/OpenRive GitHub Wiki
OpenRive is a Better-T-Stack monorepo: Bun workspaces, Turborepo, Next.js 16 (App Router, React 19), Drizzle ORM on PostgreSQL, zod for validation and Zustand for state. The browser does all editing and rendering; the server only stores documents.
ββββββββββββββ browser ββββββββββββββββββββββββββββββββββββββ
β React UI (apps/web/src/components, packages/ui) β
β β actions / shortcuts / menus β
β βΌ β
β Zustand store (apps/web/src/lib/store/editor.ts) β
β doc: RiveDoc ββ undo history, auto-keying β
β β β β
β βΌ βΌ β
β exportRiv() StageEngine (apps/web/src/lib/engine.ts)β
β β official Rive WASM renders the stage β
βββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β REST + zod (apps/web/src/app/api)
βββββββΌββββββββ server / tools βββββββββββββββ
β @openrive/db (Drizzle β PostgreSQL) ββββ CLI + TUI (apps/cli)
β embedded PGlite, or DATABASE_URL ββββ MCP server (apps/cli/src/mcp-server.ts)
βββββββββββββββββββββββββββββββββββββββββββββββββ desktop (apps/desktop)
| Package | Role |
|---|---|
apps/web |
The editor: pages, panels, stage, and the REST API |
apps/cli |
openrive command: OpenTUI terminal UI, scriptable commands, MCP server |
apps/desktop |
Electrobun app that runs the standalone server in a native window |
packages/rive |
The .riv format and editing core (DOM-free) |
packages/db |
Drizzle schema, migrations, and every database query |
packages/storage |
S3-compatible object storage for cloud .riv assets |
packages/auth |
Password hashing, sessions and the permission rules |
packages/shared |
zod schemas and types (domain, API payloads, environment) |
packages/ui |
React components shared between panels and pages |
packages/config |
The base TypeScript configuration |
vendor/* |
Git submodules: the forked Rive SDKs (see Rive SDK submodules) |
Turborepo runs dev, build, lint and check-types across them (turbo.json).
| File | Role |
|---|---|
core-defs.json, schema.ts
|
Every Rive object type and property, generated from rive-runtime's dev/defs
|
binary.ts, riv-format.ts
|
Low-level reader/writer: header, table of contents, field encodings, unknown-property preservation |
document.ts |
importRiv / exportRiv: flat object stream β tree, index references β stable ids |
ops.ts |
Structural edits: insert, delete, reparent, group, reorder, duplicate, keyframes |
scene.ts |
Transforms, path geometry, bounds, hit testing |
api.ts |
High-level, name-addressable API used by templates, CLI, MCP and the Code panel |
theme.ts, text.ts, assets.ts, svg.ts
|
Theme colors, text and fonts, file assets, SVG import |
templates*.ts, examples.ts
|
Starter content |
Everything here is DOM-free, so it runs in the browser, in Bun and in tests.
types.ts defines the domain with zod (userSchema, projectMetaSchema, idSchema) and infers the TypeScript types
from it. api.ts holds the request schemas the route handlers parse. env.ts validates the environment once and
resolves the data folder. Because both sides share these schemas, the API, the CLI and the database agree on shapes.
schema.ts is the Drizzle schema (users, projects, settings); repository.ts is the only module that queries;
client.ts opens PostgreSQL (node-postgres) or the embedded PGlite and applies migrations at startup;
legacy.ts imports pre-Drizzle file storage. Migrations are generated by drizzle-kit and embedded by
scripts/embed-migrations.ts so they survive bundling.
@openrive/shared/env resolves one explicit edition: local, self_hosted, or cloud. Local and self-hosted retain
the existing embedded/PostgreSQL behavior and database-backed .riv compatibility. Cloud requires PostgreSQL, Redis,
Better Auth, and an S3-compatible provider. Project metadata and editor documents remain in PostgreSQL; cloud .riv
files are uploaded first to a versioned object key and then referenced by projects.riv_storage_key. This leaves the
previous readable object intact if an upload or database write fails. MinIO is used only by the private cloud Compose
stack; AWS S3 and Cloudflare R2 use the same adapter and environment contract.
Editor.tsx lays out the panels. Stage.tsx holds the canvas, SVG overlay, tools and inline text editing.
Inspector.tsx, Hierarchy.tsx, ThemePanel.tsx, AssetsPanel.tsx, CodePanel.tsx, Timeline.tsx and
StateMachinePanel.tsx are the other panels. actions.ts is the registry of commands (label, shortcut, enabled
state, run) used by shortcuts, menus and the shortcuts dialog. Shared shells β Modal, Tabs, ListRow,
PanelHeader β live in packages/ui.
A single Zustand store holds the RiveDoc plus editor state (selection, tool, mode, playheadβ¦). Mutations go through
commit(recipe) (Immer): each commit is an undo step, and drags group into one. Preferences, the session, context
menus and toasts are small Zustand stores of their own.
The stage re-exports the document to .riv bytes on change and loads them into the official runtime
(@rive-app/canvas-advanced), so the canvas is exactly what users ship. Selection overlays are SVG computed by
scene.ts.
apps/web/src/lib/runtime.ts loads that runtime from public/rive, which scripts/copy-wasm.ts fills on install.
A runtime built from the vendor/rive-wasm submodule can take its place β see Rive SDK submodules.
packages/rive/src/bundle.ts turns a file plus that runtime into a standalone
preview bundle: a zip with an index.html and an index.js player that reproduces the preview
screen anywhere. apps/web/src/app/api/projects/[id]/bundle/route.ts serves it, and openrive export --bundle
writes it from the CLI.
project-store.ts wraps the database for Bun processes; commands.ts holds the scriptable commands; tui/app.tsx is
the OpenTUI interface; mcp-server.ts exposes 42 MCP tools. All of them go through packages/rive's API.
packages/shared/src/permissions.ts is the single browser-safe policy for account roles, workspace roles, and project
capabilities. The dashboard uses it to present the right actions; packages/auth and every web API use the same
decisions at the server boundary. Workspace membership governs workspace projects independently of account roles,
while account roles govern personal projects and global administration. Database transactions re-check sensitive
workspace membership changes and ownership transfers. HTTP MCP resolves the signed-in account and active workspace on
each request, including requests on an existing stateful MCP connection.
apps/web/src/proxy.ts (Next 16's replacement for middleware) enforces OPENRIVE_ACCESS_TOKEN with HTTP Basic auth
for self-hosted deployments; it is an additional access gate and does not replace role checks.
| Command | Checks |
|---|---|
bun run test |
Round-trip of generated files and every template, the SVG importer, preview bundles, and data binding |
bun run test:corpus -- <rive-runtime>/tests |
Round-trip of Rive's 437 test files |
bun run test:mcp |
MCP server end to end |
bun run check-types, bun run lint
|
Types and lint across the workspace |
This page is generated from docs/architecture.md. Suggest an edit.