System Architecture¶
RefMD is split into a Rust backend, a React frontend, and a realtime bridge powered by Yjs. Plugins extend both sides through Extism on the server and ESM modules in the browser. The sections below capture how these layers interact.
High-level Components¶
- API (
refmd/api) — Rust 2024 crate built with Axum, SQLx, and Tokio. It owns authentication, document CRUD, file storage, Git sync, Markdown rendering, and plugin execution. - App (
refmd/app) — React 19 + Vite single-page app. Routing is handled by TanStack Router, state by TanStack Query, and the editor by Monaco. Tailwind CSS provides styling primitives. - Realtime hub — Implemented in
refmd/api/src/infrastructure/realtime. Each document gets a Yjs room bridged over WebSockets; presence and text updates broadcast through this hub. - Plugins — Server-side Extism WASM modules plus frontend ESM bundles. Files live under
plugins/after installation and load dynamically at runtime.
Browser (React + Monaco) ──> Axum API (Rust)
▲ │
│ ▼
Plugins ◄──── WebSocket hub ─┘
Backend Anatomy¶
- Routing:
main.rsbuilds the Axum router, wires middleware (CORS, tracing, static assets), and exposes OpenAPI docs viautoipa. - Persistence: PostgreSQL is the primary store.
sqlxrepositories live underrefmd/api/src/infrastructure/db/repositories. Migrations are tracked inrefmd/api/migrations. - Markdown: The
/api/markdown/renderendpoint usescomrak+ammoniafor rendering and sanitisation, thensyntectfor syntax highlighting. - Plugins:
ExecutePluginActionorchestrates Extism calls and applies server-side effects (createDocument,putKv,createRecord,updateRecord,deleteRecord,navigate,log). Plugin assets are managed byFilesystemPluginStore, while installations are tracked per user via the plugin installation repository. - Git sync:
refmd/api/src/application/use_cases/git/*wraps thegit2crate with a vendored libgit2 build so users can init, sync, and inspect diffs from the UI. - Sharing:
/api/sharesissues scoped tokens for documents or folders, supports optional expirations, and enforces permissions (view,edit,admin) viaaccess::require_view/resolve_documentchecks. - Publishing:
/api/publictoggles document publication status, exposes/public/:owner/:idfor read-only pages, and feeds the public profile listings consumed by the frontend. - Files: Uploads live on disk (configurable path).
GET /api/uploads/*streams files throughserve_upload, which re-validates JWT/share tokens before reading from storage;/api/files/:idfalls back to authenticated downloads when you need the original metadata. - Security: JWT-based auth with
argon2password hashing. Share tokens carry their own permission level and gate access accordingly.
Frontend Anatomy¶
- Composition: The app is built with Vite.
src/routesdefines TanStack routes (e.g./document/$id,/plugins), andsrc/featuresencapsulates domain-specific UI. - Editor:
refmd/app/src/features/edit-documentcontains the editor.Editor.tsxmounts Monaco lazily,EditorLayout.tsxhandles responsive splits, andToolbar.tsxwires formatting actions. - Preview pipeline:
ui/Markdown.tsxcallsMarkdownService.renderMarkdown, then runsupgradeAllfromentities/document/wcto hydrate attachments, wiki links, and other custom elements. Scroll synchronisation is handled viauseScrollSync, while uploads and Vim mode live inuseEditorUploadsand dynamicmonaco-vimimports. - API client: OpenAPI definitions are generated from the backend via
npm run gen:api. The resulting typed fetchers live insrc/shared/api/client. - State: TanStack Query caches API responses (documents, shares, plugins) while lightweight context providers manage editor-specific state.
- Plugins:
features/plugins/lib/runtime.tsconstructs the host object passed to plugin modules. It resolves frontend bundles from/api/plugin-assets, applies client-side effects (navigate,showToast), and exposes helpers for Markdown rendering, records, KV, and uploads.
Realtime Flow¶
- The browser opens a WebSocket to
/api/yjs/:idand attaches JWT or share credentials. - The WebSocket handler resolves access via
access::resolve_document(using theAccessRepositoryand share rules) before handing the socket to the realtime hub. - Document content travels as Yjs updates; file uploads and metadata changes continue through REST endpoints.
- Awareness updates carry cursor colour and label data, allowing Monaco to render collaborator cursors.
Deployment Notes¶
docker-compose.ymllaunches the API, frontend, PostgreSQL, and supporting services. Usedocker compose -f docker-compose.dev.yml up --buildfor local development builds.- The API exposes
/healthfor readiness probes and serves Swagger UI at/api/docs(backed by/api/openapi.json). - Documentation is now published with Material for MkDocs; see
README.mdin this directory for build instructions.
Next, explore the Plugin Platform for details on extending the system.