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)

Workspace layout

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).

Layers

Format core: packages/rive

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.

Validation: packages/shared

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.

Data: packages/db

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.

Editions and object storage

@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 UI: apps/web/src/components/editor

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.

State: apps/web/src/lib/store/editor.ts

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.

Rendering

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.

Tools: apps/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.

Access control

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.

Tests

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.

⚠️ **GitHub.com Fallback** ⚠️