Skip to content

Architecture

Orimora is a full-stack monolith powered by SvelteKit. There are no microservices: SvelteKit server routes are the API, the same Node.js process serves the frontend, handles WebSocket connections for collaborative editing, and processes background jobs.

┌────────────────────────────────────────────────────┐
│ Browser Client │
│ SvelteKit SSR + Svelte 5 Runes │
│ TipTap Editor / Yjs / Hocuspocus │
└─────────────────────────┬──────────────────────────┘
│ HTTP / WebSocket
┌─────────────────────────▼──────────────────────────┐
│ Node.js Application │
│ ┌──────────┐ ┌────────────┐ ┌────────────────┐ │
│ │SvelteKit │ │ Hocuspocus │ │ BullMQ Worker │ │
│ │ Routes │ │ WS Server │ │ (background) │ │
│ └────┬─────┘ └─────┬──────┘ └───────┬────────┘ │
└───────┼──────────────┼─────────────────┼────────────┘
│ │ │
┌────▼──────────────▼─────────────────▼────┐
│ PostgreSQL 16+ │
│ Drizzle ORM + pg_trgm extension │
└───────────────────────────────────────────┘
┌────▼──────────────────────┐
│ Redis │
│ Sessions · Cache · BullMQ│
└───────────────────────────┘
src/
├── lib/
│ ├── components/ # Svelte UI components
│ │ └── editor/ # TipTap editor + extensions
│ ├── server/ # Server-only code
│ │ ├── db/ # Drizzle schema + migrations
│ │ ├── services/ # Business logic
│ │ └── policies/ # Authorization checks
│ └── utils/ # Shared utilities
├── routes/
│ ├── (app)/ # Authenticated app routes
│ ├── (auth)/ # Login / registration
│ └── api/ # API endpoints (RPC-style + /v1/ REST)
└── hooks.server.ts # Auth, rate-limit, security headers
  1. Request arrives at hooks.server.ts — rate-limit, auth session loaded
  2. Route handler calls a service function (e.g. getDocument, createDocument)
  3. Service queries Drizzle ORM → PostgreSQL
  4. Response serialised as JSON (API routes) or rendered to HTML (page routes)
  5. Background effects (webhooks, notifications, backlinks) dispatched via BullMQ

Real-time collaboration uses Yjs (CRDT) with Hocuspocus as the server:

  • Each document has a unique room identified by its UUID
  • The Yjs document is persisted to PostgreSQL via @hocuspocus/extension-database
  • On connect, the server loads the current document content and hydrates the Yjs state
  • On disconnect, the final Yjs state is flushed back to the documents table
  • Sessions: HTTP-only, Secure, SameSite=Lax cookies with random tokens
  • CSRF: All mutating API requests from the browser require session auth
  • API keys: kb_ prefix, encrypted at rest, scope-limited
  • CSP: Nonce-based policy managed by SvelteKit
  • CORS: Only allowed on /api/v1/* with Authorization header support