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.rs
builds the Axum router, wires middleware (CORS, tracing, static assets), and exposes OpenAPI docs viautoipa
. - Persistence: PostgreSQL is the primary store.
sqlx
repositories live underrefmd/api/src/infrastructure/db/repositories
. Migrations are tracked inrefmd/api/migrations
. - Markdown: The
/api/markdown/render
endpoint usescomrak
+ammonia
for rendering and sanitisation, thensyntect
for syntax highlighting. - Plugins:
ExecutePluginAction
orchestrates 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 thegit2
crate with a vendored libgit2 build so users can init, sync, and inspect diffs from the UI. - Sharing:
/api/shares
issues scoped tokens for documents or folders, supports optional expirations, and enforces permissions (view
,edit
,admin
) viaaccess::require_view
/resolve_document
checks. - Publishing:
/api/public
toggles document publication status, exposes/public/:owner/:id
for 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/:id
falls back to authenticated downloads when you need the original metadata. - Security: JWT-based auth with
argon2
password hashing. Share tokens carry their own permission level and gate access accordingly.
Frontend Anatomy¶
- Composition: The app is built with Vite.
src/routes
defines TanStack routes (e.g./document/$id
,/plugins
), andsrc/features
encapsulates domain-specific UI. - Editor:
refmd/app/src/features/edit-document
contains the editor.Editor.tsx
mounts Monaco lazily,EditorLayout.tsx
handles responsive splits, andToolbar.tsx
wires formatting actions. - Preview pipeline:
ui/Markdown.tsx
callsMarkdownService.renderMarkdown
, then runsupgradeAll
fromentities/document/wc
to hydrate attachments, wiki links, and other custom elements. Scroll synchronisation is handled viauseScrollSync
, while uploads and Vim mode live inuseEditorUploads
and dynamicmonaco-vim
imports. - 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.ts
constructs 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/:id
and attaches JWT or share credentials. - The WebSocket handler resolves access via
access::resolve_document
(using theAccessRepository
and 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.yml
launches the API, frontend, PostgreSQL, and supporting services. Usedocker compose -f docker-compose.dev.yml up --build
for local development builds.- The API exposes
/health
for readiness probes and serves Swagger UI at/api/docs
(backed by/api/openapi.json
). - Documentation is now published with Material for MkDocs; see
README.md
in this directory for build instructions.
Next, explore the Plugin Platform for details on extending the system.