From 3e0d1bfa4444248d336226fec4862fcc6df466c6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 21:47:57 +0000 Subject: [PATCH 1/8] Initial plan From 154492e454392cd642919ff89c067dbe0debc7aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 21:59:29 +0000 Subject: [PATCH 2/8] Add comprehensive technical and architectural documentation Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com> --- docs/ARCHITECTURE.md | 1016 +++++++++++++++++++++++++++++++ docs/DATABASE.md | 736 ++++++++++++++++++++++ docs/README.md | 22 +- docs/SCRIPTING.md | 734 ++++++++++++++++++++++ docs/SECURITY_ARCHITECTURE.md | 834 +++++++++++++++++++++++++ docs/SYNCHRONIZATION.md | 583 ++++++++++++++++++ docs/TECHNICAL_DOCUMENTATION.md | 423 +++++++++++++ 7 files changed, 4347 insertions(+), 1 deletion(-) create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/DATABASE.md create mode 100644 docs/SCRIPTING.md create mode 100644 docs/SECURITY_ARCHITECTURE.md create mode 100644 docs/SYNCHRONIZATION.md create mode 100644 docs/TECHNICAL_DOCUMENTATION.md diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 000000000..44d4f99fd --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,1016 @@ +# Trilium Notes - Technical Architecture Documentation + +> **Version:** 0.99.3 +> **Last Updated:** November 2025 +> **Maintainer:** TriliumNext Team + +## Table of Contents + +1. [Introduction](#introduction) +2. [High-Level Architecture](#high-level-architecture) +3. [Monorepo Structure](#monorepo-structure) +4. [Core Architecture Patterns](#core-architecture-patterns) +5. [Data Layer](#data-layer) +6. [Caching System](#caching-system) +7. [Frontend Architecture](#frontend-architecture) +8. [Backend Architecture](#backend-architecture) +9. [API Architecture](#api-architecture) +10. [Build System](#build-system) +11. [Testing Strategy](#testing-strategy) +12. [Security Architecture](#security-architecture) +13. [Related Documentation](#related-documentation) + +--- + +## Introduction + +Trilium Notes is a hierarchical note-taking application built as a TypeScript monorepo. It supports multiple deployment modes (desktop, server, mobile web) and features advanced capabilities including synchronization, scripting, encryption, and rich content editing. + +### Key Characteristics + +- **Monorepo Architecture**: Uses pnpm workspaces for dependency management +- **Multi-Platform**: Desktop (Electron), Server (Node.js/Express), and Mobile Web +- **TypeScript-First**: Strong typing throughout the codebase +- **Plugin-Based**: Extensible architecture for note types and UI components +- **Offline-First**: Full functionality without network connectivity +- **Synchronization-Ready**: Built-in sync protocol for multi-device usage + +### Technology Stack + +- **Runtime**: Node.js (backend), Browser/Electron (frontend) +- **Language**: TypeScript, JavaScript +- **Database**: SQLite (better-sqlite3) +- **Build Tools**: Vite, ESBuild, pnpm +- **UI Framework**: Custom widget-based system +- **Rich Text**: CKEditor 5 (customized) +- **Code Editing**: CodeMirror 6 +- **Desktop**: Electron +- **Server**: Express.js + +--- + +## High-Level Architecture + +Trilium follows a **client-server architecture** even in desktop mode, where Electron runs both the backend server and frontend client within the same process. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Frontend │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │ Widgets │ │ Froca │ │ UI │ │ +│ │ System │ │ Cache │ │ Services │ │ +│ └────────────┘ └────────────┘ └────────────┘ │ +│ │ │ +│ WebSocket / REST API │ +│ │ │ +└─────────────────────────┼────────────────────────────────────┘ + │ +┌─────────────────────────┼────────────────────────────────────┐ +│ Backend Server │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │ Express │ │ Becca │ │ Script │ │ +│ │ Routes │ │ Cache │ │ Engine │ │ +│ └────────────┘ └────────────┘ └────────────┘ │ +│ │ │ +│ ┌────┴─────┐ │ +│ │ SQLite │ │ +│ │ Database │ │ +│ └──────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Deployment Modes + +1. **Desktop Application** + - Electron wrapper running both frontend and backend + - Local SQLite database + - Full offline functionality + - Cross-platform (Windows, macOS, Linux) + +2. **Server Installation** + - Node.js server exposing web interface + - Multi-user capable + - Can sync with desktop clients + - Docker deployment supported + +3. **Mobile Web** + - Optimized responsive interface + - Accessed via browser + - Requires server installation + +--- + +## Monorepo Structure + +Trilium uses **pnpm workspaces** to manage its monorepo structure, with apps and packages clearly separated. + +``` +trilium/ +├── apps/ # Runnable applications +│ ├── client/ # Frontend application (shared by server & desktop) +│ ├── server/ # Node.js server with web interface +│ ├── desktop/ # Electron desktop application +│ ├── web-clipper/ # Browser extension for web content capture +│ ├── db-compare/ # Database comparison tool +│ ├── dump-db/ # Database export tool +│ ├── edit-docs/ # Documentation editing tool +│ ├── build-docs/ # Documentation build tool +│ └── website/ # Marketing website +│ +├── packages/ # Shared libraries +│ ├── commons/ # Shared interfaces and utilities +│ ├── ckeditor5/ # Custom rich text editor +│ ├── codemirror/ # Code editor customizations +│ ├── highlightjs/ # Syntax highlighting +│ ├── ckeditor5-admonition/ # CKEditor plugin: admonitions +│ ├── ckeditor5-footnotes/ # CKEditor plugin: footnotes +│ ├── ckeditor5-keyboard-marker/# CKEditor plugin: keyboard shortcuts +│ ├── ckeditor5-math/ # CKEditor plugin: math equations +│ ├── ckeditor5-mermaid/ # CKEditor plugin: diagrams +│ ├── express-partial-content/ # HTTP partial content middleware +│ ├── share-theme/ # Shared note theme +│ ├── splitjs/ # Split pane library +│ └── turndown-plugin-gfm/ # Markdown conversion +│ +├── docs/ # Documentation +├── scripts/ # Build and utility scripts +└── patches/ # Package patches (via pnpm) +``` + +### Package Dependencies + +The monorepo uses workspace protocol (`workspace:*`) for internal dependencies: + +``` +desktop → client → commons +server → client → commons +client → ckeditor5, codemirror, highlightjs +ckeditor5 → ckeditor5-* plugins +``` + +--- + +## Core Architecture Patterns + +### Three-Layer Cache System + +Trilium implements a sophisticated **three-tier caching system** to optimize performance and enable offline functionality: + +#### 1. Becca (Backend Cache) + +Located at: `apps/server/src/becca/` + +```typescript +// Becca caches all entities in memory +class Becca { + notes: Record + branches: Record + attributes: Record + attachments: Record + // ... other entity collections +} +``` + +**Responsibilities:** +- Server-side entity cache +- Maintains complete note tree in memory +- Handles entity relationships and integrity +- Provides fast lookups without database queries +- Manages entity lifecycle (create, update, delete) + +**Key Files:** +- `becca.ts` - Main cache instance +- `becca_loader.ts` - Loads entities from database +- `becca_service.ts` - Cache management operations +- `entities/` - Entity classes (BNote, BBranch, etc.) + +#### 2. Froca (Frontend Cache) + +Located at: `apps/client/src/services/froca.ts` + +```typescript +// Froca is a read-only mirror of backend data +class Froca { + notes: Record + branches: Record + attributes: Record + // ... other entity collections +} +``` + +**Responsibilities:** +- Frontend read-only cache +- Lazy loading of note tree +- Minimizes API calls +- Enables fast UI rendering +- Synchronizes with backend via WebSocket + +**Loading Strategy:** +- Initial load: root notes and immediate children +- Lazy load: notes loaded when accessed +- When note is loaded, all parent and child branches load +- Deleted entities tracked via missing branches + +#### 3. Shaca (Share Cache) + +Located at: `apps/server/src/share/` + +**Responsibilities:** +- Optimized cache for shared/published notes +- Handles public note access without authentication +- Performance-optimized for high-traffic scenarios +- Separate from main Becca to isolate concerns + +### Entity System + +Trilium's data model is based on five core entities: + +``` +┌──────────────────────────────────────────────────────────┐ +│ Note Tree │ +│ │ +│ ┌─────────┐ │ +│ │ Note │ │ +│ │ (BNote) │ │ +│ └────┬────┘ │ +│ │ │ +│ │ linked by │ +│ ▼ │ +│ ┌──────────┐ ┌─────────────┐ │ +│ │ Branch │◄────────│ Attribute │ │ +│ │(BBranch) │ │ (BAttribute)│ │ +│ └──────────┘ └─────────────┘ │ +│ │ │ +│ │ creates │ +│ ▼ │ +│ ┌──────────┐ ┌─────────────┐ │ +│ │ Revision │ │ Attachment │ │ +│ │(BRevision│ │(BAttachment)│ │ +│ └──────────┘ └─────────────┘ │ +│ │ +└──────────────────────────────────────────────────────────┘ +``` + +#### Entity Definitions + +**1. BNote** (`apps/server/src/becca/entities/bnote.ts`) +- Represents a note with title, content, and metadata +- Type can be: text, code, file, image, canvas, mermaid, etc. +- Contains content via blob reference +- Can be protected (encrypted) +- Has creation and modification timestamps + +**2. BBranch** (`apps/server/src/becca/entities/bbranch.ts`) +- Represents parent-child relationship between notes +- Enables note cloning (multiple parents) +- Contains positioning information +- Has optional prefix for customization +- Tracks expansion state in tree + +**3. BAttribute** (`apps/server/src/becca/entities/battribute.ts`) +- Key-value metadata attached to notes +- Two types: labels (tags) and relations (links) +- Can be inheritable to child notes +- Used for search, organization, and scripting +- Supports promoted attributes (displayed prominently) + +**4. BRevision** (`apps/server/src/becca/entities/brevision.ts`) +- Stores historical versions of note content +- Automatic versioning on edits +- Retains title, type, and content +- Enables note history browsing and restoration + +**5. BAttachment** (`apps/server/src/becca/entities/battachment.ts`) +- File attachments linked to notes +- Has owner (note), role, and mime type +- Content stored in blobs +- Can be protected (encrypted) + +**6. BBlob** (`apps/server/src/becca/entities/bblob.ts`) +- Binary large object storage +- Stores actual note content and attachments +- Referenced by notes, revisions, and attachments +- Supports encryption for protected content + +### Widget-Based UI + +The frontend uses a **widget system** for modular, reusable UI components. + +Located at: `apps/client/src/widgets/` + +```typescript +// Widget Hierarchy +BasicWidget +├── NoteContextAwareWidget (responds to note changes) +│ ├── RightPanelWidget (displayed in right sidebar) +│ └── Type-specific widgets +├── Container widgets (tabs, ribbons, etc.) +└── Specialized widgets (search, calendar, etc.) +``` + +**Base Classes:** + +1. **BasicWidget** (`basic_widget.ts`) + - Base class for all UI components + - Lifecycle: construction → rendering → events → destruction + - Handles DOM manipulation + - Event subscription management + - Child widget management + +2. **NoteContextAwareWidget** (`note_context_aware_widget.ts`) + - Extends BasicWidget + - Automatically updates when active note changes + - Accesses current note context + - Used for note-dependent UI + +3. **RightPanelWidget** + - Widgets displayed in right sidebar + - Collapsible sections + - Context-specific tools and information + +**Type-Specific Widgets:** + +Located at: `apps/client/src/widgets/type_widgets/` + +Each note type has a dedicated widget: +- `text_type_widget.ts` - CKEditor integration +- `code_type_widget.ts` - CodeMirror integration +- `file_type_widget.ts` - File preview and download +- `image_type_widget.ts` - Image display and editing +- `canvas_type_widget.ts` - Excalidraw integration +- `mermaid_type_widget.ts` - Diagram rendering +- And more... + +--- + +## Data Layer + +### Database Schema + +Trilium uses **SQLite** as its database engine, managed via `better-sqlite3`. + +Schema location: `apps/server/src/assets/db/schema.sql` + +**Core Tables:** + +```sql +-- Notes: Core content storage +notes ( + noteId, title, isProtected, type, mime, + blobId, isDeleted, dateCreated, dateModified +) + +-- Branches: Tree relationships +branches ( + branchId, noteId, parentNoteId, notePosition, + prefix, isExpanded, isDeleted +) + +-- Attributes: Metadata +attributes ( + attributeId, noteId, type, name, value, + position, isInheritable, isDeleted +) + +-- Revisions: Version history +revisions ( + revisionId, noteId, type, mime, title, + blobId, utcDateLastEdited +) + +-- Attachments: File attachments +attachments ( + attachmentId, ownerId, role, mime, title, + blobId, isProtected, isDeleted +) + +-- Blobs: Binary content +blobs ( + blobId, content, dateModified +) + +-- Options: Application settings +options ( + name, value, isSynced +) + +-- Entity Changes: Sync tracking +entity_changes ( + entityName, entityId, hash, changeId, + isSynced, utcDateChanged +) +``` + +### Data Access Patterns + +**Direct SQL:** +```typescript +// apps/server/src/services/sql.ts +sql.getRows("SELECT * FROM notes WHERE type = ?", ['text']) +sql.execute("UPDATE notes SET title = ? WHERE noteId = ?", [title, noteId]) +``` + +**Through Becca:** +```typescript +// Recommended approach - uses cache +const note = becca.getNote('noteId') +note.title = 'New Title' +note.save() +``` + +**Through Froca (Frontend):** +```typescript +// Read-only access +const note = froca.getNote('noteId') +console.log(note.title) +``` + +### Database Migrations + +Migration system: `apps/server/src/migrations/` + +- Sequential numbered files (e.g., `XXXX_migration_name.sql`) +- Automatic execution on version upgrade +- Schema version tracked in options table +- Both SQL and JavaScript migrations supported + +--- + +## Caching System + +### Cache Initialization + +**Backend (Becca):** +```typescript +// On server startup +await becca_loader.load() // Loads all entities into memory +becca.loaded = true +``` + +**Frontend (Froca):** +```typescript +// On app initialization +await froca.loadInitialTree() // Loads root and visible notes +// Lazy load on demand +const note = await froca.getNote(noteId) // Triggers load if not cached +``` + +### Cache Invalidation + +**Server-Side:** +- Entities automatically update cache on save +- WebSocket broadcasts changes to all clients +- Synchronization updates trigger cache refresh + +**Client-Side:** +- WebSocket listeners update Froca +- Manual reload via `froca.loadSubTree(noteId)` +- Full reload on protected session changes + +### Cache Consistency + +**Entity Change Tracking:** +```typescript +// Every entity modification tracked +entity_changes ( + entityName: 'notes', + entityId: 'note123', + hash: 'abc...', + changeId: 'change456', + utcDateChanged: '2025-11-02...' +) +``` + +**Sync Protocol:** +1. Client requests changes since last sync +2. Server returns entity_changes records +3. Client applies changes to Froca +4. Client sends local changes to server +5. Server updates Becca and database + +--- + +## Frontend Architecture + +### Application Entry Point + +**Desktop:** `apps/client/src/desktop.ts` +**Web:** `apps/client/src/index.ts` + +### Service Layer + +Located at: `apps/client/src/services/` + +Key services: +- `froca.ts` - Frontend cache +- `server.ts` - API communication +- `ws.ts` - WebSocket connection +- `tree_service.ts` - Note tree management +- `note_context.ts` - Active note tracking +- `protected_session.ts` - Encryption key management +- `link.ts` - Note linking and navigation +- `export.ts` - Note export functionality + +### UI Components + +**Main Layout:** +``` +┌──────────────────────────────────────────────────────┐ +│ Title Bar │ +├──────────┬────────────────────────┬──────────────────┤ +│ │ │ │ +│ Note │ Note Detail │ Right Panel │ +│ Tree │ Editor │ (Info, Links) │ +│ │ │ │ +│ │ │ │ +├──────────┴────────────────────────┴──────────────────┤ +│ Status Bar │ +└──────────────────────────────────────────────────────┘ +``` + +**Component Locations:** +- `widgets/containers/` - Layout containers +- `widgets/buttons/` - Toolbar buttons +- `widgets/dialogs/` - Modal dialogs +- `widgets/ribbon_widgets/` - Tab widgets +- `widgets/type_widgets/` - Note type editors + +### Event System + +**Application Events:** +```typescript +// Subscribe to events +appContext.addBeforeUnloadListener(() => { + // Cleanup before page unload +}) + +// Trigger events +appContext.trigger('noteTreeLoaded') +``` + +**Note Context Events:** +```typescript +// NoteContextAwareWidget automatically receives: +- noteSwitched() +- noteChanged() +- refresh() +``` + +### State Management + +Trilium uses **custom state management** rather than Redux/MobX: + +- `note_context.ts` - Active note and context +- `froca.ts` - Entity cache +- Component local state +- URL parameters for shareable state + +--- + +## Backend Architecture + +### Application Entry Point + +Location: `apps/server/src/main.ts` + +**Startup Sequence:** +1. Load configuration +2. Initialize database +3. Run migrations +4. Load Becca cache +5. Start Express server +6. Initialize WebSocket +7. Start scheduled tasks + +### Service Layer + +Located at: `apps/server/src/services/` + +**Core Services:** + +- **Notes Management** + - `notes.ts` - CRUD operations + - `note_contents.ts` - Content handling + - `note_types.ts` - Type-specific logic + - `cloning.ts` - Note cloning/multi-parent + +- **Tree Operations** + - `tree.ts` - Tree structure management + - `branches.ts` - Branch operations + - `consistency_checks.ts` - Tree integrity + +- **Search** + - `search/search.ts` - Main search engine + - `search/expressions/` - Search expression parsing + - `search/services/` - Search utilities + +- **Sync** + - `sync.ts` - Synchronization protocol + - `sync_update.ts` - Update handling + - `sync_mutex.ts` - Concurrency control + +- **Scripting** + - `backend_script_api.ts` - Backend script API + - `script_context.ts` - Script execution context + +- **Import/Export** + - `import/` - Various import formats + - `export/` - Export to different formats + - `zip.ts` - Archive handling + +- **Security** + - `encryption.ts` - Note encryption + - `protected_session.ts` - Session management + - `password.ts` - Password handling + +### Route Structure + +Located at: `apps/server/src/routes/` + +``` +routes/ +├── index.ts # Route registration +├── api/ # REST API endpoints +│ ├── notes.ts +│ ├── branches.ts +│ ├── attributes.ts +│ ├── search.ts +│ ├── login.ts +│ └── ... +└── custom/ # Special endpoints + ├── setup.ts + ├── share.ts + └── ... +``` + +**API Endpoint Pattern:** +```typescript +router.get('/api/notes/:noteId', (req, res) => { + const noteId = req.params.noteId + const note = becca.getNote(noteId) + res.json(note.getPojoWithContent()) +}) +``` + +### Middleware + +Key middleware components: +- `auth.ts` - Authentication +- `csrf.ts` - CSRF protection +- `request_context.ts` - Request-scoped data +- `error_handling.ts` - Error responses + +--- + +## API Architecture + +### Internal API + +**REST Endpoints** (`/api/*`) + +Used by the frontend for all operations: + +**Note Operations:** +- `GET /api/notes/:noteId` - Get note +- `POST /api/notes/:noteId/content` - Update content +- `PUT /api/notes/:noteId` - Update metadata +- `DELETE /api/notes/:noteId` - Delete note + +**Tree Operations:** +- `GET /api/tree` - Get note tree +- `POST /api/branches` - Create branch +- `PUT /api/branches/:branchId` - Update branch +- `DELETE /api/branches/:branchId` - Delete branch + +**Search:** +- `GET /api/search?query=...` - Search notes +- `GET /api/search-note/:noteId` - Execute search note + +### ETAPI (External API) + +Located at: `apps/server/src/etapi/` + +**Purpose:** Third-party integrations and automation + +**Authentication:** Token-based (ETAPI tokens) + +**OpenAPI Spec:** Auto-generated + +**Key Endpoints:** +- `/etapi/notes` - Note CRUD +- `/etapi/branches` - Branch management +- `/etapi/attributes` - Attribute operations +- `/etapi/attachments` - Attachment handling + +**Example:** +```bash +curl -H "Authorization: YOUR_TOKEN" \ + https://trilium.example.com/etapi/notes/noteId +``` + +### WebSocket API + +Located at: `apps/server/src/services/ws.ts` + +**Purpose:** Real-time updates and synchronization + +**Protocol:** WebSocket (Socket.IO-like custom protocol) + +**Message Types:** +- `sync` - Synchronization request +- `entity-change` - Entity update notification +- `refresh-tree` - Tree structure changed +- `open-note` - Open note in UI + +**Client Subscribe:** +```typescript +ws.subscribe('entity-change', (data) => { + froca.processEntityChange(data) +}) +``` + +--- + +## Build System + +### Package Manager: pnpm + +**Why pnpm:** +- Fast, disk-efficient +- Strict dependency isolation +- Native monorepo support via workspaces +- Patch package support + +**Workspace Configuration:** +```yaml +# pnpm-workspace.yaml +packages: + - 'apps/*' + - 'packages/*' +``` + +### Build Tools + +**Vite** (Development & Production) +- Fast HMR for development +- Optimized production builds +- Asset handling +- Plugin ecosystem + +**ESBuild** (TypeScript compilation) +- Fast TypeScript transpilation +- Bundling support +- Minification + +**TypeScript** +- Project references for monorepo +- Strict type checking +- Shared `tsconfig.base.json` + +### Build Scripts + +**Root `package.json` scripts:** +```json +{ + "server:start": "pnpm run --filter server dev", + "server:build": "pnpm run --filter server build", + "client:build": "pnpm run --filter client build", + "desktop:build": "pnpm run --filter desktop build", + "test:all": "pnpm test:parallel && pnpm test:sequential" +} +``` + +### Build Process + +**Development:** +```bash +pnpm install # Install dependencies +pnpm server:start # Start dev server (port 8080) +# or +pnpm desktop:start # Start Electron dev +``` + +**Production (Server):** +```bash +pnpm server:build # Build server + client +node apps/server/dist/main.js +``` + +**Production (Desktop):** +```bash +pnpm desktop:build # Build Electron app +# Creates distributable in apps/desktop/out/make/ +``` + +**Docker:** +```bash +docker build -t trilium . +docker run -p 8080:8080 trilium +``` + +### Asset Pipeline + +**Client Assets:** +- Entry: `apps/client/src/index.html` +- Bundled by Vite +- Output: `apps/client/dist/` + +**Server Static:** +- Serves client assets in production +- Public directory: `apps/server/public/` + +**Desktop:** +- Packages client assets +- Electron main process: `apps/desktop/src/main.ts` +- Electron renderer: loads client app + +--- + +## Testing Strategy + +### Test Organization + +**Parallel Tests** (can run simultaneously): +- Client tests +- Package tests +- E2E tests (isolated databases) + +**Sequential Tests** (shared resources): +- Server tests (shared database) +- CKEditor plugin tests + +### Test Frameworks + +- **Vitest** - Unit and integration tests +- **Playwright** - E2E tests +- **Happy-DOM** - DOM testing environment + +### Running Tests + +```bash +pnpm test:all # All tests +pnpm test:parallel # Fast parallel tests +pnpm test:sequential # Sequential tests only +pnpm coverage # With coverage reports +``` + +### Test Locations + +``` +apps/ +├── server/ +│ └── src/**/*.spec.ts # Server tests +├── client/ +│ └── src/**/*.spec.ts # Client tests +└── server-e2e/ + └── tests/**/*.spec.ts # E2E tests +``` + +### E2E Testing + +**Server E2E:** +- Tests full REST API +- Tests WebSocket functionality +- Tests sync protocol + +**Desktop E2E:** +- Playwright with Electron +- Tests full desktop app +- Screenshot comparison + +--- + +## Security Architecture + +### Encryption System + +**Per-Note Encryption:** +- Notes can be individually protected +- AES-256 encryption +- Password-derived encryption key (PBKDF2) +- Separate protected session management + +**Protected Session:** +- Time-limited access to protected notes +- Automatic timeout +- Re-authentication required +- Frontend: `protected_session.ts` +- Backend: `protected_session.ts` + +### Authentication + +**Password Auth:** +- PBKDF2 key derivation +- Salt per installation +- Hash verification + +**OpenID Connect:** +- External identity provider support +- OAuth 2.0 flow +- Configurable providers + +**TOTP (2FA):** +- Time-based one-time passwords +- QR code setup +- Backup codes + +### Authorization + +**Single-User Model:** +- Desktop: single user (owner) +- Server: single user per installation + +**Share Notes:** +- Public access without authentication +- Separate Shaca cache +- Read-only access + +### CSRF Protection + +**CSRF Tokens:** +- Required for state-changing operations +- Token in header or cookie +- Validation middleware + +### Input Sanitization + +**XSS Prevention:** +- DOMPurify for HTML sanitization +- CKEditor content filtering +- CSP headers + +**SQL Injection:** +- Parameterized queries only +- Better-sqlite3 prepared statements +- No string concatenation in SQL + +### Dependency Security + +**Vulnerability Scanning:** +- Renovate bot for updates +- npm audit integration +- Override vulnerable sub-dependencies + +--- + +## Related Documentation + +### User Documentation +- [User Guide](User%20Guide/User%20Guide/) - End-user features and usage +- [Installation Guide](User%20Guide/User%20Guide/Installation%20&%20Setup/) +- [Basic Concepts](User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/) + +### Developer Documentation +- [Developer Guide](Developer%20Guide/Developer%20Guide/) - Development setup +- [Environment Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) +- [Project Structure](Developer%20Guide/Developer%20Guide/Project%20Structure.md) +- [Adding Note Types](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Adding%20a%20new%20note%20type/) +- [Database Schema](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Database/) + +### API Documentation +- [Script API](Script%20API/) - User scripting API +- [ETAPI Documentation](https://triliumnext.github.io/Docs/Wiki/etapi) - External API + +### Additional Resources +- [CLAUDE.md](../CLAUDE.md) - AI assistant guidance +- [README.md](../README.md) - Project overview +- [SECURITY.md](../SECURITY.md) - Security policy + +--- + +## Appendices + +### Glossary + +- **Becca**: Backend Cache - server-side entity cache +- **Froca**: Frontend Cache - client-side entity mirror +- **Shaca**: Share Cache - cache for public shared notes +- **ETAPI**: External API for third-party integrations +- **Protected Note**: Encrypted note requiring password +- **Clone**: Note with multiple parent branches +- **Branch**: Parent-child relationship between notes +- **Attribute**: Metadata (label or relation) attached to note +- **Blob**: Binary large object containing note content + +### File Naming Conventions + +- `BEntity` - Backend entity (e.g., BNote, BBranch) +- `FEntity` - Frontend entity (e.g., FNote, FBranch) +- `*_widget.ts` - Widget classes +- `*_service.ts` - Service modules +- `*.spec.ts` - Test files +- `XXXX_*.sql` - Migration files + +### Architecture Decision Records + +For historical context on major architectural decisions, see: +- Migration to TypeScript monorepo +- Adoption of pnpm workspaces +- CKEditor 5 upgrade +- Entity change tracking system + +--- + +**Document Maintainer:** TriliumNext Team +**Last Review:** November 2025 +**Next Review:** When major architectural changes occur diff --git a/docs/DATABASE.md b/docs/DATABASE.md new file mode 100644 index 000000000..d02452f21 --- /dev/null +++ b/docs/DATABASE.md @@ -0,0 +1,736 @@ +# Trilium Database Architecture + +> **Related:** [ARCHITECTURE.md](ARCHITECTURE.md) | [Database Schema](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Database/) + +## Overview + +Trilium uses **SQLite** as its embedded database engine, providing a reliable, file-based storage system that requires no separate database server. The database stores all notes, their relationships, metadata, and configuration. + +## Database File + +**Location:** +- Desktop: `~/.local/share/trilium-data/document.db` (Linux/Mac) or `%APPDATA%/trilium-data/document.db` (Windows) +- Server: Configured via `TRILIUM_DATA_DIR` environment variable +- Docker: Mounted volume at `/home/node/trilium-data/` + +**Characteristics:** +- Single-file database +- Embedded (no server required) +- ACID compliant +- Cross-platform +- Supports up to 281 TB database size +- Efficient for 100k+ notes + +## Database Driver + +**Library:** `better-sqlite3` + +**Why better-sqlite3:** +- Native performance (C++ bindings) +- Synchronous API (simpler code) +- Prepared statements +- Transaction support +- Type safety + +**Usage:** +```typescript +// apps/server/src/services/sql.ts +import Database from 'better-sqlite3' + +const db = new Database('document.db') +const stmt = db.prepare('SELECT * FROM notes WHERE noteId = ?') +const note = stmt.get(noteId) +``` + +## Schema Overview + +Schema location: `apps/server/src/assets/db/schema.sql` + +**Entity Tables:** +- `notes` - Core note data +- `branches` - Tree relationships +- `attributes` - Metadata (labels/relations) +- `revisions` - Version history +- `attachments` - File attachments +- `blobs` - Binary content storage + +**System Tables:** +- `options` - Application configuration +- `entity_changes` - Change tracking for sync +- `recent_notes` - Recently accessed notes +- `etapi_tokens` - API authentication tokens +- `user_data` - User credentials +- `sessions` - Web session storage + +## Entity Tables + +### Notes Table + +```sql +CREATE TABLE notes ( + noteId TEXT NOT NULL PRIMARY KEY, + title TEXT NOT NULL DEFAULT "note", + isProtected INT NOT NULL DEFAULT 0, + type TEXT NOT NULL DEFAULT 'text', + mime TEXT NOT NULL DEFAULT 'text/html', + blobId TEXT DEFAULT NULL, + isDeleted INT NOT NULL DEFAULT 0, + deleteId TEXT DEFAULT NULL, + dateCreated TEXT NOT NULL, + dateModified TEXT NOT NULL, + utcDateCreated TEXT NOT NULL, + utcDateModified TEXT NOT NULL +); + +-- Indexes for performance +CREATE INDEX IDX_notes_title ON notes (title); +CREATE INDEX IDX_notes_type ON notes (type); +CREATE INDEX IDX_notes_dateCreated ON notes (dateCreated); +CREATE INDEX IDX_notes_dateModified ON notes (dateModified); +CREATE INDEX IDX_notes_utcDateModified ON notes (utcDateModified); +CREATE INDEX IDX_notes_blobId ON notes (blobId); +``` + +**Field Descriptions:** + +| Field | Type | Description | +|-------|------|-------------| +| `noteId` | TEXT | Unique identifier (UUID or custom) | +| `title` | TEXT | Note title (displayed in tree) | +| `isProtected` | INT | 1 if encrypted, 0 if not | +| `type` | TEXT | Note type: text, code, file, image, etc. | +| `mime` | TEXT | MIME type: text/html, application/json, etc. | +| `blobId` | TEXT | Reference to content in blobs table | +| `isDeleted` | INT | Soft delete flag | +| `deleteId` | TEXT | Unique delete operation ID | +| `dateCreated` | TEXT | Creation date (local timezone) | +| `dateModified` | TEXT | Last modified (local timezone) | +| `utcDateCreated` | TEXT | Creation date (UTC) | +| `utcDateModified` | TEXT | Last modified (UTC) | + +**Note Types:** +- `text` - Rich text with HTML +- `code` - Source code +- `file` - Binary file +- `image` - Image file +- `search` - Saved search +- `render` - Custom HTML rendering +- `relation-map` - Relationship diagram +- `canvas` - Excalidraw drawing +- `mermaid` - Mermaid diagram +- `book` - Container for documentation +- `web-view` - Embedded web page +- `mindmap` - Mind map +- `geomap` - Geographical map + +### Branches Table + +```sql +CREATE TABLE branches ( + branchId TEXT NOT NULL PRIMARY KEY, + noteId TEXT NOT NULL, + parentNoteId TEXT NOT NULL, + notePosition INTEGER NOT NULL, + prefix TEXT, + isExpanded INTEGER NOT NULL DEFAULT 0, + isDeleted INTEGER NOT NULL DEFAULT 0, + deleteId TEXT DEFAULT NULL, + utcDateModified TEXT NOT NULL +); + +-- Indexes +CREATE INDEX IDX_branches_noteId_parentNoteId ON branches (noteId, parentNoteId); +CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId); +``` + +**Field Descriptions:** + +| Field | Type | Description | +|-------|------|-------------| +| `branchId` | TEXT | Unique identifier for this branch | +| `noteId` | TEXT | Child note ID | +| `parentNoteId` | TEXT | Parent note ID | +| `notePosition` | INT | Sort order among siblings | +| `prefix` | TEXT | Optional prefix text (e.g., "Chapter 1:") | +| `isExpanded` | INT | Tree expansion state | +| `isDeleted` | INT | Soft delete flag | +| `deleteId` | TEXT | Delete operation ID | +| `utcDateModified` | TEXT | Last modified (UTC) | + +**Key Concepts:** +- **Cloning:** A note can have multiple branches (multiple parents) +- **Position:** Siblings ordered by `notePosition` +- **Prefix:** Display text before note title in tree +- **Soft Delete:** Allows sync before permanent deletion + +### Attributes Table + +```sql +CREATE TABLE attributes ( + attributeId TEXT NOT NULL PRIMARY KEY, + noteId TEXT NOT NULL, + type TEXT NOT NULL, + name TEXT NOT NULL, + value TEXT DEFAULT '' NOT NULL, + position INT DEFAULT 0 NOT NULL, + utcDateModified TEXT NOT NULL, + isDeleted INT NOT NULL, + deleteId TEXT DEFAULT NULL, + isInheritable INT DEFAULT 0 NULL +); + +-- Indexes +CREATE INDEX IDX_attributes_name_value ON attributes (name, value); +CREATE INDEX IDX_attributes_noteId ON attributes (noteId); +CREATE INDEX IDX_attributes_value ON attributes (value); +``` + +**Field Descriptions:** + +| Field | Type | Description | +|-------|------|-------------| +| `attributeId` | TEXT | Unique identifier | +| `noteId` | TEXT | Note this attribute belongs to | +| `type` | TEXT | 'label' or 'relation' | +| `name` | TEXT | Attribute name | +| `value` | TEXT | Attribute value (text for labels, noteId for relations) | +| `position` | INT | Display order | +| `utcDateModified` | TEXT | Last modified (UTC) | +| `isDeleted` | INT | Soft delete flag | +| `deleteId` | TEXT | Delete operation ID | +| `isInheritable` | INT | Inherited by child notes | + +**Attribute Types:** + +**Labels** (key-value pairs): +```sql +-- Example: #priority=high +INSERT INTO attributes (attributeId, noteId, type, name, value) +VALUES ('attr1', 'note123', 'label', 'priority', 'high') +``` + +**Relations** (links to other notes): +```sql +-- Example: ~author=[[noteId]] +INSERT INTO attributes (attributeId, noteId, type, name, value) +VALUES ('attr2', 'note123', 'relation', 'author', 'author-note-id') +``` + +**Special Attributes:** +- `#run=frontendStartup` - Execute script on frontend load +- `#run=backendStartup` - Execute script on backend load +- `#customWidget` - Custom widget implementation +- `#iconClass` - Custom tree icon +- `#cssClass` - CSS class for note +- `#sorted` - Auto-sort children +- `#hideChildrenOverview` - Don't show child list + +### Revisions Table + +```sql +CREATE TABLE revisions ( + revisionId TEXT NOT NULL PRIMARY KEY, + noteId TEXT NOT NULL, + type TEXT DEFAULT '' NOT NULL, + mime TEXT DEFAULT '' NOT NULL, + title TEXT NOT NULL, + isProtected INT NOT NULL DEFAULT 0, + blobId TEXT DEFAULT NULL, + utcDateLastEdited TEXT NOT NULL, + utcDateCreated TEXT NOT NULL, + utcDateModified TEXT NOT NULL, + dateLastEdited TEXT NOT NULL, + dateCreated TEXT NOT NULL +); + +-- Indexes +CREATE INDEX IDX_revisions_noteId ON revisions (noteId); +CREATE INDEX IDX_revisions_utcDateCreated ON revisions (utcDateCreated); +CREATE INDEX IDX_revisions_utcDateLastEdited ON revisions (utcDateLastEdited); +CREATE INDEX IDX_revisions_blobId ON revisions (blobId); +``` + +**Revision Strategy:** +- Automatic revision created on note modification +- Configurable interval (default: daily max) +- Stores complete note snapshot +- Allows reverting to previous versions +- Can be disabled with `#disableVersioning` + +### Attachments Table + +```sql +CREATE TABLE attachments ( + attachmentId TEXT NOT NULL PRIMARY KEY, + ownerId TEXT NOT NULL, + role TEXT NOT NULL, + mime TEXT NOT NULL, + title TEXT NOT NULL, + isProtected INT NOT NULL DEFAULT 0, + position INT DEFAULT 0 NOT NULL, + blobId TEXT DEFAULT NULL, + dateModified TEXT NOT NULL, + utcDateModified TEXT NOT NULL, + utcDateScheduledForErasureSince TEXT DEFAULT NULL, + isDeleted INT NOT NULL, + deleteId TEXT DEFAULT NULL +); + +-- Indexes +CREATE INDEX IDX_attachments_ownerId_role ON attachments (ownerId, role); +CREATE INDEX IDX_attachments_blobId ON attachments (blobId); +``` + +**Attachment Roles:** +- `file` - Regular file attachment +- `image` - Image file +- `cover-image` - Note cover image +- Custom roles for specific purposes + +### Blobs Table + +```sql +CREATE TABLE blobs ( + blobId TEXT NOT NULL PRIMARY KEY, + content TEXT NULL DEFAULT NULL, + dateModified TEXT NOT NULL, + utcDateModified TEXT NOT NULL +); +``` + +**Blob Usage:** +- Stores actual content (text or binary) +- Referenced by notes, revisions, attachments +- Deduplication via hash-based blobId +- TEXT type stores both text and binary (base64) + +**Content Types:** +- **Text notes:** HTML content +- **Code notes:** Plain text source code +- **Binary notes:** Base64 encoded data +- **Protected notes:** Encrypted content + +## System Tables + +### Options Table + +```sql +CREATE TABLE options ( + name TEXT NOT NULL PRIMARY KEY, + value TEXT NOT NULL, + isSynced INTEGER DEFAULT 0 NOT NULL, + utcDateModified TEXT NOT NULL +); +``` + +**Key Options:** +- `documentId` - Unique installation ID +- `dbVersion` - Schema version +- `syncVersion` - Sync protocol version +- `passwordVerificationHash` - Password verification +- `encryptedDataKey` - Encryption key (encrypted) +- `theme` - UI theme +- Various feature flags and settings + +**Synced Options:** +- `isSynced = 1` - Synced across devices +- `isSynced = 0` - Local to this installation + +### Entity Changes Table + +```sql +CREATE TABLE entity_changes ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + entityName TEXT NOT NULL, + entityId TEXT NOT NULL, + hash TEXT NOT NULL, + isErased INT NOT NULL, + changeId TEXT NOT NULL, + componentId TEXT NOT NULL, + instanceId TEXT NOT NULL, + isSynced INTEGER NOT NULL, + utcDateChanged TEXT NOT NULL +); + +-- Indexes +CREATE UNIQUE INDEX IDX_entityChanges_entityName_entityId + ON entity_changes (entityName, entityId); +CREATE INDEX IDX_entity_changes_changeId ON entity_changes (changeId); +``` + +**Purpose:** Track all entity modifications for synchronization + +**Entity Types:** +- `notes` +- `branches` +- `attributes` +- `revisions` +- `attachments` +- `options` +- `etapi_tokens` + +### Recent Notes Table + +```sql +CREATE TABLE recent_notes ( + noteId TEXT NOT NULL PRIMARY KEY, + notePath TEXT NOT NULL, + utcDateCreated TEXT NOT NULL +); +``` + +**Purpose:** Track recently accessed notes for quick access + +### Sessions Table + +```sql +CREATE TABLE sessions ( + sid TEXT PRIMARY KEY, + sess TEXT NOT NULL, + expired TEXT NOT NULL +); +``` + +**Purpose:** HTTP session storage for web interface + +### User Data Table + +```sql +CREATE TABLE user_data ( + tmpID INT PRIMARY KEY, + username TEXT, + email TEXT, + userIDEncryptedDataKey TEXT, + userIDVerificationHash TEXT, + salt TEXT, + derivedKey TEXT, + isSetup TEXT DEFAULT "false" +); +``` + +**Purpose:** Store user authentication credentials + +### ETAPI Tokens Table + +```sql +CREATE TABLE etapi_tokens ( + etapiTokenId TEXT PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + tokenHash TEXT NOT NULL, + utcDateCreated TEXT NOT NULL, + utcDateModified TEXT NOT NULL, + isDeleted INT NOT NULL DEFAULT 0 +); +``` + +**Purpose:** API token authentication for external access + +## Data Relationships + +``` + ┌──────────────┐ + │ Notes │ + └───┬──────────┘ + │ + ┌───────────┼───────────┐ + │ │ │ + ▼ ▼ ▼ + ┌────────┐ ┌──────────┐ ┌───────────┐ + │Branches│ │Attributes│ │Attachments│ + └────────┘ └──────────┘ └─────┬─────┘ + │ │ + │ │ + │ ┌──────────┐ │ + └──────▶│ Blobs │◀────────┘ + └──────────┘ + ▲ + │ + ┌────┴─────┐ + │Revisions │ + └──────────┘ +``` + +**Relationships:** +- Notes ↔ Branches (many-to-many via noteId) +- Notes → Attributes (one-to-many) +- Notes → Blobs (one-to-one) +- Notes → Revisions (one-to-many) +- Notes → Attachments (one-to-many) +- Attachments → Blobs (one-to-one) +- Revisions → Blobs (one-to-one) + +## Database Access Patterns + +### Direct SQL Access + +**Location:** `apps/server/src/services/sql.ts` + +```typescript +// Execute query (returns rows) +const notes = sql.getRows('SELECT * FROM notes WHERE type = ?', ['text']) + +// Execute query (returns single row) +const note = sql.getRow('SELECT * FROM notes WHERE noteId = ?', [noteId]) + +// Execute statement (no return) +sql.execute('UPDATE notes SET title = ? WHERE noteId = ?', [title, noteId]) + +// Insert +sql.insert('notes', { + noteId: 'new-note-id', + title: 'New Note', + type: 'text', + // ... +}) + +// Transactions +sql.transactional(() => { + sql.execute('UPDATE ...') + sql.execute('INSERT ...') +}) +``` + +### Entity-Based Access (Recommended) + +**Via Becca Cache:** + +```typescript +// Get entity from cache +const note = becca.getNote(noteId) + +// Modify and save +note.title = 'Updated Title' +note.save() // Writes to database + +// Create new +const newNote = becca.createNote({ + parentNoteId: 'root', + title: 'New Note', + type: 'text', + content: 'Hello World' +}) + +// Delete +note.markAsDeleted() +``` + +## Database Migrations + +**Location:** `apps/server/src/migrations/` + +**Migration Files:** +- Format: `XXXX_migration_name.sql` or `XXXX_migration_name.js` +- Executed in numerical order +- Version tracked in `options.dbVersion` + +**SQL Migration Example:** +```sql +-- 0280_add_new_column.sql +ALTER TABLE notes ADD COLUMN newField TEXT DEFAULT NULL; + +UPDATE options SET value = '280' WHERE name = 'dbVersion'; +``` + +**JavaScript Migration Example:** +```javascript +// 0285_complex_migration.js +module.exports = () => { + const notes = sql.getRows('SELECT * FROM notes WHERE type = ?', ['old-type']) + + for (const note of notes) { + sql.execute('UPDATE notes SET type = ? WHERE noteId = ?', + ['new-type', note.noteId]) + } +} +``` + +**Migration Process:** +1. Server checks `dbVersion` on startup +2. Compares with latest migration number +3. Executes pending migrations in order +4. Updates `dbVersion` after each +5. Restarts if migrations ran + +## Database Maintenance + +### Backup + +**Full Backup:** +```bash +# Copy database file +cp document.db document.db.backup + +# Or use Trilium's backup feature +# Settings → Backup +``` + +**Automatic Backups:** +- Daily backup (configurable) +- Stored in `backup/` directory +- Retention policy (keep last N backups) + +### Vacuum + +**Purpose:** Reclaim unused space, defragment + +```sql +VACUUM; +``` + +**When to vacuum:** +- After deleting many notes +- Database file size larger than expected +- Performance degradation + +### Integrity Check + +```sql +PRAGMA integrity_check; +``` + +**Result:** "ok" or list of errors + +### Consistency Checks + +**Built-in Consistency Checks:** + +Location: `apps/server/src/services/consistency_checks.ts` + +- Orphaned branches +- Missing parent notes +- Circular dependencies +- Invalid entity references +- Blob reference integrity + +**Run Checks:** +```typescript +// Via API +POST /api/consistency-check + +// Or from backend script +api.runConsistencyChecks() +``` + +## Performance Optimization + +### Indexes + +**Existing Indexes:** +- `notes.title` - Fast title searches +- `notes.type` - Filter by type +- `notes.dateCreated/Modified` - Time-based queries +- `branches.noteId_parentNoteId` - Tree navigation +- `attributes.name_value` - Attribute searches + +**Query Optimization:** +```sql +-- Use indexed columns in WHERE clause +SELECT * FROM notes WHERE type = 'text' -- Uses index + +-- Avoid functions on indexed columns +SELECT * FROM notes WHERE LOWER(title) = 'test' -- No index + +-- Better +SELECT * FROM notes WHERE title = 'Test' -- Uses index +``` + +### Connection Settings + +```typescript +// apps/server/src/services/sql.ts +const db = new Database('document.db', { + // Enable WAL mode for better concurrency + verbose: console.log +}) + +db.pragma('journal_mode = WAL') +db.pragma('synchronous = NORMAL') +db.pragma('cache_size = -64000') // 64MB cache +db.pragma('temp_store = MEMORY') +``` + +**WAL Mode Benefits:** +- Better concurrency (readers don't block writers) +- Faster commits +- More robust + +### Query Performance + +**Use EXPLAIN QUERY PLAN:** +```sql +EXPLAIN QUERY PLAN +SELECT * FROM notes +WHERE type = 'text' + AND dateCreated > '2025-01-01' +``` + +**Analyze slow queries:** +- Check index usage +- Avoid SELECT * +- Use prepared statements +- Batch operations in transactions + +## Database Size Management + +**Typical Sizes:** +- 1,000 notes: ~5-10 MB +- 10,000 notes: ~50-100 MB +- 100,000 notes: ~500 MB - 1 GB + +**Size Reduction Strategies:** + +1. **Delete old revisions** +2. **Remove large attachments** +3. **Vacuum database** +4. **Compact blobs** +5. **Archive old notes** + +**Blob Deduplication:** +- Blobs identified by content hash +- Identical content shares one blob +- Automatic deduplication on insert + +## Security Considerations + +### Protected Notes Encryption + +**Encryption Process:** +```typescript +// Encrypt blob content +const encryptedContent = encrypt(content, dataKey) +blob.content = encryptedContent + +// Store encrypted +sql.insert('blobs', { blobId, content: encryptedContent }) +``` + +**Encryption Details:** +- Algorithm: AES-256-CBC +- Key derivation: PBKDF2 (10,000 iterations) +- Per-note encryption +- Master key encrypted with user password + +### SQL Injection Prevention + +**Always use parameterized queries:** +```typescript +// GOOD - Safe from SQL injection +sql.execute('SELECT * FROM notes WHERE title = ?', [userInput]) + +// BAD - Vulnerable to SQL injection +sql.execute(`SELECT * FROM notes WHERE title = '${userInput}'`) +``` + +### Database File Protection + +**File Permissions:** +- Owner read/write only +- No group/other access +- Located in user-specific directory + +--- + +**See Also:** +- [ARCHITECTURE.md](ARCHITECTURE.md) - Overall architecture +- [Database Schema Files](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Database/) +- [Migration Scripts](../apps/server/src/migrations/) diff --git a/docs/README.md b/docs/README.md index be65024ee..ea2fdd378 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,17 @@ -# Trilium Notes +# Trilium Notes Documentation + +## 📚 Technical Documentation + +**NEW:** Comprehensive technical and architectural documentation is now available! + +- **[Technical Documentation Index](TECHNICAL_DOCUMENTATION.md)** - Complete index to all technical docs +- **[Architecture Overview](ARCHITECTURE.md)** - System design and core patterns +- **[Database Architecture](DATABASE.md)** - Complete database documentation +- **[Synchronization](SYNCHRONIZATION.md)** - Sync protocol and implementation +- **[Scripting System](SCRIPTING.md)** - User scripting guide and API +- **[Security Architecture](SECURITY_ARCHITECTURE.md)** - Security implementation details + +## 📖 User Documentation Please see the [main documentation](index.md) or visit one of our translated versions: @@ -9,4 +22,11 @@ Please see the [main documentation](index.md) or visit one of our translated ver - [简体中文](README-ZH_CN.md) - [繁體中文](README-ZH_TW.md) +## 🔧 Developer Documentation + +- [Developer Guide](Developer%20Guide/Developer%20Guide/) - Development environment and contribution guide +- [Script API](Script%20API/) - Complete scripting API reference + +## 🔗 Additional Resources + For the full application README, please visit our [GitHub repository](https://github.com/triliumnext/trilium). \ No newline at end of file diff --git a/docs/SCRIPTING.md b/docs/SCRIPTING.md new file mode 100644 index 000000000..49e548c50 --- /dev/null +++ b/docs/SCRIPTING.md @@ -0,0 +1,734 @@ +# Trilium Scripting System + +> **Related:** [ARCHITECTURE.md](ARCHITECTURE.md) | [Script API Documentation](Script%20API/) + +## Overview + +Trilium features a **powerful scripting system** that allows users to extend and customize the application without modifying source code. Scripts are written in JavaScript and can execute both in the **frontend (browser)** and **backend (Node.js)** contexts. + +## Script Types + +### Frontend Scripts + +**Location:** Attached to notes with `#run=frontendStartup` attribute + +**Execution Context:** Browser environment + +**Access:** +- Trilium Frontend API +- Browser APIs (DOM, localStorage, etc.) +- Froca (frontend cache) +- UI widgets +- No direct file system access + +**Lifecycle:** +- `frontendStartup` - Run once when Trilium loads +- `frontendReload` - Run on every note context change + +**Example:** +```javascript +// Attach to note with #run=frontendStartup +const api = window.api + +// Add custom button to toolbar +api.addButtonToToolbar({ + title: 'My Button', + icon: 'star', + action: () => { + api.showMessage('Hello from frontend!') + } +}) +``` + +### Backend Scripts + +**Location:** Attached to notes with `#run=backendStartup` attribute + +**Execution Context:** Node.js server environment + +**Access:** +- Trilium Backend API +- Node.js APIs (fs, http, etc.) +- Becca (backend cache) +- Database (SQL) +- External libraries (via require) + +**Lifecycle:** +- `backendStartup` - Run once when server starts +- Event handlers (custom events) + +**Example:** +```javascript +// Attach to note with #run=backendStartup +const api = require('@triliumnext/api') + +// Listen for note creation +api.dayjs // Example: access dayjs library + +api.onNoteCreated((note) => { + if (note.title.includes('TODO')) { + note.setLabel('priority', 'high') + } +}) +``` + +### Render Scripts + +**Location:** Attached to notes with `#customWidget` or similar attributes + +**Purpose:** Custom note rendering/widgets + +**Example:** +```javascript +// Custom widget for a note +class MyWidget extends api.NoteContextAwareWidget { + doRender() { + this.$widget = $('
') + .text('Custom widget content') + return this.$widget + } +} + +module.exports = MyWidget +``` + +## Script API + +### Frontend API + +**Location:** `apps/client/src/services/frontend_script_api.ts` + +**Global Access:** `window.api` + +**Key Methods:** + +```typescript +// Note Operations +api.getNote(noteId) // Get note object +api.getBranch(branchId) // Get branch object +api.getActiveNote() // Currently displayed note +api.openNote(noteId, activateNote) // Open note in UI + +// UI Operations +api.showMessage(message) // Show toast notification +api.showDialog() // Show modal dialog +api.confirm(message) // Show confirmation dialog +api.prompt(message, defaultValue) // Show input prompt + +// Tree Operations +api.getTree() // Get note tree structure +api.expandTree(noteId) // Expand tree branch +api.collapseTree(noteId) // Collapse tree branch + +// Search +api.searchForNotes(searchQuery) // Search notes +api.searchForNote(searchQuery) // Get single note + +// Navigation +api.openTabWithNote(noteId) // Open note in new tab +api.closeActiveTab() // Close current tab +api.activateNote(noteId) // Switch to note + +// Attributes +api.getAttribute(noteId, type, name) // Get attribute +api.getAttributes(noteId, type, name) // Get all matching attributes + +// Custom Widgets +api.addButtonToToolbar(def) // Add toolbar button +api.addCustomWidget(def) // Add custom widget + +// Events +api.runOnNoteOpened(callback) // Note opened event +api.runOnNoteContentChange(callback) // Content changed event + +// Utilities +api.dayjs // Date/time library +api.formatDate(date) // Format date +api.log(message) // Console log +``` + +### Backend API + +**Location:** `apps/server/src/services/backend_script_api.ts` + +**Access:** `require('@triliumnext/api')` or global `api` + +**Key Methods:** + +```typescript +// Note Operations +api.getNote(noteId) // Get note from Becca +api.getNoteWithContent(noteId) // Get note with content +api.createNote(parentNoteId, title) // Create new note +api.deleteNote(noteId) // Delete note + +// Branch Operations +api.getBranch(branchId) // Get branch +api.createBranch(noteId, parentNoteId) // Create branch (clone) + +// Attribute Operations +api.getAttribute(noteId, type, name) // Get attribute +api.createAttribute(noteId, type, name, value) // Create attribute + +// Database Access +api.sql.getRow(query, params) // Execute SQL query (single row) +api.sql.getRows(query, params) // Execute SQL query (multiple rows) +api.sql.execute(query, params) // Execute SQL statement + +// Events +api.onNoteCreated(callback) // Note created event +api.onNoteUpdated(callback) // Note updated event +api.onNoteDeleted(callback) // Note deleted event +api.onAttributeCreated(callback) // Attribute created event + +// Search +api.searchForNotes(searchQuery) // Search notes + +// Date/Time +api.dayjs // Date/time library +api.now() // Current date/time + +// Logging +api.log(message) // Log message +api.error(message) // Log error + +// External Communication +api.axios // HTTP client library + +// Utilities +api.backup.backupNow() // Trigger backup +api.export.exportSubtree(noteId) // Export notes +``` + +## Script Attributes + +### Execute Attributes + +- `#run=frontendStartup` - Execute on frontend startup +- `#run=backendStartup` - Execute on backend startup +- `#run=hourly` - Execute every hour +- `#run=daily` - Execute daily + +### Widget Attributes + +- `#customWidget` - Custom note widget +- `#widget` - Standard widget integration + +### Other Attributes + +- `#disableVersioning` - Disable automatic versioning for this note +- `#hideChildrenOverview` - Hide children in overview +- `#iconClass` - Custom icon for note + +## Entity Classes + +### Frontend Entities + +**FNote** (`apps/client/src/entities/fnote.ts`) + +```typescript +class FNote { + noteId: string + title: string + type: string + mime: string + + // Relationships + getParentNotes(): FNote[] + getChildNotes(): FNote[] + getBranches(): FBranch[] + + // Attributes + getAttribute(type, name): FAttribute + getAttributes(type?, name?): FAttribute[] + hasLabel(name): boolean + getLabelValue(name): string + + // Content + getContent(): Promise + + // Navigation + open(): void +} +``` + +**FBranch** + +```typescript +class FBranch { + branchId: string + noteId: string + parentNoteId: string + prefix: string + notePosition: number + + getNote(): FNote + getParentNote(): FNote +} +``` + +**FAttribute** + +```typescript +class FAttribute { + attributeId: string + noteId: string + type: 'label' | 'relation' + name: string + value: string + + getNote(): FNote + getTargetNote(): FNote // For relations +} +``` + +### Backend Entities + +**BNote** (`apps/server/src/becca/entities/bnote.ts`) + +```typescript +class BNote { + noteId: string + title: string + type: string + mime: string + isProtected: boolean + + // Content + getContent(): string | Buffer + setContent(content: string | Buffer): void + + // Relationships + getParentNotes(): BNote[] + getChildNotes(): BNote[] + getBranches(): BBranch[] + + // Attributes + getAttribute(type, name): BAttribute + getAttributes(type?, name?): BAttribute[] + setLabel(name, value): BAttribute + setRelation(name, targetNoteId): BAttribute + hasLabel(name): boolean + getLabelValue(name): string + + // Operations + save(): void + markAsDeleted(): void +} +``` + +**BBranch** + +```typescript +class BBranch { + branchId: string + noteId: string + parentNoteId: string + prefix: string + notePosition: number + + getNote(): BNote + getParentNote(): BNote + save(): void +} +``` + +**BAttribute** + +```typescript +class BAttribute { + attributeId: string + noteId: string + type: 'label' | 'relation' + name: string + value: string + + getNote(): BNote + getTargetNote(): BNote // For relations + save(): void +} +``` + +## Script Examples + +### Frontend Examples + +**1. Custom Toolbar Button** + +```javascript +// #run=frontendStartup +api.addButtonToToolbar({ + title: 'Export to PDF', + icon: 'file-export', + action: async () => { + const note = api.getActiveNote() + if (note) { + await api.runOnBackend('exportToPdf', [note.noteId]) + api.showMessage('Export started') + } + } +}) +``` + +**2. Auto-Save Reminder** + +```javascript +// #run=frontendStartup +let saveTimer +api.runOnNoteContentChange(() => { + clearTimeout(saveTimer) + saveTimer = setTimeout(() => { + api.showMessage('Remember to save your work!') + }, 300000) // 5 minutes +}) +``` + +**3. Note Statistics Widget** + +```javascript +// #customWidget +class StatsWidget extends api.NoteContextAwareWidget { + doRender() { + this.$widget = $('
') + return this.$widget + } + + async refreshWithNote(note) { + const content = await note.getContent() + const words = content.split(/\s+/).length + const chars = content.length + + this.$widget.html(` +
Words: ${words}
+
Characters: ${chars}
+ `) + } +} + +module.exports = StatsWidget +``` + +### Backend Examples + +**1. Auto-Tagging on Note Creation** + +```javascript +// #run=backendStartup +api.onNoteCreated((note) => { + // Auto-tag TODO notes + if (note.title.includes('TODO')) { + note.setLabel('type', 'todo') + note.setLabel('priority', 'normal') + } + + // Auto-tag meeting notes by date + if (note.title.match(/Meeting \d{4}-\d{2}-\d{2}/)) { + note.setLabel('type', 'meeting') + const dateMatch = note.title.match(/(\d{4}-\d{2}-\d{2})/) + if (dateMatch) { + note.setLabel('date', dateMatch[1]) + } + } +}) +``` + +**2. Daily Backup Reminder** + +```javascript +// #run=daily +const todayNote = api.getTodayNote() +todayNote.setLabel('backupDone', 'false') + +// Create reminder note +api.createNote(todayNote.noteId, '🔔 Backup Reminder', { + content: 'Remember to verify today\'s backup!', + type: 'text' +}) +``` + +**3. External API Integration** + +```javascript +// #run=backendStartup +api.onNoteCreated(async (note) => { + // Sync new notes to external service + if (note.hasLabel('sync-external')) { + try { + await api.axios.post('https://external-api.com/sync', { + noteId: note.noteId, + title: note.title, + content: note.getContent() + }) + note.setLabel('lastSync', api.dayjs().format()) + } catch (error) { + api.log('Sync failed: ' + error.message) + } + } +}) +``` + +**4. Database Cleanup** + +```javascript +// #run=weekly +// Clean up old revisions +const cutoffDate = api.dayjs().subtract(90, 'days').format() + +const oldRevisions = api.sql.getRows(` + SELECT revisionId FROM revisions + WHERE utcDateCreated < ? +`, [cutoffDate]) + +api.log(`Deleting ${oldRevisions.length} old revisions`) + +for (const row of oldRevisions) { + api.sql.execute('DELETE FROM revisions WHERE revisionId = ?', [row.revisionId]) +} +``` + +## Script Storage + +**Storage Location:** Scripts are stored as regular notes + +**Identifying Scripts:** +- Have `#run` attribute or `#customWidget` attribute +- Type is typically `code` with MIME `application/javascript` + +**Script Note Structure:** +``` +📁 Scripts (folder note) +├── 📜 Frontend Scripts +│ ├── Custom Toolbar Button (#run=frontendStartup) +│ └── Statistics Widget (#customWidget) +└── 📜 Backend Scripts + ├── Auto-Tagger (#run=backendStartup) + └── Daily Backup (#run=daily) +``` + +## Script Execution + +### Frontend Execution + +**Timing:** +1. Trilium frontend loads +2. Froca cache initializes +3. Script notes with `#run=frontendStartup` are found +4. Scripts execute in dependency order + +**Isolation:** +- Each script runs in separate context +- Shared `window.api` object +- Can access global window object + +### Backend Execution + +**Timing:** +1. Server starts +2. Becca cache loads +3. Script notes with `#run=backendStartup` are found +4. Scripts execute in dependency order + +**Isolation:** +- Each script is a separate module +- Can require Node.js modules +- Shared `api` global + +### Error Handling + +**Frontend:** +```javascript +try { + // Script code +} catch (error) { + api.showError('Script error: ' + error.message) + console.error(error) +} +``` + +**Backend:** +```javascript +try { + // Script code +} catch (error) { + api.log('Script error: ' + error.message) + console.error(error) +} +``` + +## Security Considerations + +### Frontend Scripts + +**Risks:** +- Can access all notes via Froca +- Can manipulate DOM +- Can make API calls +- Limited by browser security model + +**Mitigations:** +- User must trust scripts they add +- Scripts run with user privileges +- No access to file system + +### Backend Scripts + +**Risks:** +- Full Node.js access +- Can execute system commands +- Can access file system +- Can make network requests + +**Mitigations:** +- Scripts are user-created (trusted) +- Single-user model (no privilege escalation) +- Review scripts before adding `#run` attribute + +### Best Practices + +1. **Review script code** before adding execution attributes +2. **Use specific attributes** rather than wildcard searches +3. **Avoid eval()** and dynamic code execution +4. **Validate inputs** in scripts +5. **Handle errors** gracefully +6. **Log important actions** for audit trail + +## Performance Considerations + +### Optimization Tips + +**1. Cache Results:** +```javascript +// Bad: Re-query on every call +function getConfig() { + return api.getNote('config').getContent() +} + +// Good: Cache the result +let cachedConfig +function getConfig() { + if (!cachedConfig) { + cachedConfig = api.getNote('config').getContent() + } + return cachedConfig +} +``` + +**2. Use Efficient Queries:** +```javascript +// Bad: Load all notes and filter +const todos = api.searchForNotes('#type=todo') + +// Good: Use specific search +const todos = api.searchForNotes('#type=todo #status=pending') +``` + +**3. Batch Operations:** +```javascript +// Bad: Save after each change +notes.forEach(note => { + note.title = 'Updated' + note.save() +}) + +// Good: Batch changes +notes.forEach(note => { + note.title = 'Updated' +}) +// Save happens in batch +``` + +**4. Debounce Event Handlers:** +```javascript +let timeout +api.runOnNoteContentChange(() => { + clearTimeout(timeout) + timeout = setTimeout(() => { + // Process change + }, 500) +}) +``` + +## Debugging Scripts + +### Frontend Debugging + +**Browser DevTools:** +```javascript +console.log('Debug info:', data) +debugger // Breakpoint +``` + +**Trilium Log:** +```javascript +api.log('Script executed') +``` + +### Backend Debugging + +**Console Output:** +```javascript +console.log('Backend debug:', data) +api.log('Script log message') +``` + +**Inspect Becca:** +```javascript +api.log('Note count:', Object.keys(api.becca.notes).length) +``` + +## Advanced Topics + +### Custom Note Types + +Scripts can implement custom note type handlers: + +```javascript +// Register custom type +api.registerNoteType({ + type: 'mytype', + mime: 'application/x-mytype', + renderNote: (note) => { + // Custom rendering + } +}) +``` + +### External Libraries + +**Frontend:** +```javascript +// Load external library +const myLib = await import('https://cdn.example.com/lib.js') +``` + +**Backend:** +```javascript +// Use Node.js require +const fs = require('fs') +const axios = require('axios') +``` + +### State Persistence + +**Frontend:** +```javascript +// Use localStorage +localStorage.setItem('myScript:data', JSON.stringify(data)) +const data = JSON.parse(localStorage.getItem('myScript:data')) +``` + +**Backend:** +```javascript +// Store in special note +const stateNote = api.getNote('script-state-note') +stateNote.setContent(JSON.stringify(data)) + +const data = JSON.parse(stateNote.getContent()) +``` + +--- + +**See Also:** +- [Script API Documentation](Script%20API/) - Complete API reference +- [Advanced Showcases](https://triliumnext.github.io/Docs/Wiki/advanced-showcases) - Example scripts +- [ARCHITECTURE.md](ARCHITECTURE.md) - Overall architecture diff --git a/docs/SECURITY_ARCHITECTURE.md b/docs/SECURITY_ARCHITECTURE.md new file mode 100644 index 000000000..27993deac --- /dev/null +++ b/docs/SECURITY_ARCHITECTURE.md @@ -0,0 +1,834 @@ +# Trilium Security Architecture + +> **Related:** [ARCHITECTURE.md](ARCHITECTURE.md) | [SECURITY.md](../SECURITY.md) + +## Overview + +Trilium implements a **defense-in-depth security model** with multiple layers of protection for user data. The security architecture covers authentication, authorization, encryption, input sanitization, and secure communication. + +## Security Principles + +1. **Data Privacy**: User data is protected at rest and in transit +2. **Encryption**: Per-note encryption for sensitive content +3. **Authentication**: Multiple authentication methods supported +4. **Authorization**: Single-user model with granular protected sessions +5. **Input Validation**: All user input sanitized +6. **Secure Defaults**: Security features enabled by default +7. **Transparency**: Open source allows security audits + +## Threat Model + +### Threats Considered + +1. **Unauthorized Access** + - Physical access to device + - Network eavesdropping + - Stolen credentials + - Session hijacking + +2. **Data Exfiltration** + - Malicious scripts + - XSS attacks + - SQL injection + - CSRF attacks + +3. **Data Corruption** + - Malicious modifications + - Database tampering + - Sync conflicts + +4. **Privacy Leaks** + - Unencrypted backups + - Search indexing + - Temporary files + - Memory dumps + +### Out of Scope + +- Nation-state attackers +- Zero-day vulnerabilities in dependencies +- Hardware vulnerabilities (Spectre, Meltdown) +- Physical access with unlimited time +- Quantum computing attacks + +## Authentication + +### Password Authentication + +**Implementation:** `apps/server/src/services/password.ts` + +**Password Storage:** +```typescript +// Password is never stored directly +const salt = crypto.randomBytes(32) +const derivedKey = crypto.pbkdf2Sync(password, salt, 10000, 32, 'sha256') +const verificationHash = crypto.createHash('sha256') + .update(derivedKey) + .digest('hex') + +// Store only salt and verification hash +sql.insert('user_data', { + salt: salt.toString('hex'), + derivedKey: derivedKey.toString('hex') // Used for encryption +}) + +sql.insert('options', { + name: 'passwordVerificationHash', + value: verificationHash +}) +``` + +**Password Requirements:** +- Minimum length: 4 characters (configurable) +- No maximum length +- All characters allowed +- Can be changed by user + +**Login Process:** +```typescript +// 1. User submits password +POST /api/login/password +Body: { password: "user-password" } + +// 2. Server derives key +const derivedKey = crypto.pbkdf2Sync(password, salt, 10000, 32, 'sha256') + +// 3. Verify against stored hash +const verificationHash = crypto.createHash('sha256') + .update(derivedKey) + .digest('hex') + +if (verificationHash === storedHash) { + // 4. Create session + req.session.loggedIn = true + req.session.regenerate() +} +``` + +### TOTP (Two-Factor Authentication) + +**Implementation:** `apps/server/src/routes/api/login.ts` + +**Setup Process:** +```typescript +// 1. Generate secret +const secret = speakeasy.generateSecret({ + name: `Trilium (${username})`, + length: 32 +}) + +// 2. Store encrypted secret +const encryptedSecret = encrypt(secret.base32, dataKey) +sql.insert('options', { + name: 'totpSecret', + value: encryptedSecret +}) + +// 3. Generate QR code +const qrCodeUrl = secret.otpauth_url +``` + +**Verification:** +```typescript +// User submits TOTP token +POST /api/login/totp +Body: { token: "123456" } + +// Verify token +const secret = decrypt(encryptedSecret, dataKey) +const verified = speakeasy.totp.verify({ + secret: secret, + encoding: 'base32', + token: token, + window: 1 // Allow 1 time step tolerance +}) +``` + +### OpenID Connect + +**Implementation:** `apps/server/src/routes/api/login.ts` + +**Supported Providers:** +- Any OpenID Connect compatible provider +- Google, GitHub, Auth0, etc. + +**Flow:** +```typescript +// 1. Redirect to provider +GET /api/login/openid + +// 2. Provider redirects back with code +GET /api/login/openid/callback?code=... + +// 3. Exchange code for tokens +const tokens = await openidClient.callback(redirectUri, req.query) + +// 4. Verify ID token +const claims = tokens.claims() + +// 5. Create session +req.session.loggedIn = true +``` + +### Session Management + +**Session Storage:** SQLite database (sessions table) + +**Session Configuration:** +```typescript +app.use(session({ + secret: sessionSecret, + resave: false, + saveUninitialized: false, + rolling: true, + cookie: { + maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days + httpOnly: true, + secure: isHttps, + sameSite: 'lax' + }, + store: new SqliteStore({ + db: db, + table: 'sessions' + }) +})) +``` + +**Session Invalidation:** +- Automatic timeout after inactivity +- Manual logout clears session +- Server restart invalidates all sessions (optional) + +## Authorization + +### Single-User Model + +**Desktop:** +- Single user (owner of device) +- No multi-user support +- Full access to all notes + +**Server:** +- Single user per installation +- Authentication required for all operations +- No user roles or permissions + +### Protected Sessions + +**Purpose:** Temporary access to encrypted (protected) notes + +**Implementation:** `apps/server/src/services/protected_session.ts` + +**Workflow:** +```typescript +// 1. User enters password for protected notes +POST /api/protected-session/enter +Body: { password: "protected-password" } + +// 2. Derive encryption key +const protectedDataKey = deriveKey(password) + +// 3. Verify password (decrypt known encrypted value) +const decrypted = decrypt(testValue, protectedDataKey) +if (decrypted === expectedValue) { + // 4. Store in memory (not in session) + protectedSessionHolder.setProtectedDataKey(protectedDataKey) + + // 5. Set timeout + setTimeout(() => { + protectedSessionHolder.clearProtectedDataKey() + }, timeout) +} +``` + +**Protected Session Timeout:** +- Default: 10 minutes (configurable) +- Extends on activity +- Cleared on browser close +- Separate from main session + +### API Authorization + +**Internal API:** +- Requires authenticated session +- CSRF token validation +- Same-origin policy + +**ETAPI (External API):** +- Token-based authentication +- No session required +- Rate limiting + +## Encryption + +### Note Encryption + +**Encryption Algorithm:** AES-256-CBC + +**Key Hierarchy:** +``` +User Password + ↓ (PBKDF2) +Data Key (for protected notes) + ↓ (AES-256) +Protected Note Content +``` + +**Encryption Process:** +```typescript +// 1. Generate IV (initialization vector) +const iv = crypto.randomBytes(16) + +// 2. Encrypt content +const cipher = crypto.createCipheriv('aes-256-cbc', dataKey, iv) +let encrypted = cipher.update(content, 'utf8', 'base64') +encrypted += cipher.final('base64') + +// 3. Prepend IV to encrypted content +const encryptedBlob = iv.toString('base64') + ':' + encrypted + +// 4. Store in database +sql.insert('blobs', { + blobId: blobId, + content: encryptedBlob +}) +``` + +**Decryption Process:** +```typescript +// 1. Split IV and encrypted content +const [ivBase64, encryptedData] = encryptedBlob.split(':') +const iv = Buffer.from(ivBase64, 'base64') + +// 2. Decrypt +const decipher = crypto.createDecipheriv('aes-256-cbc', dataKey, iv) +let decrypted = decipher.update(encryptedData, 'base64', 'utf8') +decrypted += decipher.final('utf8') + +return decrypted +``` + +**Protected Note Metadata:** +- Title is NOT encrypted (for tree display) +- Type and MIME are NOT encrypted +- Content IS encrypted +- Attributes CAN be encrypted (optional) + +### Data Key Management + +**Master Data Key:** +```typescript +// Generated once during setup +const dataKey = crypto.randomBytes(32) // 256 bits + +// Encrypted with derived key from user password +const derivedKey = crypto.pbkdf2Sync(password, salt, 10000, 32, 'sha256') +const encryptedDataKey = encrypt(dataKey, derivedKey) + +// Stored in database +sql.insert('options', { + name: 'encryptedDataKey', + value: encryptedDataKey.toString('hex') +}) +``` + +**Key Rotation:** +- Not currently supported +- Requires re-encrypting all protected notes +- Planned for future version + +### Transport Encryption + +**HTTPS:** +- Required for server installations (recommended) +- TLS 1.2+ only +- Strong cipher suites preferred +- Certificate validation enabled + +**Desktop:** +- Local communication (no network) +- No HTTPS required + +### Backup Encryption + +**Database Backups:** +- Protected notes remain encrypted in backup +- Backup file should be protected separately +- Consider encrypting backup storage location + +## Input Sanitization + +### XSS Prevention + +**HTML Sanitization:** + +Location: `apps/client/src/services/dompurify.ts` + +```typescript +import DOMPurify from 'dompurify' + +// Configure DOMPurify +DOMPurify.setConfig({ + ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'div', ...], + ALLOWED_ATTR: ['href', 'title', 'class', 'id', ...], + ALLOW_DATA_ATTR: false +}) + +// Sanitize HTML before rendering +const cleanHtml = DOMPurify.sanitize(userHtml) +``` + +**CKEditor Configuration:** +```typescript +// apps/client/src/widgets/type_widgets/text_type_widget.ts +ClassicEditor.create(element, { + // Restrict allowed content + htmlSupport: { + allow: [ + { name: /./, attributes: true, classes: true, styles: true } + ], + disallow: [ + { name: 'script' }, + { name: 'iframe', attributes: /^(?!src$).*/ } + ] + } +}) +``` + +**Content Security Policy:** +```typescript +// apps/server/src/main.ts +app.use((req, res, next) => { + res.setHeader('Content-Security-Policy', + "default-src 'self'; " + + "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " + + "style-src 'self' 'unsafe-inline'; " + + "img-src 'self' data: blob:;" + ) + next() +}) +``` + +### SQL Injection Prevention + +**Parameterized Queries:** +```typescript +// GOOD - Safe from SQL injection +const notes = sql.getRows( + 'SELECT * FROM notes WHERE title = ?', + [userInput] +) + +// BAD - Vulnerable to SQL injection +const notes = sql.getRows( + `SELECT * FROM notes WHERE title = '${userInput}'` +) +``` + +**ORM Usage:** +```typescript +// Entity-based access prevents SQL injection +const note = becca.getNote(noteId) +note.title = userInput // Sanitized by entity +note.save() // Parameterized query +``` + +### CSRF Prevention + +**CSRF Token Validation:** + +Location: `apps/server/src/routes/middleware/csrf.ts` + +```typescript +// Generate CSRF token +const csrfToken = crypto.randomBytes(32).toString('hex') +req.session.csrfToken = csrfToken + +// Validate on state-changing requests +app.use((req, res, next) => { + if (['POST', 'PUT', 'DELETE'].includes(req.method)) { + const token = req.headers['x-csrf-token'] + if (token !== req.session.csrfToken) { + return res.status(403).json({ error: 'CSRF token mismatch' }) + } + } + next() +}) +``` + +**Client-Side:** +```typescript +// apps/client/src/services/server.ts +const csrfToken = getCsrfToken() + +fetch('/api/notes', { + method: 'POST', + headers: { + 'X-CSRF-Token': csrfToken, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) +}) +``` + +### File Upload Validation + +**Validation:** +```typescript +// apps/server/src/routes/api/attachments.ts +const allowedMimeTypes = [ + 'image/jpeg', + 'image/png', + 'application/pdf', + // ... +] + +if (!allowedMimeTypes.includes(file.mimetype)) { + throw new Error('File type not allowed') +} + +// Validate file size +const maxSize = 100 * 1024 * 1024 // 100 MB +if (file.size > maxSize) { + throw new Error('File too large') +} + +// Sanitize filename +const sanitizedFilename = path.basename(file.originalname) + .replace(/[^a-z0-9.-]/gi, '_') +``` + +## Network Security + +### HTTPS Configuration + +**Server Setup:** +```typescript +// apps/server/src/main.ts +const httpsOptions = { + key: fs.readFileSync('server.key'), + cert: fs.readFileSync('server.cert') +} + +https.createServer(httpsOptions, app).listen(443) +``` + +**Certificate Validation:** +- Require valid certificates in production +- Self-signed certificates allowed for development +- Certificate pinning not implemented + +### Secure Headers + +```typescript +// apps/server/src/main.ts +app.use((req, res, next) => { + // Prevent clickjacking + res.setHeader('X-Frame-Options', 'SAMEORIGIN') + + // Prevent MIME sniffing + res.setHeader('X-Content-Type-Options', 'nosniff') + + // XSS protection + res.setHeader('X-XSS-Protection', '1; mode=block') + + // Referrer policy + res.setHeader('Referrer-Policy', 'same-origin') + + // HTTPS upgrade + if (req.secure) { + res.setHeader('Strict-Transport-Security', 'max-age=31536000') + } + + next() +}) +``` + +### Rate Limiting + +**API Rate Limiting:** +```typescript +// apps/server/src/routes/middleware/rate_limit.ts +const rateLimit = require('express-rate-limit') + +const apiLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 1000, // Limit each IP to 1000 requests per window + message: 'Too many requests from this IP' +}) + +app.use('/api/', apiLimiter) +``` + +**Login Rate Limiting:** +```typescript +const loginLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 5, // 5 failed attempts + skipSuccessfulRequests: true +}) + +app.post('/api/login/password', loginLimiter, loginHandler) +``` + +## Data Security + +### Secure Data Deletion + +**Soft Delete:** +```typescript +// Mark as deleted (sync first) +note.isDeleted = 1 +note.deleteId = generateUUID() +note.save() + +// Entity change tracked for sync +addEntityChange('notes', noteId, note) +``` + +**Hard Delete (Erase):** +```typescript +// After sync completed +sql.execute('DELETE FROM notes WHERE noteId = ?', [noteId]) +sql.execute('DELETE FROM branches WHERE noteId = ?', [noteId]) +sql.execute('DELETE FROM attributes WHERE noteId = ?', [noteId]) + +// Mark entity change as erased +sql.execute('UPDATE entity_changes SET isErased = 1 WHERE entityId = ?', [noteId]) +``` + +**Blob Cleanup:** +```typescript +// Find orphaned blobs (not referenced by any note/revision/attachment) +const orphanedBlobs = sql.getRows(` + SELECT blobId FROM blobs + WHERE blobId NOT IN (SELECT blobId FROM notes WHERE blobId IS NOT NULL) + AND blobId NOT IN (SELECT blobId FROM revisions WHERE blobId IS NOT NULL) + AND blobId NOT IN (SELECT blobId FROM attachments WHERE blobId IS NOT NULL) +`) + +// Delete orphaned blobs +for (const blob of orphanedBlobs) { + sql.execute('DELETE FROM blobs WHERE blobId = ?', [blob.blobId]) +} +``` + +### Memory Security + +**Protected Data in Memory:** +- Protected data keys stored in memory only +- Cleared on timeout +- Not written to disk +- Not in session storage + +**Memory Cleanup:** +```typescript +// Clear sensitive data +const clearSensitiveData = () => { + protectedDataKey = null + + // Force garbage collection if available + if (global.gc) { + global.gc() + } +} +``` + +### Temporary Files + +**Secure Temporary Files:** +```typescript +const tempDir = os.tmpdir() +const tempFile = path.join(tempDir, `trilium-${crypto.randomBytes(16).toString('hex')}`) + +// Write temp file +fs.writeFileSync(tempFile, data, { mode: 0o600 }) // Owner read/write only + +// Clean up after use +fs.unlinkSync(tempFile) +``` + +## Dependency Security + +### Vulnerability Scanning + +**Tools:** +- `npm audit` - Check for known vulnerabilities +- Renovate bot - Automatic dependency updates +- GitHub Dependabot alerts + +**Process:** +```bash +# Check for vulnerabilities +npm audit + +# Fix automatically +npm audit fix + +# Manual review for breaking changes +npm audit fix --force +``` + +### Dependency Pinning + +**package.json:** +```json +{ + "dependencies": { + "express": "4.18.2", // Exact version + "better-sqlite3": "^9.2.2" // Compatible versions + } +} +``` + +**pnpm Overrides:** +```json +{ + "pnpm": { + "overrides": { + "lodash@<4.17.21": ">=4.17.21", // Force minimum version + "axios@<0.21.2": ">=0.21.2" + } + } +} +``` + +### Patch Management + +**pnpm Patches:** +```bash +# Create patch +pnpm patch @ckeditor/ckeditor5 + +# Edit files in temporary directory +# ... + +# Generate patch file +pnpm patch-commit /tmp/ckeditor5-patch + +# Patch applied automatically on install +``` + +## Security Best Practices + +### For Users + +1. **Strong Passwords** + - Use unique password for Trilium + - Enable TOTP 2FA + - Protect password manager + +2. **Protected Notes** + - Use for sensitive information + - Set reasonable session timeout + - Don't leave sessions unattended + +3. **Backups** + - Regular backups to secure location + - Encrypt backup storage + - Test backup restoration + +4. **Server Setup** + - Use HTTPS only + - Keep software updated + - Firewall configuration + - Use reverse proxy (nginx, Caddy) + +5. **Scripts** + - Review scripts before using + - Be cautious with external scripts + - Understand script permissions + +### For Developers + +1. **Code Review** + - Review all security-related changes + - Test authentication/authorization changes + - Validate input sanitization + +2. **Testing** + - Write security tests + - Test edge cases + - Penetration testing + +3. **Dependencies** + - Regular updates + - Audit new dependencies + - Monitor security advisories + +4. **Secrets** + - No secrets in source code + - Use environment variables + - Secure key generation + +## Security Auditing + +### Logs + +**Security Events Logged:** +- Login attempts (success/failure) +- Protected session access +- Password changes +- ETAPI token usage +- Failed CSRF validations + +**Log Location:** +- Desktop: Console output +- Server: Log files or stdout + +### Monitoring + +**Metrics to Monitor:** +- Failed login attempts +- API error rates +- Unusual database changes +- Large exports/imports + +## Incident Response + +### Security Issue Reporting + +**Process:** +1. Email security@triliumnext.com +2. Include vulnerability details +3. Provide reproduction steps +4. Allow reasonable disclosure time + +**Response:** +1. Acknowledge within 48 hours +2. Investigate and validate +3. Develop fix +4. Coordinate disclosure +5. Release patch + +### Breach Response + +**If Compromised:** +1. Change password immediately +2. Review recent activity +3. Check for unauthorized changes +4. Restore from backup if needed +5. Update security settings + +## Future Security Enhancements + +**Planned:** +- Hardware security key support (U2F/WebAuthn) +- End-to-end encryption for sync +- Zero-knowledge architecture option +- Encryption key rotation +- Audit log enhancements +- Per-note access controls + +**Under Consideration:** +- Multi-user support with permissions +- Blockchain-based sync verification +- Homomorphic encryption for search +- Quantum-resistant encryption + +--- + +**See Also:** +- [SECURITY.md](../SECURITY.md) - Security policy +- [ARCHITECTURE.md](ARCHITECTURE.md) - Overall architecture +- [Protected Notes Guide](https://triliumnext.github.io/Docs/Wiki/protected-notes) diff --git a/docs/SYNCHRONIZATION.md b/docs/SYNCHRONIZATION.md new file mode 100644 index 000000000..2f39eee40 --- /dev/null +++ b/docs/SYNCHRONIZATION.md @@ -0,0 +1,583 @@ +# Trilium Synchronization Architecture + +> **Related:** [ARCHITECTURE.md](ARCHITECTURE.md) | [User Guide: Synchronization](https://triliumnext.github.io/Docs/Wiki/synchronization) + +## Overview + +Trilium implements a sophisticated **bidirectional synchronization system** that allows users to sync their note databases across multiple devices (desktop clients and server instances). The sync protocol is designed to handle: + +- Concurrent modifications across devices +- Conflict resolution +- Partial sync (only changed entities) +- Protected note synchronization +- Efficient bandwidth usage + +## Sync Architecture + +``` +┌─────────────┐ ┌─────────────┐ +│ Desktop 1 │ │ Desktop 2 │ +│ (Client) │ │ (Client) │ +└──────┬──────┘ └──────┬──────┘ + │ │ + │ WebSocket/HTTP │ + │ │ + ▼ ▼ +┌────────────────────────────────────────────────┐ +│ Sync Server │ +│ ┌──────────────────────────────────────┐ │ +│ │ Sync Service │ │ +│ │ - Entity Change Management │ │ +│ │ - Conflict Resolution │ │ +│ │ - Version Tracking │ │ +│ └──────────────────────────────────────┘ │ +│ │ │ +│ ┌──────┴───────┐ │ +│ │ Database │ │ +│ │ (entity_changes)│ │ +│ └──────────────┘ │ +└────────────────────────────────────────────────┘ +``` + +## Core Concepts + +### Entity Changes + +Every modification to any entity (note, branch, attribute, etc.) creates an **entity change** record: + +```sql +entity_changes ( + id, -- Auto-increment ID + entityName, -- 'notes', 'branches', 'attributes', etc. + entityId, -- ID of the changed entity + hash, -- Content hash for integrity + isErased, -- If entity was erased (deleted permanently) + changeId, -- Unique change identifier + componentId, -- Installation identifier + instanceId, -- Process instance identifier + isSynced, -- Whether synced to server + utcDateChanged -- When change occurred +) +``` + +**Key Properties:** +- **changeId**: Globally unique identifier (UUID) for the change +- **componentId**: Unique per Trilium installation (persists across restarts) +- **instanceId**: Unique per process (changes on restart) +- **hash**: SHA-256 hash of entity data for integrity verification + +### Sync Versions + +Each Trilium installation tracks: +- **Local sync version**: Highest change ID seen locally +- **Server sync version**: Highest change ID on server +- **Entity versions**: Last sync version for each entity type + +### Change Tracking + +**When an entity is modified:** + +```typescript +// apps/server/src/services/entity_changes.ts +function addEntityChange(entityName, entityId, entity) { + const hash = calculateHash(entity) + const changeId = generateUUID() + + sql.insert('entity_changes', { + entityName, + entityId, + hash, + changeId, + componentId: config.componentId, + instanceId: config.instanceId, + isSynced: 0, + utcDateChanged: now() + }) +} +``` + +**Entity modification triggers:** +- Note content update +- Note metadata change +- Branch creation/deletion/reorder +- Attribute addition/removal +- Options modification + +## Sync Protocol + +### Sync Handshake + +**Step 1: Client Initiates Sync** + +```typescript +// Client sends current sync version +POST /api/sync/check +{ + "sourceId": "client-component-id", + "maxChangeId": 12345 +} +``` + +**Step 2: Server Responds with Status** + +```typescript +// Server checks for changes +Response: +{ + "entityChanges": 567, // Changes on server + "maxChangeId": 12890, // Server's max change ID + "outstandingPushCount": 23 // Client changes not yet synced +} +``` + +**Step 3: Decision** + +- If `entityChanges > 0`: Pull changes from server +- If `outstandingPushCount > 0`: Push changes to server +- Both can happen in sequence + +### Pull Sync (Server → Client) + +**Client Requests Changes:** + +```typescript +POST /api/sync/pull +{ + "sourceId": "client-component-id", + "lastSyncedChangeId": 12345 +} +``` + +**Server Responds:** + +```typescript +Response: +{ + "notes": [ + { noteId: "abc", title: "New Note", ... } + ], + "branches": [...], + "attributes": [...], + "revisions": [...], + "attachments": [...], + "entityChanges": [ + { entityName: "notes", entityId: "abc", changeId: "...", ... } + ], + "maxChangeId": 12890 +} +``` + +**Client Processing:** + +1. Apply entity changes to local database +2. Update Froca cache +3. Update local sync version +4. Trigger UI refresh + +### Push Sync (Client → Server) + +**Client Sends Changes:** + +```typescript +POST /api/sync/push +{ + "sourceId": "client-component-id", + "entities": [ + { + "entity": { + "noteId": "xyz", + "title": "Modified Note", + ... + }, + "entityChange": { + "changeId": "change-uuid", + "entityName": "notes", + ... + } + } + ] +} +``` + +**Server Processing:** + +1. Validate changes +2. Check for conflicts +3. Apply changes to database +4. Update Becca cache +5. Mark as synced +6. Broadcast to other connected clients via WebSocket + +**Conflict Detection:** + +```typescript +// Check if entity was modified on server since client's last sync +const serverEntity = becca.getNote(noteId) +const serverLastModified = serverEntity.utcDateModified + +if (serverLastModified > clientSyncVersion) { + // CONFLICT! + resolveConflict(serverEntity, clientEntity) +} +``` + +## Conflict Resolution + +### Conflict Types + +**1. Content Conflict** +- Both client and server modified same note content +- **Resolution**: Last-write-wins based on `utcDateModified` + +**2. Structure Conflict** +- Branch moved/deleted on one side, modified on other +- **Resolution**: Tombstone records, reconciliation + +**3. Attribute Conflict** +- Same attribute modified differently +- **Resolution**: Last-write-wins + +### Conflict Resolution Strategy + +**Last-Write-Wins:** +```typescript +if (clientEntity.utcDateModified > serverEntity.utcDateModified) { + // Client wins, apply client changes + applyClientChange(clientEntity) +} else { + // Server wins, reject client change + // Client will pull server version on next sync +} +``` + +**Tombstone Records:** +- Deleted entities leave tombstone in `entity_changes` +- Prevents re-sync of deleted items +- `isErased = 1` for permanent deletions + +### Protected Notes Sync + +**Challenge:** Encrypted content can't be synced without password + +**Solution:** + +1. **Protected session required**: User must unlock protected notes +2. **Encrypted sync**: Content synced in encrypted form +3. **Hash verification**: Integrity checked without decryption +4. **Lazy decryption**: Only decrypt when accessed + +**Sync Flow:** + +```typescript +// Client side +if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) { + // Skip protected notes if session not active + continue +} + +// Server side +if (note.isProtected) { + // Sync encrypted blob + // Don't decrypt for sync + syncEncryptedBlob(note.blobId) +} +``` + +## Sync States + +### Connection States + +- **Connected**: WebSocket connection active +- **Disconnected**: No connection to sync server +- **Syncing**: Actively transferring data +- **Conflict**: Sync paused due to conflict + +### Entity Sync States + +Each entity can be in: +- **Synced**: In sync with server +- **Pending**: Local changes not yet pushed +- **Conflict**: Conflicting changes detected + +### UI Indicators + +```typescript +// apps/client/src/widgets/sync_status.ts +class SyncStatusWidget { + showSyncStatus() { + if (isConnected && allSynced) { + showIcon('synced') + } else if (isSyncing) { + showIcon('syncing-spinner') + } else if (hasConflicts) { + showIcon('conflict-warning') + } else { + showIcon('not-synced') + } + } +} +``` + +## Performance Optimizations + +### Incremental Sync + +Only entities changed since last sync are transferred: + +```sql +SELECT * FROM entity_changes +WHERE id > :lastSyncedChangeId +ORDER BY id ASC +LIMIT 1000 +``` + +### Batch Processing + +Changes sent in batches to reduce round trips: + +```typescript +const BATCH_SIZE = 1000 +const changes = getUnsyncedChanges(BATCH_SIZE) +await syncBatch(changes) +``` + +### Hash-Based Change Detection + +```typescript +// Only sync if hash differs +const localHash = calculateHash(localEntity) +const serverHash = getServerHash(entityId) + +if (localHash !== serverHash) { + syncEntity(localEntity) +} +``` + +### Compression + +Large payloads compressed before transmission: + +```typescript +// Server sends compressed response +res.setHeader('Content-Encoding', 'gzip') +res.send(gzip(syncData)) +``` + +## Error Handling + +### Network Errors + +**Retry Strategy:** +```typescript +const RETRY_DELAYS = [1000, 2000, 5000, 10000, 30000] + +async function syncWithRetry(attempt = 0) { + try { + await performSync() + } catch (error) { + if (attempt < RETRY_DELAYS.length) { + setTimeout(() => { + syncWithRetry(attempt + 1) + }, RETRY_DELAYS[attempt]) + } + } +} +``` + +### Sync Integrity Checks + +**Hash Verification:** +```typescript +// Verify entity hash matches +const calculatedHash = calculateHash(entity) +const receivedHash = entityChange.hash + +if (calculatedHash !== receivedHash) { + throw new Error('Hash mismatch - data corruption detected') +} +``` + +**Consistency Checks:** +- Orphaned branches detection +- Missing parent notes +- Invalid entity references +- Circular dependencies + +## Sync Server Configuration + +### Server Setup + +**Required Options:** +```javascript +{ + "syncServerHost": "https://sync.example.com", + "syncServerTimeout": 60000, + "syncProxy": "" // Optional HTTP proxy +} +``` + +**Authentication:** +- Username/password or +- Sync token (generated on server) + +### Client Setup + +**Desktop Client:** +```javascript +// Settings → Sync +{ + "syncServerHost": "https://sync.example.com", + "username": "user@example.com", + "password": "********" +} +``` + +**Test Connection:** +```typescript +POST /api/sync/test +Response: { "success": true } +``` + +## Sync API Endpoints + +Located at: `apps/server/src/routes/api/sync.ts` + +**Endpoints:** + +- `POST /api/sync/check` - Check sync status +- `POST /api/sync/pull` - Pull changes from server +- `POST /api/sync/push` - Push changes to server +- `POST /api/sync/finished` - Mark sync complete +- `POST /api/sync/test` - Test connection +- `GET /api/sync/stats` - Sync statistics + +## WebSocket Sync Updates + +Real-time sync via WebSocket: + +```typescript +// Server broadcasts change to all connected clients +ws.broadcast('entity-change', { + entityName: 'notes', + entityId: 'abc123', + changeId: 'change-uuid', + sourceId: 'originating-component-id' +}) + +// Client receives and applies +ws.on('entity-change', (data) => { + if (data.sourceId !== myComponentId) { + froca.processEntityChange(data) + } +}) +``` + +## Sync Scheduling + +### Automatic Sync + +**Desktop:** +- Sync on startup +- Periodic sync (configurable interval, default: 60s) +- Sync before shutdown + +**Server:** +- Sync on entity modification +- WebSocket push to connected clients + +### Manual Sync + +User can trigger: +- Full sync +- Sync now +- Sync specific subtree + +## Troubleshooting + +### Common Issues + +**Sync stuck:** +```sql +-- Reset sync state +UPDATE entity_changes SET isSynced = 0; +DELETE FROM options WHERE name LIKE 'sync%'; +``` + +**Hash mismatch:** +- Data corruption detected +- Re-sync from backup +- Check database integrity + +**Conflict loop:** +- Manual intervention required +- Export conflicting notes +- Choose winning version +- Re-sync + +### Sync Diagnostics + +**Check sync status:** +```typescript +GET /api/sync/stats +Response: { + "unsyncedChanges": 0, + "lastSyncDate": "2025-11-02T12:00:00Z", + "syncVersion": 12890 +} +``` + +**Entity change log:** +```sql +SELECT * FROM entity_changes +WHERE isSynced = 0 +ORDER BY id DESC; +``` + +## Security Considerations + +### Encrypted Sync + +- Protected notes synced encrypted +- No plain text over network +- Server cannot read protected content + +### Authentication + +- Username/password over HTTPS only +- Sync tokens for token-based auth +- Session cookies with CSRF protection + +### Authorization + +- Users can only sync their own data +- No cross-user sync support +- Sync server validates ownership + +## Performance Metrics + +**Typical Sync Performance:** +- 1000 changes: ~2-5 seconds +- 10000 changes: ~20-50 seconds +- Initial full sync (100k notes): ~5-10 minutes + +**Factors:** +- Network latency +- Database size +- Number of protected notes +- Attachment sizes + +## Future Improvements + +**Planned Enhancements:** +- Differential sync (binary diff) +- Peer-to-peer sync (no central server) +- Multi-server sync +- Partial sync (subtree only) +- Sync over Tor/I2P + +--- + +**See Also:** +- [ARCHITECTURE.md](ARCHITECTURE.md) - Overall architecture +- [Sync User Guide](https://triliumnext.github.io/Docs/Wiki/synchronization) +- [Sync API Source](../apps/server/src/routes/api/sync.ts) diff --git a/docs/TECHNICAL_DOCUMENTATION.md b/docs/TECHNICAL_DOCUMENTATION.md new file mode 100644 index 000000000..445df0530 --- /dev/null +++ b/docs/TECHNICAL_DOCUMENTATION.md @@ -0,0 +1,423 @@ +# Trilium Notes - Technical Documentation Index + +Welcome to the comprehensive technical and architectural documentation for Trilium Notes. This index provides quick access to all technical documentation resources. + +## 📚 Core Architecture Documentation + +### [ARCHITECTURE.md](ARCHITECTURE.md) +**Main technical architecture document** covering the complete system design. + +**Topics Covered:** +- High-level architecture overview +- Monorepo structure and organization +- Core architecture patterns (Becca, Froca, Shaca) +- Entity system and data model +- Widget-based UI architecture +- Frontend and backend architecture +- API architecture (Internal, ETAPI, WebSocket) +- Build system and tooling +- Testing strategy +- Security overview + +**Audience:** Developers, architects, contributors + +--- + +### [DATABASE.md](DATABASE.md) +**Complete database architecture and schema documentation.** + +**Topics Covered:** +- SQLite database structure +- Entity tables (notes, branches, attributes, revisions, attachments, blobs) +- System tables (options, entity_changes, sessions) +- Data relationships and integrity +- Database access patterns +- Migrations and versioning +- Performance optimization +- Backup and maintenance +- Security considerations + +**Audience:** Backend developers, database administrators + +--- + +### [SYNCHRONIZATION.md](SYNCHRONIZATION.md) +**Detailed synchronization protocol and implementation.** + +**Topics Covered:** +- Sync architecture overview +- Entity change tracking +- Sync protocol (handshake, pull, push) +- Conflict resolution strategies +- Protected notes synchronization +- Performance optimizations +- Error handling and retry logic +- Sync server configuration +- WebSocket real-time updates +- Troubleshooting guide + +**Audience:** Advanced users, sync server administrators, contributors + +--- + +### [SCRIPTING.md](SCRIPTING.md) +**Comprehensive guide to the Trilium scripting system.** + +**Topics Covered:** +- Script types (frontend, backend, render) +- Frontend API reference +- Backend API reference +- Entity classes (FNote, BNote, etc.) +- Script examples and patterns +- Script storage and execution +- Security considerations +- Performance optimization +- Debugging techniques +- Advanced topics + +**Audience:** Power users, script developers, plugin creators + +--- + +### [SECURITY_ARCHITECTURE.md](SECURITY_ARCHITECTURE.md) +**In-depth security architecture and implementation.** + +**Topics Covered:** +- Security principles and threat model +- Authentication methods (password, TOTP, OpenID) +- Session management +- Authorization and protected sessions +- Encryption (notes, transport, backups) +- Input sanitization (XSS, SQL injection, CSRF) +- Network security (HTTPS, headers, rate limiting) +- Data security and secure deletion +- Dependency security +- Security best practices +- Incident response + +**Audience:** Security engineers, administrators, auditors + +--- + +## 🔧 Developer Documentation + +### [Developer Guide](Developer%20Guide/Developer%20Guide/) +Collection of developer-focused documentation for contributing to Trilium. + +**Key Documents:** +- [Environment Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) - Setting up development environment +- [Project Structure](Developer%20Guide/Developer%20Guide/Project%20Structure.md) - Monorepo organization +- [Development and Architecture](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/) - Various development topics + +**Topics Include:** +- Local development setup +- Building and deployment +- Adding new note types +- Database schema details +- Internationalization +- Icons and UI customization +- Docker development +- Troubleshooting + +**Audience:** Contributors, developers + +--- + +## 📖 User Documentation + +### [User Guide](User%20Guide/User%20Guide/) +Comprehensive end-user documentation for using Trilium. + +**Key Sections:** +- Installation & Setup +- Basic Concepts and Features +- Note Types +- Advanced Usage +- Synchronization +- Import/Export + +**Audience:** End users, administrators + +--- + +### [Script API](Script%20API/) +Complete API reference for user scripting. + +**Coverage:** +- Frontend API methods +- Backend API methods +- Entity properties and methods +- Event handlers +- Utility functions + +**Audience:** Script developers, power users + +--- + +## 🚀 Quick Start Guides + +### For Users +1. [Installation Guide](User%20Guide/User%20Guide/Installation%20&%20Setup/) - Get Trilium running +2. [Basic Concepts](User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/) - Learn the fundamentals +3. [Scripting Guide](SCRIPTING.md) - Extend Trilium with scripts + +### For Developers +1. [Environment Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) - Setup development environment +2. [Architecture Overview](ARCHITECTURE.md) - Understand the system +3. [Contributing Guide](../README.md#-contribute) - Start contributing + +### For Administrators +1. [Server Installation](User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md) - Deploy Trilium server +2. [Synchronization Setup](SYNCHRONIZATION.md) - Configure sync +3. [Security Best Practices](SECURITY_ARCHITECTURE.md#security-best-practices) - Secure your installation + +--- + +## 🔍 Documentation by Topic + +### Architecture & Design +- [Overall Architecture](ARCHITECTURE.md) +- [Monorepo Structure](ARCHITECTURE.md#monorepo-structure) +- [Three-Layer Cache System](ARCHITECTURE.md#three-layer-cache-system) +- [Entity System](ARCHITECTURE.md#entity-system) +- [Widget-Based UI](ARCHITECTURE.md#widget-based-ui) + +### Data & Storage +- [Database Architecture](DATABASE.md) +- [Entity Tables](DATABASE.md#entity-tables) +- [Data Relationships](DATABASE.md#data-relationships) +- [Blob Storage](DATABASE.md#blobs-table) +- [Database Migrations](DATABASE.md#database-migrations) + +### Synchronization +- [Sync Architecture](SYNCHRONIZATION.md#sync-architecture) +- [Sync Protocol](SYNCHRONIZATION.md#sync-protocol) +- [Conflict Resolution](SYNCHRONIZATION.md#conflict-resolution) +- [Protected Notes Sync](SYNCHRONIZATION.md#protected-notes-sync) +- [WebSocket Sync](SYNCHRONIZATION.md#websocket-sync-updates) + +### Security +- [Authentication](SECURITY_ARCHITECTURE.md#authentication) +- [Encryption](SECURITY_ARCHITECTURE.md#encryption) +- [Input Sanitization](SECURITY_ARCHITECTURE.md#input-sanitization) +- [Network Security](SECURITY_ARCHITECTURE.md#network-security) +- [Security Best Practices](SECURITY_ARCHITECTURE.md#security-best-practices) + +### Scripting & Extensibility +- [Script Types](SCRIPTING.md#script-types) +- [Frontend API](SCRIPTING.md#frontend-api) +- [Backend API](SCRIPTING.md#backend-api) +- [Script Examples](SCRIPTING.md#script-examples) +- [Custom Widgets](SCRIPTING.md#render-scripts) + +### Frontend +- [Client Architecture](ARCHITECTURE.md#frontend-architecture) +- [Widget System](ARCHITECTURE.md#widget-based-ui) +- [Event System](ARCHITECTURE.md#event-system) +- [Froca Cache](ARCHITECTURE.md#2-froca-frontend-cache) +- [UI Components](ARCHITECTURE.md#ui-components) + +### Backend +- [Server Architecture](ARCHITECTURE.md#backend-architecture) +- [Service Layer](ARCHITECTURE.md#service-layer) +- [Route Structure](ARCHITECTURE.md#route-structure) +- [Becca Cache](ARCHITECTURE.md#1-becca-backend-cache) +- [Middleware](ARCHITECTURE.md#middleware) + +### Build & Deploy +- [Build System](ARCHITECTURE.md#build-system) +- [Package Manager](ARCHITECTURE.md#package-manager-pnpm) +- [Build Tools](ARCHITECTURE.md#build-tools) +- [Docker](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Docker.md) +- [Deployment](Developer%20Guide/Developer%20Guide/Building%20and%20deployment/) + +### Testing +- [Testing Strategy](ARCHITECTURE.md#testing-strategy) +- [Test Organization](ARCHITECTURE.md#test-organization) +- [E2E Testing](ARCHITECTURE.md#e2e-testing) + +--- + +## 📋 Reference Documentation + +### File Locations +``` +trilium/ +├── apps/ +│ ├── client/ # Frontend application +│ ├── server/ # Backend server +│ ├── desktop/ # Electron app +│ └── ... +├── packages/ +│ ├── commons/ # Shared code +│ ├── ckeditor5/ # Rich text editor +│ └── ... +├── docs/ +│ ├── ARCHITECTURE.md # Main architecture doc +│ ├── DATABASE.md # Database documentation +│ ├── SYNCHRONIZATION.md # Sync documentation +│ ├── SCRIPTING.md # Scripting guide +│ ├── SECURITY_ARCHITECTURE.md # Security documentation +│ ├── Developer Guide/ # Developer docs +│ ├── User Guide/ # User docs +│ └── Script API/ # API reference +└── ... +``` + +### Key Source Files +- **Backend Entry:** `apps/server/src/main.ts` +- **Frontend Entry:** `apps/client/src/desktop.ts` / `apps/client/src/index.ts` +- **Becca Cache:** `apps/server/src/becca/becca.ts` +- **Froca Cache:** `apps/client/src/services/froca.ts` +- **Database Schema:** `apps/server/src/assets/db/schema.sql` +- **Backend API:** `apps/server/src/services/backend_script_api.ts` +- **Frontend API:** `apps/client/src/services/frontend_script_api.ts` + +### Important Directories +- **Entities:** `apps/server/src/becca/entities/` +- **Widgets:** `apps/client/src/widgets/` +- **Services:** `apps/server/src/services/` +- **Routes:** `apps/server/src/routes/` +- **Migrations:** `apps/server/src/migrations/` +- **Tests:** Various `*.spec.ts` files throughout + +--- + +## 🎯 Common Tasks + +### Understanding the Codebase +1. Read [ARCHITECTURE.md](ARCHITECTURE.md) for overview +2. Explore [Monorepo Structure](ARCHITECTURE.md#monorepo-structure) +3. Review [Entity System](ARCHITECTURE.md#entity-system) +4. Check [Key Files](ARCHITECTURE.md#key-files-for-understanding-architecture) + +### Adding Features +1. Review relevant architecture documentation +2. Check [Developer Guide](Developer%20Guide/Developer%20Guide/) +3. Follow existing patterns in codebase +4. Write tests +5. Update documentation + +### Debugging Issues +1. Check [Troubleshooting](Developer%20Guide/Developer%20Guide/Troubleshooting/) +2. Review [Database](DATABASE.md) for data issues +3. Check [Synchronization](SYNCHRONIZATION.md) for sync issues +4. Review [Security](SECURITY_ARCHITECTURE.md) for auth issues + +### Performance Optimization +1. [Database Performance](DATABASE.md#performance-optimization) +2. [Cache Optimization](ARCHITECTURE.md#caching-system) +3. [Build Optimization](ARCHITECTURE.md#build-system) +4. [Script Performance](SCRIPTING.md#performance-considerations) + +--- + +## 🔗 External Resources + +### Official Links +- **Website:** https://triliumnotes.org +- **Documentation:** https://docs.triliumnotes.org +- **GitHub:** https://github.com/TriliumNext/Trilium +- **Discussions:** https://github.com/TriliumNext/Trilium/discussions +- **Matrix Chat:** https://matrix.to/#/#triliumnext:matrix.org + +### Community Resources +- **Awesome Trilium:** https://github.com/Nriver/awesome-trilium +- **TriliumRocks:** https://trilium.rocks/ +- **Wiki:** https://triliumnext.github.io/Docs/Wiki/ + +### Related Projects +- **TriliumDroid:** https://github.com/FliegendeWurst/TriliumDroid +- **Web Clipper:** Included in main repository + +--- + +## 📝 Documentation Conventions + +### Document Structure +- Overview section +- Table of contents +- Main content with headings +- Code examples where relevant +- "See Also" references + +### Code Examples +```typescript +// TypeScript examples with comments +const example = 'value' +``` + +```sql +-- SQL examples with formatting +SELECT * FROM notes WHERE noteId = ? +``` + +### Cross-References +- Use relative links: `[text](path/to/file.md)` +- Reference sections: `[text](file.md#section)` +- External links: Full URLs + +### Maintenance +- Review on major releases +- Update for architectural changes +- Add examples for new features +- Keep API references current + +--- + +## 🤝 Contributing to Documentation + +### What to Document +- New features and APIs +- Architecture changes +- Migration guides +- Performance tips +- Security considerations + +### How to Contribute +1. Edit markdown files in `docs/` +2. Follow existing structure and style +3. Include code examples +4. Test links and formatting +5. Submit pull request + +### Documentation Standards +- Clear, concise language +- Complete code examples +- Proper markdown formatting +- Cross-references to related docs +- Updated version numbers + +--- + +## 📅 Version Information + +- **Documentation Version:** 0.99.3 +- **Last Updated:** November 2025 +- **Trilium Version:** 0.99.3+ +- **Next Review:** When major architectural changes occur + +--- + +## 💡 Getting Help + +### For Users +- [User Guide](User%20Guide/User%20Guide/) +- [GitHub Discussions](https://github.com/TriliumNext/Trilium/discussions) +- [Matrix Chat](https://matrix.to/#/#triliumnext:matrix.org) + +### For Developers +- [Developer Guide](Developer%20Guide/Developer%20Guide/) +- [Architecture Docs](ARCHITECTURE.md) +- [GitHub Issues](https://github.com/TriliumNext/Trilium/issues) + +### For Contributors +- [Contributing Guidelines](../README.md#-contribute) +- [Code of Conduct](../CODE_OF_CONDUCT) +- [Developer Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) + +--- + +**Maintained by:** TriliumNext Team +**License:** AGPL-3.0-only +**Repository:** https://github.com/TriliumNext/Trilium From 07fe42d04e3cefc8a4fb9f6f63892199c9e947fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 22:01:17 +0000 Subject: [PATCH 3/8] Add quick reference guide for technical documentation Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com> --- docs/QUICK_REFERENCE.md | 155 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 docs/QUICK_REFERENCE.md diff --git a/docs/QUICK_REFERENCE.md b/docs/QUICK_REFERENCE.md new file mode 100644 index 000000000..396cc0dd6 --- /dev/null +++ b/docs/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Trilium Technical Documentation - Quick Reference + +> **Start here:** [TECHNICAL_DOCUMENTATION.md](TECHNICAL_DOCUMENTATION.md) - Complete index of all documentation + +## 📖 Documentation Files + +| Document | Description | Size | Lines | +|----------|-------------|------|-------| +| [TECHNICAL_DOCUMENTATION.md](TECHNICAL_DOCUMENTATION.md) | Main index and navigation hub | 13KB | 423 | +| [ARCHITECTURE.md](ARCHITECTURE.md) | Complete system architecture | 30KB | 1,016 | +| [DATABASE.md](DATABASE.md) | Database schema and operations | 19KB | 736 | +| [SYNCHRONIZATION.md](SYNCHRONIZATION.md) | Sync protocol and implementation | 14KB | 583 | +| [SCRIPTING.md](SCRIPTING.md) | User scripting system guide | 17KB | 734 | +| [SECURITY_ARCHITECTURE.md](SECURITY_ARCHITECTURE.md) | Security implementation details | 19KB | 834 | + +**Total:** 112KB of comprehensive documentation across 4,326 lines! + +## 🎯 Quick Access by Role + +### 👤 End Users +- **Getting Started:** [User Guide](User%20Guide/User%20Guide/) +- **Scripting:** [SCRIPTING.md](SCRIPTING.md) +- **Sync Setup:** [SYNCHRONIZATION.md](SYNCHRONIZATION.md) + +### 💻 Developers +- **Architecture:** [ARCHITECTURE.md](ARCHITECTURE.md) +- **Development Setup:** [Developer Guide](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) +- **Database:** [DATABASE.md](DATABASE.md) + +### 🔒 Security Auditors +- **Security:** [SECURITY_ARCHITECTURE.md](SECURITY_ARCHITECTURE.md) +- **Encryption:** [SECURITY_ARCHITECTURE.md#encryption](SECURITY_ARCHITECTURE.md#encryption) +- **Auth:** [SECURITY_ARCHITECTURE.md#authentication](SECURITY_ARCHITECTURE.md#authentication) + +### 🏗️ System Architects +- **Overall Design:** [ARCHITECTURE.md](ARCHITECTURE.md) +- **Cache System:** [ARCHITECTURE.md#three-layer-cache-system](ARCHITECTURE.md#three-layer-cache-system) +- **Entity Model:** [ARCHITECTURE.md#entity-system](ARCHITECTURE.md#entity-system) + +### 🔧 DevOps Engineers +- **Server Installation:** [User Guide - Server Installation](User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md) +- **Docker:** [Developer Guide - Docker](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Docker.md) +- **Sync Server:** [SYNCHRONIZATION.md#sync-server-configuration](SYNCHRONIZATION.md#sync-server-configuration) + +### 📊 Database Administrators +- **Schema:** [DATABASE.md#database-schema](DATABASE.md#database-schema) +- **Maintenance:** [DATABASE.md#database-maintenance](DATABASE.md#database-maintenance) +- **Performance:** [DATABASE.md#performance-optimization](DATABASE.md#performance-optimization) + +## 🔍 Quick Topic Finder + +### Core Concepts +- **Becca Cache:** [ARCHITECTURE.md#1-becca-backend-cache](ARCHITECTURE.md#1-becca-backend-cache) +- **Froca Cache:** [ARCHITECTURE.md#2-froca-frontend-cache](ARCHITECTURE.md#2-froca-frontend-cache) +- **Entity System:** [ARCHITECTURE.md#entity-system](ARCHITECTURE.md#entity-system) +- **Widget System:** [ARCHITECTURE.md#widget-based-ui](ARCHITECTURE.md#widget-based-ui) + +### Database +- **Schema Overview:** [DATABASE.md#schema-overview](DATABASE.md#schema-overview) +- **Notes Table:** [DATABASE.md#notes-table](DATABASE.md#notes-table) +- **Branches Table:** [DATABASE.md#branches-table](DATABASE.md#branches-table) +- **Migrations:** [DATABASE.md#database-migrations](DATABASE.md#database-migrations) + +### Synchronization +- **Sync Protocol:** [SYNCHRONIZATION.md#sync-protocol](SYNCHRONIZATION.md#sync-protocol) +- **Conflict Resolution:** [SYNCHRONIZATION.md#conflict-resolution](SYNCHRONIZATION.md#conflict-resolution) +- **Entity Changes:** [SYNCHRONIZATION.md#entity-changes](SYNCHRONIZATION.md#entity-changes) + +### Scripting +- **Frontend Scripts:** [SCRIPTING.md#frontend-scripts](SCRIPTING.md#frontend-scripts) +- **Backend Scripts:** [SCRIPTING.md#backend-scripts](SCRIPTING.md#backend-scripts) +- **Script Examples:** [SCRIPTING.md#script-examples](SCRIPTING.md#script-examples) +- **API Reference:** [SCRIPTING.md#script-api](SCRIPTING.md#script-api) + +### Security +- **Authentication:** [SECURITY_ARCHITECTURE.md#authentication](SECURITY_ARCHITECTURE.md#authentication) +- **Encryption:** [SECURITY_ARCHITECTURE.md#encryption](SECURITY_ARCHITECTURE.md#encryption) +- **Input Sanitization:** [SECURITY_ARCHITECTURE.md#input-sanitization](SECURITY_ARCHITECTURE.md#input-sanitization) +- **Best Practices:** [SECURITY_ARCHITECTURE.md#security-best-practices](SECURITY_ARCHITECTURE.md#security-best-practices) + +## 📚 Learning Paths + +### New to Trilium Development +1. Read [ARCHITECTURE.md](ARCHITECTURE.md) - System overview +2. Setup environment: [Environment Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) +3. Explore [DATABASE.md](DATABASE.md) - Understand data model +4. Check [Developer Guide](Developer%20Guide/Developer%20Guide/) + +### Want to Create Scripts +1. Read [SCRIPTING.md](SCRIPTING.md) - Complete guide +2. Check [Script API](Script%20API/) - API reference +3. Review examples: [SCRIPTING.md#script-examples](SCRIPTING.md#script-examples) +4. Explore [Advanced Showcases](https://triliumnext.github.io/Docs/Wiki/advanced-showcases) + +### Setting Up Sync +1. Understand protocol: [SYNCHRONIZATION.md](SYNCHRONIZATION.md) +2. Configure server: [SYNCHRONIZATION.md#sync-server-configuration](SYNCHRONIZATION.md#sync-server-configuration) +3. Setup clients: [SYNCHRONIZATION.md#client-setup](SYNCHRONIZATION.md#client-setup) +4. Troubleshoot: [SYNCHRONIZATION.md#troubleshooting](SYNCHRONIZATION.md#troubleshooting) + +### Security Review +1. Read threat model: [SECURITY_ARCHITECTURE.md#threat-model](SECURITY_ARCHITECTURE.md#threat-model) +2. Review authentication: [SECURITY_ARCHITECTURE.md#authentication](SECURITY_ARCHITECTURE.md#authentication) +3. Check encryption: [SECURITY_ARCHITECTURE.md#encryption](SECURITY_ARCHITECTURE.md#encryption) +4. Verify best practices: [SECURITY_ARCHITECTURE.md#security-best-practices](SECURITY_ARCHITECTURE.md#security-best-practices) + +## 🗺️ Documentation Map + +``` +docs/ +├── TECHNICAL_DOCUMENTATION.md ← START HERE (Index) +│ +├── Core Technical Docs +│ ├── ARCHITECTURE.md (System design) +│ ├── DATABASE.md (Data layer) +│ ├── SYNCHRONIZATION.md (Sync system) +│ ├── SCRIPTING.md (User scripting) +│ └── SECURITY_ARCHITECTURE.md (Security) +│ +├── Developer Guide/ +│ └── Developer Guide/ (Development setup) +│ +├── User Guide/ +│ └── User Guide/ (End-user docs) +│ +└── Script API/ (API reference) +``` + +## 💡 Tips for Reading Documentation + +1. **Start with the index:** [TECHNICAL_DOCUMENTATION.md](TECHNICAL_DOCUMENTATION.md) provides an overview +2. **Use search:** Press Ctrl+F / Cmd+F to find specific topics +3. **Follow links:** Documents are cross-referenced for easy navigation +4. **Code examples:** Most docs include practical code examples +5. **See Also sections:** Check bottom of each doc for related resources + +## 🔗 External Resources + +- **Website:** https://triliumnotes.org +- **Online Docs:** https://docs.triliumnotes.org +- **GitHub:** https://github.com/TriliumNext/Trilium +- **Discussions:** https://github.com/TriliumNext/Trilium/discussions +- **Matrix Chat:** https://matrix.to/#/#triliumnext:matrix.org + +## 🤝 Contributing to Documentation + +Found an error or want to improve the docs? See: +- [Contributing Guide](../README.md#-contribute) +- [Documentation Standards](TECHNICAL_DOCUMENTATION.md#documentation-conventions) + +--- + +**Version:** 0.99.3 +**Last Updated:** November 2025 +**Maintained by:** TriliumNext Team From 0494032fb5184048e68c31b9e8d00da65295c79a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:35:56 +0000 Subject: [PATCH 4/8] Convert ASCII diagrams to Mermaid.js format Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com> --- docs/ARCHITECTURE.md | 125 +++++++++++++++++++++------------------- docs/DATABASE.md | 42 +++++++------- docs/SYNCHRONIZATION.md | 36 +++++------- 3 files changed, 99 insertions(+), 104 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 44d4f99fd..53f35dbd2 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -53,30 +53,30 @@ Trilium Notes is a hierarchical note-taking application built as a TypeScript mo Trilium follows a **client-server architecture** even in desktop mode, where Electron runs both the backend server and frontend client within the same process. -``` -┌─────────────────────────────────────────────────────────────┐ -│ Frontend │ -│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ -│ │ Widgets │ │ Froca │ │ UI │ │ -│ │ System │ │ Cache │ │ Services │ │ -│ └────────────┘ └────────────┘ └────────────┘ │ -│ │ │ -│ WebSocket / REST API │ -│ │ │ -└─────────────────────────┼────────────────────────────────────┘ - │ -┌─────────────────────────┼────────────────────────────────────┐ -│ Backend Server │ -│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ -│ │ Express │ │ Becca │ │ Script │ │ -│ │ Routes │ │ Cache │ │ Engine │ │ -│ └────────────┘ └────────────┘ └────────────┘ │ -│ │ │ -│ ┌────┴─────┐ │ -│ │ SQLite │ │ -│ │ Database │ │ -│ └──────────┘ │ -└─────────────────────────────────────────────────────────────┘ +```mermaid +graph TB + subgraph Frontend + Widgets[Widgets
System] + Froca[Froca
Cache] + UIServices[UI
Services] + end + + subgraph Backend["Backend Server"] + Express[Express
Routes] + Becca[Becca
Cache] + ScriptEngine[Script
Engine] + Database[(SQLite
Database)] + end + + Widgets -.-> API[WebSocket / REST API] + Froca -.-> API + UIServices -.-> API + API -.-> Express + API -.-> Becca + API -.-> ScriptEngine + Becca --> Database + Express --> Database + ScriptEngine --> Database ``` ### Deployment Modes @@ -225,30 +225,24 @@ Located at: `apps/server/src/share/` Trilium's data model is based on five core entities: -``` -┌──────────────────────────────────────────────────────────┐ -│ Note Tree │ -│ │ -│ ┌─────────┐ │ -│ │ Note │ │ -│ │ (BNote) │ │ -│ └────┬────┘ │ -│ │ │ -│ │ linked by │ -│ ▼ │ -│ ┌──────────┐ ┌─────────────┐ │ -│ │ Branch │◄────────│ Attribute │ │ -│ │(BBranch) │ │ (BAttribute)│ │ -│ └──────────┘ └─────────────┘ │ -│ │ │ -│ │ creates │ -│ ▼ │ -│ ┌──────────┐ ┌─────────────┐ │ -│ │ Revision │ │ Attachment │ │ -│ │(BRevision│ │(BAttachment)│ │ -│ └──────────┘ └─────────────┘ │ -│ │ -└──────────────────────────────────────────────────────────┘ +```mermaid +graph TD + Note[Note
BNote] + Branch[Branch
BBranch] + Attribute[Attribute
BAttribute] + Revision[Revision
BRevision] + Attachment[Attachment
BAttachment] + + Note -->|linked by| Branch + Note -.->|metadata| Attribute + Branch -->|creates| Revision + Note -->|has| Attachment + + style Note fill:#e1f5ff + style Branch fill:#fff4e1 + style Attribute fill:#ffe1f5 + style Revision fill:#f5ffe1 + style Attachment fill:#ffe1e1 ``` #### Entity Definitions @@ -514,18 +508,29 @@ Key services: ### UI Components **Main Layout:** -``` -┌──────────────────────────────────────────────────────┐ -│ Title Bar │ -├──────────┬────────────────────────┬──────────────────┤ -│ │ │ │ -│ Note │ Note Detail │ Right Panel │ -│ Tree │ Editor │ (Info, Links) │ -│ │ │ │ -│ │ │ │ -├──────────┴────────────────────────┴──────────────────┤ -│ Status Bar │ -└──────────────────────────────────────────────────────┘ + +```mermaid +graph TD + subgraph TriliumUI[" "] + TitleBar[Title Bar] + + subgraph MainArea[" "] + NoteTree[Note Tree] + NoteDetail[Note Detail
Editor] + RightPanel[Right Panel
Info, Links] + end + + StatusBar[Status Bar] + end + + TitleBar -.-> MainArea + MainArea -.-> StatusBar + + style TitleBar fill:#e1f5ff + style NoteTree fill:#fff4e1 + style NoteDetail fill:#f5ffe1 + style RightPanel fill:#ffe1f5 + style StatusBar fill:#e1f5ff ``` **Component Locations:** diff --git a/docs/DATABASE.md b/docs/DATABASE.md index d02452f21..52df42e0b 100644 --- a/docs/DATABASE.md +++ b/docs/DATABASE.md @@ -427,27 +427,27 @@ CREATE TABLE etapi_tokens ( ## Data Relationships -``` - ┌──────────────┐ - │ Notes │ - └───┬──────────┘ - │ - ┌───────────┼───────────┐ - │ │ │ - ▼ ▼ ▼ - ┌────────┐ ┌──────────┐ ┌───────────┐ - │Branches│ │Attributes│ │Attachments│ - └────────┘ └──────────┘ └─────┬─────┘ - │ │ - │ │ - │ ┌──────────┐ │ - └──────▶│ Blobs │◀────────┘ - └──────────┘ - ▲ - │ - ┌────┴─────┐ - │Revisions │ - └──────────┘ +```mermaid +graph TB + Notes[Notes] + Branches[Branches] + Attributes[Attributes] + Attachments[Attachments] + Blobs[(Blobs)] + Revisions[Revisions] + + Notes --> Branches + Notes --> Attributes + Notes --> Attachments + Notes --> Blobs + Notes --> Revisions + + Branches --> Blobs + Attachments --> Blobs + Revisions --> Blobs + + style Notes fill:#e1f5ff + style Blobs fill:#ffe1e1 ``` **Relationships:** diff --git a/docs/SYNCHRONIZATION.md b/docs/SYNCHRONIZATION.md index 2f39eee40..0945e533a 100644 --- a/docs/SYNCHRONIZATION.md +++ b/docs/SYNCHRONIZATION.md @@ -14,29 +14,19 @@ Trilium implements a sophisticated **bidirectional synchronization system** that ## Sync Architecture -``` -┌─────────────┐ ┌─────────────┐ -│ Desktop 1 │ │ Desktop 2 │ -│ (Client) │ │ (Client) │ -└──────┬──────┘ └──────┬──────┘ - │ │ - │ WebSocket/HTTP │ - │ │ - ▼ ▼ -┌────────────────────────────────────────────────┐ -│ Sync Server │ -│ ┌──────────────────────────────────────┐ │ -│ │ Sync Service │ │ -│ │ - Entity Change Management │ │ -│ │ - Conflict Resolution │ │ -│ │ - Version Tracking │ │ -│ └──────────────────────────────────────┘ │ -│ │ │ -│ ┌──────┴───────┐ │ -│ │ Database │ │ -│ │ (entity_changes)│ │ -│ └──────────────┘ │ -└────────────────────────────────────────────────┘ +```mermaid +graph TB + Desktop1[Desktop 1
Client] + Desktop2[Desktop 2
Client] + + subgraph SyncServer["Sync Server"] + SyncService[Sync Service
- Entity Change Management
- Conflict Resolution
- Version Tracking] + SyncDB[(Database
entity_changes)] + end + + Desktop1 <-->|WebSocket/HTTP| SyncService + Desktop2 <-->|WebSocket/HTTP| SyncService + SyncService --> SyncDB ``` ## Core Concepts From 58ac3256344c4baabc3a1b3d095ddd9fe8fea99f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 4 Nov 2025 15:51:54 +0200 Subject: [PATCH 5/8] docs(dev): integrate some of the architecture notes --- .../en/User Guide/User Guide/AI.html | 2 +- .../User Guide/Advanced Usage/Attributes.html | 4 +- .../Custom Request Handler.html | 8 +- .../Navigation/Search.html | 2 +- .../Basic Concepts and Features/Notes.html | 2 +- .../UI Elements/Ribbon.html | 2 +- .../en/User Guide/User Guide/Collections.html | 6 +- .../Desktop Installation.html | 2 +- .../Server Installation.html | 2 +- .../en/User Guide/User Guide/Note Types.html | 4 +- .../en/User Guide/User Guide/Scripting.html | 6 +- .../Custom Widgets/Widget Basics.html | 21 +- .../User Guide/Troubleshooting.html | 5 +- docs/ARCHITECTURE.md | 742 ---------- docs/Developer Guide/!!!meta.json | 1314 ++++++++++------- .../Developer Guide/Architecture.md | 118 ++ .../Developer Guide/Architecture/API.md | 72 + .../Client-server architecture/Backend.md | 88 ++ .../Client-server architecture/Frontend.md | 61 + .../Developer Guide/Architecture/Database.md | 40 + .../Database structure/attachments.md | 4 +- .../Database structure/attributes.md | 2 +- .../Database structure/blobs.md | 0 .../Database structure/branches.md | 4 +- .../Database structure/entity_changes.md | 0 .../Database structure/etapi_tokens.md | 2 +- .../Database structure/notes.md | 4 +- .../Database structure/options.md | 0 .../Database structure/recent_notes.md | 0 .../Database structure/revisions.md | 2 +- .../Architecture/Protected entities.md | 6 - .../{Architecture => Concepts}/Backlinks.md | 0 .../Branch prefixes.md | 0 .../CI/1_Main_image.png | Bin .../{Architecture => Concepts}/CI/Main.md | 0 .../CI/Main_image.png | Bin .../Developer Guide/Concepts/Cache.md | 111 ++ .../Deleted notes.md | 0 .../Demo document.md | 0 .../Developer Guide/Concepts/Entities.md | 109 ++ .../Hidden notes.md | 0 .../{Architecture => Concepts}/Icons.md | 0 .../Internationalisation Translat.md | 0 .../Guidelines.md | 0 .../Server translations.md | 0 .../i18n-ally.md | 0 .../{Architecture => Concepts}/Launchers.md | 0 .../Note Revisions.md | 0 .../Copy image reference to the cl.md | 0 .../Export diagram as SVG.md | 0 .../Adding a new note type/First steps.md | 0 .../First steps/mind_map.js | 0 .../Adding a new note type/Loading data.md | 0 .../Note type checklist.md | 0 .../Adding a new note type/SVG rendering.md | 0 .../Saving data via spaced update.md | 0 .../{Architecture => Concepts}/Options.md | 0 .../Options/Creating a new option.md | 0 .../Printing and exporting to PDF.md | 0 .../Concepts/Protected entities.md | 6 + .../{Architecture => Concepts}/Share.md | 0 .../Synchronisation/Content hashing.md | 0 .../Syntax highlighting.md | 0 .../{Architecture => Concepts}/Themes.md | 0 .../Developer Guide/Documentation.md | 2 +- .../Developer Guide/Environment Setup.md | 2 +- docs/User Guide/!!!meta.json | 26 +- 67 files changed, 1428 insertions(+), 1353 deletions(-) create mode 100644 docs/Developer Guide/Developer Guide/Architecture.md create mode 100644 docs/Developer Guide/Developer Guide/Architecture/API.md create mode 100644 docs/Developer Guide/Developer Guide/Architecture/Client-server architecture/Backend.md create mode 100644 docs/Developer Guide/Developer Guide/Architecture/Client-server architecture/Frontend.md create mode 100644 docs/Developer Guide/Developer Guide/Architecture/Database.md rename docs/Developer Guide/Developer Guide/Architecture/{ => Database}/Database structure/attachments.md (90%) rename docs/Developer Guide/Developer Guide/Architecture/{ => Database}/Database structure/attributes.md (84%) rename docs/Developer Guide/Developer Guide/Architecture/{ => Database}/Database structure/blobs.md (100%) rename docs/Developer Guide/Developer Guide/Architecture/{ => Database}/Database structure/branches.md (82%) rename docs/Developer Guide/Developer Guide/Architecture/{ => Database}/Database structure/entity_changes.md (100%) rename docs/Developer Guide/Developer Guide/Architecture/{ => Database}/Database structure/etapi_tokens.md (90%) rename docs/Developer Guide/Developer Guide/Architecture/{ => Database}/Database structure/notes.md (90%) rename docs/Developer Guide/Developer Guide/Architecture/{ => Database}/Database structure/options.md (100%) rename docs/Developer Guide/Developer Guide/Architecture/{ => Database}/Database structure/recent_notes.md (100%) rename docs/Developer Guide/Developer Guide/Architecture/{ => Database}/Database structure/revisions.md (94%) delete mode 100644 docs/Developer Guide/Developer Guide/Architecture/Protected entities.md rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Backlinks.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Branch prefixes.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/CI/1_Main_image.png (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/CI/Main.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/CI/Main_image.png (100%) create mode 100644 docs/Developer Guide/Developer Guide/Concepts/Cache.md rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Deleted notes.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Demo document.md (100%) create mode 100644 docs/Developer Guide/Developer Guide/Concepts/Entities.md rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Hidden notes.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Icons.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Internationalisation Translat.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Internationalisation Translations/Guidelines.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Internationalisation Translations/Server translations.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Internationalisation Translations/i18n-ally.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Launchers.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Note Revisions.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Note Types/Adding a new note type/Copy image reference to the cl.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Note Types/Adding a new note type/Export diagram as SVG.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Note Types/Adding a new note type/First steps.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Note Types/Adding a new note type/First steps/mind_map.js (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Note Types/Adding a new note type/Loading data.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Note Types/Adding a new note type/Note type checklist.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Note Types/Adding a new note type/SVG rendering.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Note Types/Adding a new note type/Saving data via spaced update.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Options.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Options/Creating a new option.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Printing and exporting to PDF.md (100%) create mode 100644 docs/Developer Guide/Developer Guide/Concepts/Protected entities.md rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Share.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Synchronisation/Content hashing.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Syntax highlighting.md (100%) rename docs/Developer Guide/Developer Guide/{Architecture => Concepts}/Themes.md (100%) diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI.html index e94560b68..0fc11a28d 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI.html @@ -36,7 +36,7 @@ class="image image_resized" style="width:74.04%;">

To see what embedding models Ollama has available, you can check out this searchon their website, and then pull whichever one - you want to try out. As of 4/15/25, my personal favorite is mxbai-embed-large.

+ you want to try out. A popular choice is mxbai-embed-large.

First, we'll need to select the Ollama provider from the tabs of providers, then we will enter in the Base URL for our Ollama. Since our Ollama is running on our local machine, our Base URL is http://localhost:11434. diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes.html index 8f28f72f4..d46a3e51c 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes.html @@ -8,7 +8,7 @@

  • Labels can be used for a variety of purposes, such as storing metadata or configuring - the behaviour of notes. Labels are also searchable, enhancing note retrieval.

    + the behavior of notes. Labels are also searchable, enhancing note retrieval.

    For more information, including predefined labels, see Labels.

  • @@ -21,7 +21,7 @@ class="reference-link" href="#root/_help_Cq5X6iKQop6R">Relations.

    -

    These attributes play a crucial role in organizing, categorising, and +

    These attributes play a crucial role in organizing, categorizing, and enhancing the functionality of notes.

    Viewing the list of attributes

    Both the labels and relations for the current note are displayed in the Owned Attributes section diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Custom Request Handler.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Custom Request Handler.html index d7068f7eb..196996eed 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Custom Request Handler.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Custom Request Handler.html @@ -11,7 +11,7 @@ const {secret, title, content} = req.body; if (req.method == 'POST' && secret === 'secret-password') { // notes must be saved somewhere in the tree hierarchy specified by a parent note. // This is defined by a relation from this code note to the "target" parent note - // alternetively you can just use constant noteId for simplicity (get that from "Note Info" dialog of the desired parent note) + // alternatively you can just use constant noteId for simplicity (get that from "Note Info" dialog of the desired parent note) const targetParentNoteId = api.currentNote.getRelationValue('targetNote'); const {note} = api.createTextNote(targetParentNoteId, title, content); @@ -30,7 +30,7 @@ else { be saved

    Explanation

    -

    Let's test this by using an HTTP client to send a request:

    POST http://my.trilium.org/custom/create-note
    +

    Let's test this by using an HTTP client to send a request:

    POST http://your-trilium-server/custom/create-note
     Content-Type: application/json
     
     {
    @@ -64,12 +64,12 @@ Content-Type: application/json
       can always look into its documentation for
       details.

    Parameters

    -

    REST request paths often contain parameters in the URL, e.g.:

    http://my.trilium.org/custom/notes/123
    +

    REST request paths often contain parameters in the URL, e.g.:

    http://your-trilium-server/custom/notes/123

    The last part is dynamic so the matching of the URL must also be dynamic - for this reason the matching is done with regular expressions. Following customRequestHandler value would match it:

    notes/([0-9]+)

    Additionally, this also defines a matching group with the use of parenthesis which then makes it easier to extract the value. The matched groups are available in api.pathParams:

    const noteId = api.pathParams[0];
    -

    Often you also need query params (as in e.g. http://my.trilium.org/custom/notes?noteId=123), +

    Often you also need query params (as in e.g. http://your-trilium-server/custom/notes?noteId=123), you can get those with standard express req.query.noteId.

    \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Navigation/Search.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Navigation/Search.html index 5a93064e8..0911a8b6c 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Navigation/Search.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Navigation/Search.html @@ -21,7 +21,7 @@
    1. Set the text to search for in the Search string field.
        -
      1. Apart from searching for words ad-literam, there is also the possibility +
      2. Apart from searching for words literally, there is also the possibility to search for attributes or properties of notes.
      3. See the examples below for more information.
      diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes.html index 26efd0947..42e484e70 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes.html @@ -31,7 +31,7 @@ and you will see a list of all modified notes including the deleted ones. Notes available for undeletion have a link to do so. This is kind of "trash can" functionality known from e.g. Windows.

      -

      Clicking an undelete will recover the note, it's content and attributes +

      Clicking an undelete will recover the note, its content and attributes - note should be just as before being deleted. This action will also undelete note's children which have been deleted in the same action.

      To be able to undelete a note, it is necessary that deleted note's parent diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Ribbon.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Ribbon.html index 96db62c4b..e23f83d72 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Ribbon.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Ribbon.html @@ -29,7 +29,7 @@

    2. Editable changes whether the current note:
      • Enters read-only mode automatically if - the note is too big (default behaviour).
      • + the note is too big (default behavior).
      • Is always in read-only mode (however it can still be edited temporarily).
      • Is always editable, regardless of its size.
      diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections.html index fa40dcfbd..3aaad0a7e 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections.html @@ -1,5 +1,5 @@ -

      Collections are a unique type of notes that don't have a content, but - instead display its child notes in various presentation methods.

      +

      Collections are a unique type of note that don't have content, but instead + display their child notes in various presentation methods.

      Main collections

      @@ -94,7 +94,7 @@ in the Ribbon.

      Archived notes

      By default, archived notes will not be - shown in collections. This behaviour can be changed by going to Collection Properties in + shown in collections. This behavior can be changed by going to Collection Properties in the Ribbon and checking Show archived notes.

      Archived notes will be generally indicated by being greyed out as opposed diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Desktop Installation.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Desktop Installation.html index b213daf6e..5adf8dbe1 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Desktop Installation.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Desktop Installation.html @@ -27,6 +27,6 @@ any startup scripts that might cause the application to crash.

      Synchronization

      -

      For Trilium desktp users who wish to synchronize their data with a server +

      For Trilium desktop users who wish to synchronize their data with a server instance, refer to the Synchronization guide for detailed instructions.

      \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Server Installation.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Server Installation.html index 0c34a40ae..dfaa68a0d 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Server Installation.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Server Installation.html @@ -40,7 +40,7 @@

      Disabling / Modifying the Upload Limit

      If you're running into the 250MB limit imposed on the server by default, and you'd like to increase the upload limit, you can set the TRILIUM_NO_UPLOAD_LIMIT environment - variable to true disable it completely:

      export TRILIUM_NO_UPLOAD_LIMIT=true 
      + variable to true to disable it completely:

      export TRILIUM_NO_UPLOAD_LIMIT=true 

      Or, if you'd simply like to increase the upload limit size to something beyond 250MB, you can set the MAX_ALLOWED_FILE_SIZE_MB environment variable to something larger than the integer 250 (e.g. 450 in diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types.html index 9c69cade3..1369ccfa6 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types.html @@ -1,5 +1,5 @@ -

      One core features of Trilium is that it supports multiple types of notes, - depending on the need.

      +

      One of the core features of Trilium is that it supports multiple types + of notes, depending on the need.

      Creating a new note with a different type via the note tree

      The default note type in Trilium (e.g. when creating a new note) is  Script API provide extra functionality.

      -

      Scripting

      +

      Architecture Overview

      To go further I must explain basic architecture of Trilium - in its essence it is a classic web application - it has these two main components:

        @@ -14,8 +14,8 @@

      So we have frontend and backend, each with their own set of responsibilities, but their common feature is that they both run JavaScript code. Add to - this the fact, that we're able to create JavaScript [[code notes]] and - we're onto something.

      + this the fact, that we're able to create JavaScript code notes and we're onto something.

      Use cases

      • "New Task" launcher button diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Widget Basics.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Widget Basics.html index 57a0a834b..84a15c29e 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Widget Basics.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Widget Basics.html @@ -16,11 +16,11 @@ module.exports = new MyWidget();

        To implement this widget:

          -
        1. Create a new JS Frontend note in Trilium and paste in the code +
        2. Create a new JS Frontend note in Trilium and paste in the code above.
        3. -
        4. Assign the #widget attribute to +
        5. Assign the #widget attribute to the note.
        6. -
        7. Restart Trilium or reload the window.
        8. +
        9. Restart Trilium or reload the window.

        To verify that the widget is working, open the developer tools (Cmd + Shift + I) and run document.querySelector("#my-widget"). If the element @@ -89,16 +89,15 @@ module.exports = new MyWidget(); module.exports = new MyWidget();

        parentWidget() can be given the following values:

          -
        • left-pane - This renders the widget on the left side of the +
        • left-pane - This renders the widget on the left side of the screen where the note tree lives.
        • -
        • center-pane - This renders the widget in the center of the +
        • center-pane - This renders the widget in the center of the layout in the same location that notes and splits appear.
        • -
        • note-detail-pane - This renders the widget with the +
        • note-detail-pane - This renders the widget with the note in the center pane. This means it can appear multiple times with splits.
        • -
        • right-pane - This renders the widget to the right of any opened +
        • right-pane - This renders the widget to the right of any opened notes.
        -

        Reload the application - one last time. When you click the button, a "Hello World!" message should - appear, confirming that your widget is fully functional.

        \ No newline at end of file +

        Reload the application one last time. + When you click the button, a "Hello World!" message should appear, confirming + that your widget is fully functional.

        \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Troubleshooting.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Troubleshooting.html index cc53090a9..11b6cdc3c 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Troubleshooting.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Troubleshooting.html @@ -1,4 +1,5 @@ -

        As Trilium is currently in beta, encountering bugs is to be expected.

        +

        While Trilium is actively maintained and stable, encountering bugs is + possible.

        General Quick Fix

        The first step in troubleshooting is often a restart.

        If you experience an UI issue, the frontend may have entered an inconsistent @@ -15,7 +16,7 @@ variable to reset the open tabs to a single specified note ID (e.g., root). In Linux, you can set it as follows:

        TRILIUM_START_NOTE_ID=root ./trilium

        Broken Script Prevents Application Startup

        -

        If a custom script causes Triliumto crash, and it is set as a startup +

        If a custom script causes Trilium to crash, and it is set as a startup script or in an active custom widget, start Triliumin "safe mode" to prevent any custom scripts from executing:

        TRILIUM_SAFE_MODE=true ./trilium

        Depending on your Trilium distribution, you may have pre-made scripts diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 53f35dbd2..1c1d6638b 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,741 +1,5 @@ -# Trilium Notes - Technical Architecture Documentation - -> **Version:** 0.99.3 -> **Last Updated:** November 2025 -> **Maintainer:** TriliumNext Team - -## Table of Contents - -1. [Introduction](#introduction) -2. [High-Level Architecture](#high-level-architecture) -3. [Monorepo Structure](#monorepo-structure) -4. [Core Architecture Patterns](#core-architecture-patterns) -5. [Data Layer](#data-layer) -6. [Caching System](#caching-system) -7. [Frontend Architecture](#frontend-architecture) -8. [Backend Architecture](#backend-architecture) -9. [API Architecture](#api-architecture) -10. [Build System](#build-system) -11. [Testing Strategy](#testing-strategy) -12. [Security Architecture](#security-architecture) -13. [Related Documentation](#related-documentation) - ---- - -## Introduction - -Trilium Notes is a hierarchical note-taking application built as a TypeScript monorepo. It supports multiple deployment modes (desktop, server, mobile web) and features advanced capabilities including synchronization, scripting, encryption, and rich content editing. - -### Key Characteristics - -- **Monorepo Architecture**: Uses pnpm workspaces for dependency management -- **Multi-Platform**: Desktop (Electron), Server (Node.js/Express), and Mobile Web -- **TypeScript-First**: Strong typing throughout the codebase -- **Plugin-Based**: Extensible architecture for note types and UI components -- **Offline-First**: Full functionality without network connectivity -- **Synchronization-Ready**: Built-in sync protocol for multi-device usage - -### Technology Stack - -- **Runtime**: Node.js (backend), Browser/Electron (frontend) -- **Language**: TypeScript, JavaScript -- **Database**: SQLite (better-sqlite3) -- **Build Tools**: Vite, ESBuild, pnpm -- **UI Framework**: Custom widget-based system -- **Rich Text**: CKEditor 5 (customized) -- **Code Editing**: CodeMirror 6 -- **Desktop**: Electron -- **Server**: Express.js - ---- - -## High-Level Architecture - -Trilium follows a **client-server architecture** even in desktop mode, where Electron runs both the backend server and frontend client within the same process. - -```mermaid -graph TB - subgraph Frontend - Widgets[Widgets
        System] - Froca[Froca
        Cache] - UIServices[UI
        Services] - end - - subgraph Backend["Backend Server"] - Express[Express
        Routes] - Becca[Becca
        Cache] - ScriptEngine[Script
        Engine] - Database[(SQLite
        Database)] - end - - Widgets -.-> API[WebSocket / REST API] - Froca -.-> API - UIServices -.-> API - API -.-> Express - API -.-> Becca - API -.-> ScriptEngine - Becca --> Database - Express --> Database - ScriptEngine --> Database -``` - -### Deployment Modes - -1. **Desktop Application** - - Electron wrapper running both frontend and backend - - Local SQLite database - - Full offline functionality - - Cross-platform (Windows, macOS, Linux) - -2. **Server Installation** - - Node.js server exposing web interface - - Multi-user capable - - Can sync with desktop clients - - Docker deployment supported - -3. **Mobile Web** - - Optimized responsive interface - - Accessed via browser - - Requires server installation - ---- - -## Monorepo Structure - -Trilium uses **pnpm workspaces** to manage its monorepo structure, with apps and packages clearly separated. - -``` -trilium/ -├── apps/ # Runnable applications -│ ├── client/ # Frontend application (shared by server & desktop) -│ ├── server/ # Node.js server with web interface -│ ├── desktop/ # Electron desktop application -│ ├── web-clipper/ # Browser extension for web content capture -│ ├── db-compare/ # Database comparison tool -│ ├── dump-db/ # Database export tool -│ ├── edit-docs/ # Documentation editing tool -│ ├── build-docs/ # Documentation build tool -│ └── website/ # Marketing website -│ -├── packages/ # Shared libraries -│ ├── commons/ # Shared interfaces and utilities -│ ├── ckeditor5/ # Custom rich text editor -│ ├── codemirror/ # Code editor customizations -│ ├── highlightjs/ # Syntax highlighting -│ ├── ckeditor5-admonition/ # CKEditor plugin: admonitions -│ ├── ckeditor5-footnotes/ # CKEditor plugin: footnotes -│ ├── ckeditor5-keyboard-marker/# CKEditor plugin: keyboard shortcuts -│ ├── ckeditor5-math/ # CKEditor plugin: math equations -│ ├── ckeditor5-mermaid/ # CKEditor plugin: diagrams -│ ├── express-partial-content/ # HTTP partial content middleware -│ ├── share-theme/ # Shared note theme -│ ├── splitjs/ # Split pane library -│ └── turndown-plugin-gfm/ # Markdown conversion -│ -├── docs/ # Documentation -├── scripts/ # Build and utility scripts -└── patches/ # Package patches (via pnpm) -``` - -### Package Dependencies - -The monorepo uses workspace protocol (`workspace:*`) for internal dependencies: - -``` -desktop → client → commons -server → client → commons -client → ckeditor5, codemirror, highlightjs -ckeditor5 → ckeditor5-* plugins -``` - ---- - -## Core Architecture Patterns - -### Three-Layer Cache System - -Trilium implements a sophisticated **three-tier caching system** to optimize performance and enable offline functionality: - -#### 1. Becca (Backend Cache) - -Located at: `apps/server/src/becca/` - -```typescript -// Becca caches all entities in memory -class Becca { - notes: Record - branches: Record - attributes: Record - attachments: Record - // ... other entity collections -} -``` - -**Responsibilities:** -- Server-side entity cache -- Maintains complete note tree in memory -- Handles entity relationships and integrity -- Provides fast lookups without database queries -- Manages entity lifecycle (create, update, delete) - -**Key Files:** -- `becca.ts` - Main cache instance -- `becca_loader.ts` - Loads entities from database -- `becca_service.ts` - Cache management operations -- `entities/` - Entity classes (BNote, BBranch, etc.) - -#### 2. Froca (Frontend Cache) - -Located at: `apps/client/src/services/froca.ts` - -```typescript -// Froca is a read-only mirror of backend data -class Froca { - notes: Record - branches: Record - attributes: Record - // ... other entity collections -} -``` - -**Responsibilities:** -- Frontend read-only cache -- Lazy loading of note tree -- Minimizes API calls -- Enables fast UI rendering -- Synchronizes with backend via WebSocket - -**Loading Strategy:** -- Initial load: root notes and immediate children -- Lazy load: notes loaded when accessed -- When note is loaded, all parent and child branches load -- Deleted entities tracked via missing branches - -#### 3. Shaca (Share Cache) - -Located at: `apps/server/src/share/` - -**Responsibilities:** -- Optimized cache for shared/published notes -- Handles public note access without authentication -- Performance-optimized for high-traffic scenarios -- Separate from main Becca to isolate concerns - -### Entity System - -Trilium's data model is based on five core entities: - -```mermaid -graph TD - Note[Note
        BNote] - Branch[Branch
        BBranch] - Attribute[Attribute
        BAttribute] - Revision[Revision
        BRevision] - Attachment[Attachment
        BAttachment] - - Note -->|linked by| Branch - Note -.->|metadata| Attribute - Branch -->|creates| Revision - Note -->|has| Attachment - - style Note fill:#e1f5ff - style Branch fill:#fff4e1 - style Attribute fill:#ffe1f5 - style Revision fill:#f5ffe1 - style Attachment fill:#ffe1e1 -``` - -#### Entity Definitions - -**1. BNote** (`apps/server/src/becca/entities/bnote.ts`) -- Represents a note with title, content, and metadata -- Type can be: text, code, file, image, canvas, mermaid, etc. -- Contains content via blob reference -- Can be protected (encrypted) -- Has creation and modification timestamps - -**2. BBranch** (`apps/server/src/becca/entities/bbranch.ts`) -- Represents parent-child relationship between notes -- Enables note cloning (multiple parents) -- Contains positioning information -- Has optional prefix for customization -- Tracks expansion state in tree - -**3. BAttribute** (`apps/server/src/becca/entities/battribute.ts`) -- Key-value metadata attached to notes -- Two types: labels (tags) and relations (links) -- Can be inheritable to child notes -- Used for search, organization, and scripting -- Supports promoted attributes (displayed prominently) - -**4. BRevision** (`apps/server/src/becca/entities/brevision.ts`) -- Stores historical versions of note content -- Automatic versioning on edits -- Retains title, type, and content -- Enables note history browsing and restoration - -**5. BAttachment** (`apps/server/src/becca/entities/battachment.ts`) -- File attachments linked to notes -- Has owner (note), role, and mime type -- Content stored in blobs -- Can be protected (encrypted) - -**6. BBlob** (`apps/server/src/becca/entities/bblob.ts`) -- Binary large object storage -- Stores actual note content and attachments -- Referenced by notes, revisions, and attachments -- Supports encryption for protected content - -### Widget-Based UI - -The frontend uses a **widget system** for modular, reusable UI components. - -Located at: `apps/client/src/widgets/` - -```typescript -// Widget Hierarchy -BasicWidget -├── NoteContextAwareWidget (responds to note changes) -│ ├── RightPanelWidget (displayed in right sidebar) -│ └── Type-specific widgets -├── Container widgets (tabs, ribbons, etc.) -└── Specialized widgets (search, calendar, etc.) -``` - -**Base Classes:** - -1. **BasicWidget** (`basic_widget.ts`) - - Base class for all UI components - - Lifecycle: construction → rendering → events → destruction - - Handles DOM manipulation - - Event subscription management - - Child widget management - -2. **NoteContextAwareWidget** (`note_context_aware_widget.ts`) - - Extends BasicWidget - - Automatically updates when active note changes - - Accesses current note context - - Used for note-dependent UI - -3. **RightPanelWidget** - - Widgets displayed in right sidebar - - Collapsible sections - - Context-specific tools and information - -**Type-Specific Widgets:** - -Located at: `apps/client/src/widgets/type_widgets/` - -Each note type has a dedicated widget: -- `text_type_widget.ts` - CKEditor integration -- `code_type_widget.ts` - CodeMirror integration -- `file_type_widget.ts` - File preview and download -- `image_type_widget.ts` - Image display and editing -- `canvas_type_widget.ts` - Excalidraw integration -- `mermaid_type_widget.ts` - Diagram rendering -- And more... - ---- - -## Data Layer - -### Database Schema - -Trilium uses **SQLite** as its database engine, managed via `better-sqlite3`. - -Schema location: `apps/server/src/assets/db/schema.sql` - -**Core Tables:** - -```sql --- Notes: Core content storage -notes ( - noteId, title, isProtected, type, mime, - blobId, isDeleted, dateCreated, dateModified -) - --- Branches: Tree relationships -branches ( - branchId, noteId, parentNoteId, notePosition, - prefix, isExpanded, isDeleted -) - --- Attributes: Metadata -attributes ( - attributeId, noteId, type, name, value, - position, isInheritable, isDeleted -) - --- Revisions: Version history -revisions ( - revisionId, noteId, type, mime, title, - blobId, utcDateLastEdited -) - --- Attachments: File attachments -attachments ( - attachmentId, ownerId, role, mime, title, - blobId, isProtected, isDeleted -) - --- Blobs: Binary content -blobs ( - blobId, content, dateModified -) - --- Options: Application settings -options ( - name, value, isSynced -) - --- Entity Changes: Sync tracking -entity_changes ( - entityName, entityId, hash, changeId, - isSynced, utcDateChanged -) -``` - -### Data Access Patterns - -**Direct SQL:** -```typescript -// apps/server/src/services/sql.ts -sql.getRows("SELECT * FROM notes WHERE type = ?", ['text']) -sql.execute("UPDATE notes SET title = ? WHERE noteId = ?", [title, noteId]) -``` - -**Through Becca:** -```typescript -// Recommended approach - uses cache -const note = becca.getNote('noteId') -note.title = 'New Title' -note.save() -``` - -**Through Froca (Frontend):** -```typescript -// Read-only access -const note = froca.getNote('noteId') -console.log(note.title) -``` - -### Database Migrations - -Migration system: `apps/server/src/migrations/` - -- Sequential numbered files (e.g., `XXXX_migration_name.sql`) -- Automatic execution on version upgrade -- Schema version tracked in options table -- Both SQL and JavaScript migrations supported - ---- - -## Caching System - -### Cache Initialization - -**Backend (Becca):** -```typescript -// On server startup -await becca_loader.load() // Loads all entities into memory -becca.loaded = true -``` - -**Frontend (Froca):** -```typescript -// On app initialization -await froca.loadInitialTree() // Loads root and visible notes -// Lazy load on demand -const note = await froca.getNote(noteId) // Triggers load if not cached -``` - -### Cache Invalidation - -**Server-Side:** -- Entities automatically update cache on save -- WebSocket broadcasts changes to all clients -- Synchronization updates trigger cache refresh - -**Client-Side:** -- WebSocket listeners update Froca -- Manual reload via `froca.loadSubTree(noteId)` -- Full reload on protected session changes - -### Cache Consistency - -**Entity Change Tracking:** -```typescript -// Every entity modification tracked -entity_changes ( - entityName: 'notes', - entityId: 'note123', - hash: 'abc...', - changeId: 'change456', - utcDateChanged: '2025-11-02...' -) -``` - -**Sync Protocol:** -1. Client requests changes since last sync -2. Server returns entity_changes records -3. Client applies changes to Froca -4. Client sends local changes to server -5. Server updates Becca and database - ---- - -## Frontend Architecture - -### Application Entry Point - -**Desktop:** `apps/client/src/desktop.ts` -**Web:** `apps/client/src/index.ts` - -### Service Layer - -Located at: `apps/client/src/services/` - -Key services: -- `froca.ts` - Frontend cache -- `server.ts` - API communication -- `ws.ts` - WebSocket connection -- `tree_service.ts` - Note tree management -- `note_context.ts` - Active note tracking -- `protected_session.ts` - Encryption key management -- `link.ts` - Note linking and navigation -- `export.ts` - Note export functionality - -### UI Components - -**Main Layout:** - -```mermaid -graph TD - subgraph TriliumUI[" "] - TitleBar[Title Bar] - - subgraph MainArea[" "] - NoteTree[Note Tree] - NoteDetail[Note Detail
        Editor] - RightPanel[Right Panel
        Info, Links] - end - - StatusBar[Status Bar] - end - - TitleBar -.-> MainArea - MainArea -.-> StatusBar - - style TitleBar fill:#e1f5ff - style NoteTree fill:#fff4e1 - style NoteDetail fill:#f5ffe1 - style RightPanel fill:#ffe1f5 - style StatusBar fill:#e1f5ff -``` - -**Component Locations:** -- `widgets/containers/` - Layout containers -- `widgets/buttons/` - Toolbar buttons -- `widgets/dialogs/` - Modal dialogs -- `widgets/ribbon_widgets/` - Tab widgets -- `widgets/type_widgets/` - Note type editors - -### Event System - -**Application Events:** -```typescript -// Subscribe to events -appContext.addBeforeUnloadListener(() => { - // Cleanup before page unload -}) - -// Trigger events -appContext.trigger('noteTreeLoaded') -``` - -**Note Context Events:** -```typescript -// NoteContextAwareWidget automatically receives: -- noteSwitched() -- noteChanged() -- refresh() -``` - -### State Management - -Trilium uses **custom state management** rather than Redux/MobX: - -- `note_context.ts` - Active note and context -- `froca.ts` - Entity cache -- Component local state -- URL parameters for shareable state - ---- - -## Backend Architecture - -### Application Entry Point - -Location: `apps/server/src/main.ts` - -**Startup Sequence:** -1. Load configuration -2. Initialize database -3. Run migrations -4. Load Becca cache -5. Start Express server -6. Initialize WebSocket -7. Start scheduled tasks - -### Service Layer - -Located at: `apps/server/src/services/` - -**Core Services:** - -- **Notes Management** - - `notes.ts` - CRUD operations - - `note_contents.ts` - Content handling - - `note_types.ts` - Type-specific logic - - `cloning.ts` - Note cloning/multi-parent - -- **Tree Operations** - - `tree.ts` - Tree structure management - - `branches.ts` - Branch operations - - `consistency_checks.ts` - Tree integrity - -- **Search** - - `search/search.ts` - Main search engine - - `search/expressions/` - Search expression parsing - - `search/services/` - Search utilities - -- **Sync** - - `sync.ts` - Synchronization protocol - - `sync_update.ts` - Update handling - - `sync_mutex.ts` - Concurrency control - -- **Scripting** - - `backend_script_api.ts` - Backend script API - - `script_context.ts` - Script execution context - -- **Import/Export** - - `import/` - Various import formats - - `export/` - Export to different formats - - `zip.ts` - Archive handling - -- **Security** - - `encryption.ts` - Note encryption - - `protected_session.ts` - Session management - - `password.ts` - Password handling - -### Route Structure - -Located at: `apps/server/src/routes/` - -``` -routes/ -├── index.ts # Route registration -├── api/ # REST API endpoints -│ ├── notes.ts -│ ├── branches.ts -│ ├── attributes.ts -│ ├── search.ts -│ ├── login.ts -│ └── ... -└── custom/ # Special endpoints - ├── setup.ts - ├── share.ts - └── ... -``` - -**API Endpoint Pattern:** -```typescript -router.get('/api/notes/:noteId', (req, res) => { - const noteId = req.params.noteId - const note = becca.getNote(noteId) - res.json(note.getPojoWithContent()) -}) -``` - -### Middleware - -Key middleware components: -- `auth.ts` - Authentication -- `csrf.ts` - CSRF protection -- `request_context.ts` - Request-scoped data -- `error_handling.ts` - Error responses - ---- - ## API Architecture -### Internal API - -**REST Endpoints** (`/api/*`) - -Used by the frontend for all operations: - -**Note Operations:** -- `GET /api/notes/:noteId` - Get note -- `POST /api/notes/:noteId/content` - Update content -- `PUT /api/notes/:noteId` - Update metadata -- `DELETE /api/notes/:noteId` - Delete note - -**Tree Operations:** -- `GET /api/tree` - Get note tree -- `POST /api/branches` - Create branch -- `PUT /api/branches/:branchId` - Update branch -- `DELETE /api/branches/:branchId` - Delete branch - -**Search:** -- `GET /api/search?query=...` - Search notes -- `GET /api/search-note/:noteId` - Execute search note - -### ETAPI (External API) - -Located at: `apps/server/src/etapi/` - -**Purpose:** Third-party integrations and automation - -**Authentication:** Token-based (ETAPI tokens) - -**OpenAPI Spec:** Auto-generated - -**Key Endpoints:** -- `/etapi/notes` - Note CRUD -- `/etapi/branches` - Branch management -- `/etapi/attributes` - Attribute operations -- `/etapi/attachments` - Attachment handling - -**Example:** -```bash -curl -H "Authorization: YOUR_TOKEN" \ - https://trilium.example.com/etapi/notes/noteId -``` - -### WebSocket API - -Located at: `apps/server/src/services/ws.ts` - -**Purpose:** Real-time updates and synchronization - -**Protocol:** WebSocket (Socket.IO-like custom protocol) - -**Message Types:** -- `sync` - Synchronization request -- `entity-change` - Entity update notification -- `refresh-tree` - Tree structure changed -- `open-note` - Open note in UI - -**Client Subscribe:** -```typescript -ws.subscribe('entity-change', (data) => { - froca.processEntityChange(data) -}) -``` - ---- - ## Build System ### Package Manager: pnpm @@ -1013,9 +277,3 @@ For historical context on major architectural decisions, see: - Adoption of pnpm workspaces - CKEditor 5 upgrade - Entity change tracking system - ---- - -**Document Maintainer:** TriliumNext Team -**Last Review:** November 2025 -**Next Review:** When major architectural changes occur diff --git a/docs/Developer Guide/!!!meta.json b/docs/Developer Guide/!!!meta.json index e75df02a9..a871c66f5 100644 --- a/docs/Developer Guide/!!!meta.json +++ b/docs/Developer Guide/!!!meta.json @@ -124,18 +124,687 @@ }, { "isClone": false, - "noteId": "zdQzavvHDl1k", + "noteId": "MhwWMgxwDTZL", "notePath": [ "jdjRLhLV3TtI", - "zdQzavvHDl1k" + "MhwWMgxwDTZL" ], - "title": "Documentation", + "title": "Architecture", "notePosition": 280, "prefix": null, "isExpanded": false, "type": "text", "mime": "text/html", "attributes": [ + { + "type": "label", + "name": "shareAlias", + "value": "architecture", + "isInheritable": false, + "position": 20 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bx-arch", + "isInheritable": false, + "position": 30 + } + ], + "format": "markdown", + "dataFileName": "Architecture.md", + "attachments": [], + "dirFileName": "Architecture", + "children": [ + { + "isClone": false, + "noteId": "2DJZgzpTJ078", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "2DJZgzpTJ078" + ], + "title": "Client-server architecture", + "notePosition": 10, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [], + "format": "markdown", + "attachments": [], + "dirFileName": "Client-server architecture", + "children": [ + { + "isClone": false, + "noteId": "dsMq2EIOMOBU", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "2DJZgzpTJ078", + "dsMq2EIOMOBU" + ], + "title": "Frontend", + "notePosition": 10, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [], + "format": "markdown", + "dataFileName": "Frontend.md", + "attachments": [] + }, + { + "isClone": false, + "noteId": "tsswRlmHEnYW", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "2DJZgzpTJ078", + "tsswRlmHEnYW" + ], + "title": "Backend", + "notePosition": 20, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [], + "format": "markdown", + "dataFileName": "Backend.md", + "attachments": [] + } + ] + }, + { + "isClone": false, + "noteId": "pRZhrVIGCbMu", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "pRZhrVIGCbMu" + ], + "title": "Database", + "notePosition": 20, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "label", + "name": "shareAlias", + "value": "database", + "isInheritable": false, + "position": 20 + } + ], + "format": "markdown", + "dataFileName": "Database.md", + "attachments": [], + "dirFileName": "Database", + "children": [ + { + "isClone": false, + "noteId": "vNMojjUN76jc", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "pRZhrVIGCbMu", + "vNMojjUN76jc" + ], + "title": "Database structure", + "notePosition": 10, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "label", + "name": "shareAlias", + "value": "tables", + "isInheritable": false, + "position": 20 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bx-table", + "isInheritable": false, + "position": 30 + } + ], + "format": "markdown", + "attachments": [], + "dirFileName": "Database structure", + "children": [ + { + "isClone": false, + "noteId": "e6GnYOXeIWjg", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "pRZhrVIGCbMu", + "vNMojjUN76jc", + "e6GnYOXeIWjg" + ], + "title": "attachments", + "notePosition": 20, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "relation", + "name": "internalLink", + "value": "DSkl8C325tEC", + "isInheritable": false, + "position": 10 + }, + { + "type": "relation", + "name": "internalLink", + "value": "UvXpeSqfYc6d", + "isInheritable": false, + "position": 20 + }, + { + "type": "relation", + "name": "internalLink", + "value": "VIcWnKGs0sMh", + "isInheritable": false, + "position": 30 + }, + { + "type": "relation", + "name": "internalLink", + "value": "tM3rIZQzlum4", + "isInheritable": false, + "position": 40 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bx-table", + "isInheritable": false, + "position": 10 + }, + { + "type": "label", + "name": "shareAlias", + "value": "attachments", + "isInheritable": false, + "position": 50 + } + ], + "format": "markdown", + "dataFileName": "attachments.md", + "attachments": [] + }, + { + "isClone": false, + "noteId": "ciL84vNBNi9y", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "pRZhrVIGCbMu", + "vNMojjUN76jc", + "ciL84vNBNi9y" + ], + "title": "attributes", + "notePosition": 30, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "relation", + "name": "internalLink", + "value": "DSkl8C325tEC", + "isInheritable": false, + "position": 10 + }, + { + "type": "relation", + "name": "internalLink", + "value": "tM3rIZQzlum4", + "isInheritable": false, + "position": 20 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bx-table", + "isInheritable": false, + "position": 10 + }, + { + "type": "label", + "name": "shareAlias", + "value": "attributes", + "isInheritable": false, + "position": 40 + } + ], + "format": "markdown", + "dataFileName": "attributes.md", + "attachments": [] + }, + { + "isClone": false, + "noteId": "VIcWnKGs0sMh", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "pRZhrVIGCbMu", + "vNMojjUN76jc", + "VIcWnKGs0sMh" + ], + "title": "blobs", + "notePosition": 40, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "label", + "name": "iconClass", + "value": "bx bx-table", + "isInheritable": false, + "position": 10 + }, + { + "type": "label", + "name": "shareAlias", + "value": "blobs", + "isInheritable": false, + "position": 20 + } + ], + "format": "markdown", + "dataFileName": "blobs.md", + "attachments": [] + }, + { + "isClone": false, + "noteId": "GskLPkgY5n6E", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "pRZhrVIGCbMu", + "vNMojjUN76jc", + "GskLPkgY5n6E" + ], + "title": "branches", + "notePosition": 50, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "relation", + "name": "internalLink", + "value": "h8AsuFjSD4fB", + "isInheritable": false, + "position": 10 + }, + { + "type": "relation", + "name": "internalLink", + "value": "DSkl8C325tEC", + "isInheritable": false, + "position": 20 + }, + { + "type": "relation", + "name": "internalLink", + "value": "tM3rIZQzlum4", + "isInheritable": false, + "position": 30 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bx-table", + "isInheritable": false, + "position": 10 + }, + { + "type": "label", + "name": "shareAlias", + "value": "branches", + "isInheritable": false, + "position": 40 + } + ], + "format": "markdown", + "dataFileName": "branches.md", + "attachments": [] + }, + { + "isClone": false, + "noteId": "ohhExR078MPU", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "pRZhrVIGCbMu", + "vNMojjUN76jc", + "ohhExR078MPU" + ], + "title": "entity_changes", + "notePosition": 51, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "label", + "name": "iconClass", + "value": "bx bx-table", + "isInheritable": false, + "position": 10 + }, + { + "type": "label", + "name": "shareAlias", + "value": "entity-changes", + "isInheritable": false, + "position": 20 + } + ], + "format": "markdown", + "dataFileName": "entity_changes.md", + "attachments": [] + }, + { + "isClone": false, + "noteId": "bRqbIg633nCs", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "pRZhrVIGCbMu", + "vNMojjUN76jc", + "bRqbIg633nCs" + ], + "title": "etapi_tokens", + "notePosition": 52, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "relation", + "name": "internalLink", + "value": "tM3rIZQzlum4", + "isInheritable": false, + "position": 10 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bx-table", + "isInheritable": false, + "position": 10 + }, + { + "type": "label", + "name": "shareAlias", + "value": "etapi-tokens", + "isInheritable": false, + "position": 20 + } + ], + "format": "markdown", + "dataFileName": "etapi_tokens.md", + "attachments": [] + }, + { + "isClone": false, + "noteId": "DSkl8C325tEC", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "pRZhrVIGCbMu", + "vNMojjUN76jc", + "DSkl8C325tEC" + ], + "title": "notes", + "notePosition": 53, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "relation", + "name": "internalLink", + "value": "VIcWnKGs0sMh", + "isInheritable": false, + "position": 10 + }, + { + "type": "relation", + "name": "internalLink", + "value": "UvXpeSqfYc6d", + "isInheritable": false, + "position": 20 + }, + { + "type": "relation", + "name": "internalLink", + "value": "tM3rIZQzlum4", + "isInheritable": false, + "position": 30 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bx-table", + "isInheritable": false, + "position": 10 + }, + { + "type": "label", + "name": "shareAlias", + "value": "notes", + "isInheritable": false, + "position": 40 + } + ], + "format": "markdown", + "dataFileName": "notes.md", + "attachments": [] + }, + { + "isClone": false, + "noteId": "4oeftEmy77Bt", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "pRZhrVIGCbMu", + "vNMojjUN76jc", + "4oeftEmy77Bt" + ], + "title": "options", + "notePosition": 54, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "label", + "name": "iconClass", + "value": "bx bx-table", + "isInheritable": false, + "position": 10 + }, + { + "type": "label", + "name": "shareAlias", + "value": "options", + "isInheritable": false, + "position": 20 + } + ], + "format": "markdown", + "dataFileName": "options.md", + "attachments": [] + }, + { + "isClone": false, + "noteId": "VyFirdgAOoh5", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "pRZhrVIGCbMu", + "vNMojjUN76jc", + "VyFirdgAOoh5" + ], + "title": "recent_notes", + "notePosition": 55, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "relation", + "name": "internalLink", + "value": "DSkl8C325tEC", + "isInheritable": false, + "position": 10 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bx-table", + "isInheritable": false, + "position": 10 + }, + { + "type": "label", + "name": "shareAlias", + "value": "recent-notes", + "isInheritable": false, + "position": 20 + } + ], + "format": "markdown", + "dataFileName": "recent_notes.md", + "attachments": [] + }, + { + "isClone": false, + "noteId": "s7ZBiaJVNumK", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "pRZhrVIGCbMu", + "vNMojjUN76jc", + "s7ZBiaJVNumK" + ], + "title": "revisions", + "notePosition": 56, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "relation", + "name": "internalLink", + "value": "DSkl8C325tEC", + "isInheritable": false, + "position": 10 + }, + { + "type": "relation", + "name": "internalLink", + "value": "VIcWnKGs0sMh", + "isInheritable": false, + "position": 20 + }, + { + "type": "relation", + "name": "internalLink", + "value": "UvXpeSqfYc6d", + "isInheritable": false, + "position": 30 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bx-table", + "isInheritable": false, + "position": 10 + }, + { + "type": "label", + "name": "shareAlias", + "value": "revisions", + "isInheritable": false, + "position": 50 + } + ], + "format": "markdown", + "dataFileName": "revisions.md", + "attachments": [] + } + ] + } + ] + }, + { + "isClone": false, + "noteId": "Wxn82Em8B7U5", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "Wxn82Em8B7U5" + ], + "title": "API", + "notePosition": 30, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [], + "format": "markdown", + "dataFileName": "API.md", + "attachments": [] + } + ] + }, + { + "isClone": false, + "noteId": "zdQzavvHDl1k", + "notePath": [ + "jdjRLhLV3TtI", + "zdQzavvHDl1k" + ], + "title": "Documentation", + "notePosition": 290, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "relation", + "name": "internalLink", + "value": "T2W7WCZrYZBU", + "isInheritable": false, + "position": 10 + }, { "type": "label", "name": "shareAlias", @@ -149,13 +818,6 @@ "value": "bx bx-book-open", "isInheritable": false, "position": 30 - }, - { - "type": "relation", - "name": "internalLink", - "value": "T2W7WCZrYZBU", - "isInheritable": false, - "position": 40 } ], "format": "markdown", @@ -200,7 +862,7 @@ "a0mkxxB4Uvbf" ], "title": "Building", - "notePosition": 290, + "notePosition": 300, "prefix": null, "isExpanded": false, "type": "text", @@ -415,7 +1077,7 @@ "name": "internalLink", "value": "zdQzavvHDl1k", "isInheritable": false, - "position": 20 + "position": 10 }, { "type": "label", @@ -439,7 +1101,7 @@ "qalhAaJoQ7AN" ], "title": "Dependencies", - "notePosition": 310, + "notePosition": 320, "prefix": null, "isExpanded": false, "type": "text", @@ -682,8 +1344,8 @@ "jdjRLhLV3TtI", "yeqU0zo0ZQ83" ], - "title": "Architecture", - "notePosition": 320, + "title": "Concepts", + "notePosition": 330, "prefix": null, "isExpanded": false, "type": "text", @@ -692,14 +1354,14 @@ { "type": "label", "name": "shareAlias", - "value": "architecture", + "value": "concepts", "isInheritable": false, "position": 10 }, { "type": "label", "name": "iconClass", - "value": "bx bx-arch", + "value": "bx bx-sitemap", "isInheritable": false, "position": 20 }, @@ -713,7 +1375,7 @@ ], "format": "markdown", "attachments": [], - "dirFileName": "Architecture", + "dirFileName": "Concepts", "children": [ { "isClone": false, @@ -776,6 +1438,33 @@ "dataFileName": "Branch prefixes.md", "attachments": [] }, + { + "isClone": false, + "noteId": "6Yms5izbd0GF", + "notePath": [ + "jdjRLhLV3TtI", + "yeqU0zo0ZQ83", + "6Yms5izbd0GF" + ], + "title": "Cache", + "notePosition": 30, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "label", + "name": "iconClass", + "value": "bx bx-microchip", + "isInheritable": false, + "position": 20 + } + ], + "format": "markdown", + "dataFileName": "Cache.md", + "attachments": [] + }, { "isClone": false, "noteId": "oqg9OpK8xfcm", @@ -785,7 +1474,7 @@ "oqg9OpK8xfcm" ], "title": "CI", - "notePosition": 30, + "notePosition": 40, "prefix": null, "isExpanded": false, "type": "text", @@ -857,511 +1546,6 @@ } ] }, - { - "isClone": false, - "noteId": "vNMojjUN76jc", - "notePath": [ - "jdjRLhLV3TtI", - "yeqU0zo0ZQ83", - "vNMojjUN76jc" - ], - "title": "Database structure", - "notePosition": 40, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "label", - "name": "shareAlias", - "value": "database", - "isInheritable": false, - "position": 20 - }, - { - "type": "label", - "name": "iconClass", - "value": "bx bx-data", - "isInheritable": false, - "position": 30 - } - ], - "format": "markdown", - "attachments": [], - "dirFileName": "Database structure", - "children": [ - { - "isClone": false, - "noteId": "e6GnYOXeIWjg", - "notePath": [ - "jdjRLhLV3TtI", - "yeqU0zo0ZQ83", - "vNMojjUN76jc", - "e6GnYOXeIWjg" - ], - "title": "attachments", - "notePosition": 20, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "relation", - "name": "internalLink", - "value": "DSkl8C325tEC", - "isInheritable": false, - "position": 10 - }, - { - "type": "relation", - "name": "internalLink", - "value": "UvXpeSqfYc6d", - "isInheritable": false, - "position": 20 - }, - { - "type": "relation", - "name": "internalLink", - "value": "VIcWnKGs0sMh", - "isInheritable": false, - "position": 30 - }, - { - "type": "relation", - "name": "internalLink", - "value": "tM3rIZQzlum4", - "isInheritable": false, - "position": 40 - }, - { - "type": "label", - "name": "iconClass", - "value": "bx bx-table", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "shareAlias", - "value": "attachments", - "isInheritable": false, - "position": 50 - } - ], - "format": "markdown", - "dataFileName": "attachments.md", - "attachments": [] - }, - { - "isClone": false, - "noteId": "ciL84vNBNi9y", - "notePath": [ - "jdjRLhLV3TtI", - "yeqU0zo0ZQ83", - "vNMojjUN76jc", - "ciL84vNBNi9y" - ], - "title": "attributes", - "notePosition": 30, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "relation", - "name": "internalLink", - "value": "DSkl8C325tEC", - "isInheritable": false, - "position": 10 - }, - { - "type": "relation", - "name": "internalLink", - "value": "tM3rIZQzlum4", - "isInheritable": false, - "position": 20 - }, - { - "type": "label", - "name": "iconClass", - "value": "bx bx-table", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "shareAlias", - "value": "attributes", - "isInheritable": false, - "position": 40 - } - ], - "format": "markdown", - "dataFileName": "attributes.md", - "attachments": [] - }, - { - "isClone": false, - "noteId": "VIcWnKGs0sMh", - "notePath": [ - "jdjRLhLV3TtI", - "yeqU0zo0ZQ83", - "vNMojjUN76jc", - "VIcWnKGs0sMh" - ], - "title": "blobs", - "notePosition": 40, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "label", - "name": "iconClass", - "value": "bx bx-table", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "shareAlias", - "value": "blobs", - "isInheritable": false, - "position": 20 - } - ], - "format": "markdown", - "dataFileName": "blobs.md", - "attachments": [] - }, - { - "isClone": false, - "noteId": "GskLPkgY5n6E", - "notePath": [ - "jdjRLhLV3TtI", - "yeqU0zo0ZQ83", - "vNMojjUN76jc", - "GskLPkgY5n6E" - ], - "title": "branches", - "notePosition": 50, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "relation", - "name": "internalLink", - "value": "h8AsuFjSD4fB", - "isInheritable": false, - "position": 10 - }, - { - "type": "relation", - "name": "internalLink", - "value": "DSkl8C325tEC", - "isInheritable": false, - "position": 20 - }, - { - "type": "relation", - "name": "internalLink", - "value": "tM3rIZQzlum4", - "isInheritable": false, - "position": 30 - }, - { - "type": "label", - "name": "iconClass", - "value": "bx bx-table", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "shareAlias", - "value": "branches", - "isInheritable": false, - "position": 40 - } - ], - "format": "markdown", - "dataFileName": "branches.md", - "attachments": [] - }, - { - "isClone": false, - "noteId": "ohhExR078MPU", - "notePath": [ - "jdjRLhLV3TtI", - "yeqU0zo0ZQ83", - "vNMojjUN76jc", - "ohhExR078MPU" - ], - "title": "entity_changes", - "notePosition": 51, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "label", - "name": "iconClass", - "value": "bx bx-table", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "shareAlias", - "value": "entity-changes", - "isInheritable": false, - "position": 20 - } - ], - "format": "markdown", - "dataFileName": "entity_changes.md", - "attachments": [] - }, - { - "isClone": false, - "noteId": "bRqbIg633nCs", - "notePath": [ - "jdjRLhLV3TtI", - "yeqU0zo0ZQ83", - "vNMojjUN76jc", - "bRqbIg633nCs" - ], - "title": "etapi_tokens", - "notePosition": 52, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "relation", - "name": "internalLink", - "value": "tM3rIZQzlum4", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "iconClass", - "value": "bx bx-table", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "shareAlias", - "value": "etapi-tokens", - "isInheritable": false, - "position": 20 - } - ], - "format": "markdown", - "dataFileName": "etapi_tokens.md", - "attachments": [] - }, - { - "isClone": false, - "noteId": "DSkl8C325tEC", - "notePath": [ - "jdjRLhLV3TtI", - "yeqU0zo0ZQ83", - "vNMojjUN76jc", - "DSkl8C325tEC" - ], - "title": "notes", - "notePosition": 53, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "relation", - "name": "internalLink", - "value": "VIcWnKGs0sMh", - "isInheritable": false, - "position": 10 - }, - { - "type": "relation", - "name": "internalLink", - "value": "UvXpeSqfYc6d", - "isInheritable": false, - "position": 20 - }, - { - "type": "relation", - "name": "internalLink", - "value": "tM3rIZQzlum4", - "isInheritable": false, - "position": 30 - }, - { - "type": "label", - "name": "iconClass", - "value": "bx bx-table", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "shareAlias", - "value": "notes", - "isInheritable": false, - "position": 40 - } - ], - "format": "markdown", - "dataFileName": "notes.md", - "attachments": [] - }, - { - "isClone": false, - "noteId": "4oeftEmy77Bt", - "notePath": [ - "jdjRLhLV3TtI", - "yeqU0zo0ZQ83", - "vNMojjUN76jc", - "4oeftEmy77Bt" - ], - "title": "options", - "notePosition": 54, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "label", - "name": "iconClass", - "value": "bx bx-table", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "shareAlias", - "value": "options", - "isInheritable": false, - "position": 20 - } - ], - "format": "markdown", - "dataFileName": "options.md", - "attachments": [] - }, - { - "isClone": false, - "noteId": "VyFirdgAOoh5", - "notePath": [ - "jdjRLhLV3TtI", - "yeqU0zo0ZQ83", - "vNMojjUN76jc", - "VyFirdgAOoh5" - ], - "title": "recent_notes", - "notePosition": 55, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "relation", - "name": "internalLink", - "value": "DSkl8C325tEC", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "iconClass", - "value": "bx bx-table", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "shareAlias", - "value": "recent-notes", - "isInheritable": false, - "position": 20 - } - ], - "format": "markdown", - "dataFileName": "recent_notes.md", - "attachments": [] - }, - { - "isClone": false, - "noteId": "s7ZBiaJVNumK", - "notePath": [ - "jdjRLhLV3TtI", - "yeqU0zo0ZQ83", - "vNMojjUN76jc", - "s7ZBiaJVNumK" - ], - "title": "revisions", - "notePosition": 56, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "relation", - "name": "internalLink", - "value": "DSkl8C325tEC", - "isInheritable": false, - "position": 10 - }, - { - "type": "relation", - "name": "internalLink", - "value": "VIcWnKGs0sMh", - "isInheritable": false, - "position": 20 - }, - { - "type": "relation", - "name": "internalLink", - "value": "UvXpeSqfYc6d", - "isInheritable": false, - "position": 30 - }, - { - "type": "label", - "name": "iconClass", - "value": "bx bx-table", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "shareAlias", - "value": "revisions", - "isInheritable": false, - "position": 50 - } - ], - "format": "markdown", - "dataFileName": "revisions.md", - "attachments": [] - } - ] - }, { "isClone": false, "noteId": "tM3rIZQzlum4", @@ -1371,7 +1555,7 @@ "tM3rIZQzlum4" ], "title": "Deleted notes", - "notePosition": 50, + "notePosition": 70, "prefix": null, "isExpanded": false, "type": "text", @@ -1405,7 +1589,7 @@ "oLhKpfi2kGON" ], "title": "Demo document", - "notePosition": 60, + "notePosition": 80, "prefix": null, "isExpanded": false, "type": "text", @@ -1430,6 +1614,40 @@ "dataFileName": "Demo document.md", "attachments": [] }, + { + "isClone": false, + "noteId": "TiUll0Osoaz6", + "notePath": [ + "jdjRLhLV3TtI", + "yeqU0zo0ZQ83", + "TiUll0Osoaz6" + ], + "title": "Entities", + "notePosition": 90, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "label", + "name": "shareAlias", + "value": "entities", + "isInheritable": false, + "position": 20 + }, + { + "type": "label", + "name": "iconClass", + "value": "bx bxs-note", + "isInheritable": false, + "position": 30 + } + ], + "format": "markdown", + "dataFileName": "Entities.md", + "attachments": [] + }, { "isClone": false, "noteId": "UzRirf46Xi46", @@ -1439,7 +1657,7 @@ "UzRirf46Xi46" ], "title": "Hidden notes", - "notePosition": 80, + "notePosition": 100, "prefix": null, "isExpanded": false, "type": "text", @@ -1473,7 +1691,7 @@ "m2W35hwSDUeh" ], "title": "Icons", - "notePosition": 90, + "notePosition": 110, "prefix": null, "isExpanded": false, "type": "text", @@ -1514,7 +1732,7 @@ "TLXJwBDo8Rdv" ], "title": "Internationalisation / Translations", - "notePosition": 100, + "notePosition": 120, "prefix": null, "isExpanded": false, "type": "text", @@ -1649,7 +1867,7 @@ "Usiyzn9C4WFv" ], "title": "Launchers", - "notePosition": 110, + "notePosition": 130, "prefix": null, "isExpanded": false, "type": "text", @@ -1676,7 +1894,7 @@ "qjQNyaYXSNWu" ], "title": "Note Revisions", - "notePosition": 120, + "notePosition": 140, "prefix": null, "isExpanded": false, "type": "text", @@ -1710,7 +1928,7 @@ "7RBJMqVz2EsJ" ], "title": "Note Types", - "notePosition": 130, + "notePosition": 150, "prefix": null, "isExpanded": false, "type": "text", @@ -2044,7 +2262,7 @@ "6dC7ha5vjqqS" ], "title": "Options", - "notePosition": 140, + "notePosition": 160, "prefix": null, "isExpanded": false, "type": "text", @@ -2109,7 +2327,7 @@ "W0msUwLxm40d" ], "title": "Printing and exporting to PDF", - "notePosition": 150, + "notePosition": 170, "prefix": null, "isExpanded": false, "type": "text", @@ -2150,7 +2368,7 @@ "UvXpeSqfYc6d" ], "title": "Protected entities", - "notePosition": 160, + "notePosition": 180, "prefix": null, "isExpanded": false, "type": "text", @@ -2205,7 +2423,7 @@ "vphziLmQeQHY" ], "title": "Share", - "notePosition": 170, + "notePosition": 190, "prefix": null, "isExpanded": false, "type": "text", @@ -2239,7 +2457,7 @@ "n9wYW9nUTynV" ], "title": "Synchronisation", - "notePosition": 180, + "notePosition": 200, "prefix": null, "isExpanded": false, "type": "text", @@ -2303,7 +2521,7 @@ "k7RavjuXQt8z" ], "title": "Syntax highlighting", - "notePosition": 190, + "notePosition": 210, "prefix": null, "isExpanded": false, "type": "text", @@ -2337,7 +2555,7 @@ "7BCukQTCm7fv" ], "title": "Themes", - "notePosition": 200, + "notePosition": 220, "prefix": null, "isExpanded": false, "type": "text", @@ -2372,7 +2590,7 @@ "YjerxU7Aii8X" ], "title": "Troubleshooting", - "notePosition": 360, + "notePosition": 370, "prefix": null, "isExpanded": false, "type": "text", @@ -2461,12 +2679,19 @@ "dtKC3FmoWOrv" ], "title": "Testing", - "notePosition": 380, + "notePosition": 390, "prefix": null, "isExpanded": false, "type": "text", "mime": "text/html", "attributes": [ + { + "type": "relation", + "name": "internalLink", + "value": "w6gMvKh0UAVT", + "isInheritable": false, + "position": 10 + }, { "type": "label", "name": "shareAlias", @@ -2480,13 +2705,6 @@ "value": "bx bxs-eyedropper", "isInheritable": false, "position": 30 - }, - { - "type": "relation", - "name": "internalLink", - "value": "w6gMvKh0UAVT", - "isInheritable": false, - "position": 40 } ], "format": "markdown", diff --git a/docs/Developer Guide/Developer Guide/Architecture.md b/docs/Developer Guide/Developer Guide/Architecture.md new file mode 100644 index 000000000..a11d27274 --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Architecture.md @@ -0,0 +1,118 @@ +# Architecture +Trilium Notes is a hierarchical note-taking application built as a TypeScript monorepo. It supports multiple deployment modes (desktop, server, mobile web) and features advanced capabilities including synchronization, scripting, encryption, and rich content editing. + +### Key Characteristics + +* **Monorepo Architecture**: Uses pnpm workspaces for dependency management +* **Multi-Platform**: Desktop (Electron), Server (Node.js/Express), and Mobile Web +* **TypeScript-First**: Strong typing throughout the codebase +* **Plugin-Based**: Extensible architecture for note types and UI components +* **Offline-First**: Full functionality without network connectivity +* **Synchronization-Ready**: Built-in sync protocol for multi-device usage + +### Technology Stack + +* **Runtime**: Node.js (backend), Browser/Electron (frontend) +* **Language**: TypeScript, JavaScript +* **Database**: SQLite (better-sqlite3) +* **Build Tools**: Vite, ESBuild, pnpm +* **UI Framework**: Custom widget-based system (vanilla HTML, CSS & JavaScript + jQuery), in the process of converting to React/Preact. +* **Rich Text**: CKEditor 5 (customized) +* **Code Editing**: CodeMirror 6 +* **Desktop**: Electron +* **Server**: Express.js + +## Main architecture + +Trilium follows a **client-server architecture** even in desktop mode, where Electron runs both the backend server and frontend client within the same process. + +```mermaid +graph TB + subgraph Frontend + Widgets[Widgets
        System] + Froca[Froca
        Cache] + UIServices[UI
        Services] + end + + subgraph Backend["Backend Server"] + Express[Express
        Routes] + Becca[Becca
        Cache] + ScriptEngine[Script
        Engine] + Database[(SQLite
        Database)] + end + + Widgets -.-> API[WebSocket & REST API] + Froca -.-> API + UIServices -.-> API + API -.-> Express + API -.-> Becca + API -.-> ScriptEngine + Becca --> Database + Express --> Database + ScriptEngine --> Database +``` + +### Deployment Modes + +1. **Desktop Application** + * Electron wrapper running both frontend and backend + * Local SQLite database + * Full offline functionality + * Cross-platform (Windows, macOS, Linux) +2. **Server Installation** + * Node.js server exposing web interface + * Multi-user capable + * Can sync with desktop clients + * Docker deployment supported +3. **Mobile Web** + * Optimized responsive interface + * Accessed via browser + * Requires server installation + +## Monorepo Structure + +Trilium uses **pnpm workspaces** to manage its monorepo structure, with apps and packages clearly separated. + +``` +trilium/ +├── apps/ # Runnable applications +│ ├── client/ # Frontend application (shared by server & desktop) +│ ├── server/ # Node.js server with web interface +│ ├── desktop/ # Electron desktop application +│ ├── web-clipper/ # Browser extension for web content capture +│ ├── db-compare/ # Database comparison tool +│ ├── dump-db/ # Database export tool +│ ├── edit-docs/ # Documentation editing tool +│ ├── build-docs/ # Documentation build tool +│ └── website/ # Marketing website +│ +├── packages/ # Shared libraries +│ ├── commons/ # Shared interfaces and utilities +│ ├── ckeditor5/ # Custom rich text editor +│ ├── codemirror/ # Code editor customizations +│ ├── highlightjs/ # Syntax highlighting +│ ├── ckeditor5-admonition/ # CKEditor plugin: admonitions +│ ├── ckeditor5-footnotes/ # CKEditor plugin: footnotes +│ ├── ckeditor5-keyboard-marker/# CKEditor plugin: keyboard shortcuts +│ ├── ckeditor5-math/ # CKEditor plugin: math equations +│ ├── ckeditor5-mermaid/ # CKEditor plugin: diagrams +│ ├── express-partial-content/ # HTTP partial content middleware +│ ├── share-theme/ # Shared note theme +│ ├── splitjs/ # Split pane library +│ └── turndown-plugin-gfm/ # Markdown conversion +│ +├── docs/ # Documentation +├── scripts/ # Build and utility scripts +└── patches/ # Package patches (via pnpm) +``` + +### Package Dependencies + +The monorepo uses workspace protocol (`workspace:*`) for internal dependencies: + +``` +desktop → client → commons +server → client → commons +client → ckeditor5, codemirror, highlightjs +ckeditor5 → ckeditor5-* plugins +``` \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/API.md b/docs/Developer Guide/Developer Guide/Architecture/API.md new file mode 100644 index 000000000..57fc8c164 --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Architecture/API.md @@ -0,0 +1,72 @@ +# API +### Internal API + +**REST Endpoints** (`/api/*`) + +Used by the frontend for all operations: + +**Note Operations:** + +* `GET /api/notes/:noteId` - Get note +* `POST /api/notes/:noteId/content` - Update content +* `PUT /api/notes/:noteId` - Update metadata +* `DELETE /api/notes/:noteId` - Delete note + +**Tree Operations:** + +* `GET /api/tree` - Get note tree +* `POST /api/branches` - Create branch +* `PUT /api/branches/:branchId` - Update branch +* `DELETE /api/branches/:branchId` - Delete branch + +**Search:** + +* `GET /api/search?query=...` - Search notes +* `GET /api/search-note/:noteId` - Execute search note + +### ETAPI (External API) + +Located at: `apps/server/src/etapi/` + +**Purpose:** Third-party integrations and automation + +**Authentication:** Token-based (ETAPI tokens) + +**OpenAPI Spec:** Auto-generated + +**Key Endpoints:** + +* `/etapi/notes` - Note CRUD +* `/etapi/branches` - Branch management +* `/etapi/attributes` - Attribute operations +* `/etapi/attachments` - Attachment handling + +**Example:** + +```sh +curl -H "Authorization: YOUR_TOKEN" \ + https://trilium.example.com/etapi/notes/noteId +``` + +### WebSocket API + +Located at: `apps/server/src/services/ws.ts` + +**Purpose:** Real-time updates and synchronization + +**Protocol:** WebSocket (Socket.IO-like custom protocol) + +**Message Types:** + +* `sync` - Synchronization request +* `entity-change` - Entity update notification +* `refresh-tree` - Tree structure changed +* `open-note` - Open note in UI + +**Client Subscribe:** + +```typescript +ws.subscribe('entity-change', (data) => { + froca.processEntityChange(data) +}) +``` \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Client-server architecture/Backend.md b/docs/Developer Guide/Developer Guide/Architecture/Client-server architecture/Backend.md new file mode 100644 index 000000000..df0ddf21d --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Architecture/Client-server architecture/Backend.md @@ -0,0 +1,88 @@ +# Backend +### Application Entry Point + +Location: `apps/server/src/main.ts` + +**Startup Sequence:** + +1. Load configuration +2. Initialize database +3. Run migrations +4. Load Becca cache +5. Start Express server +6. Initialize WebSocket +7. Start scheduled tasks + +### Service Layer + +Located at: `apps/server/src/services/` + +**Core Services:** + +* **Notes Management** + * `notes.ts` - CRUD operations + * `note_contents.ts` - Content handling + * `note_types.ts` - Type-specific logic + * `cloning.ts` - Note cloning/multi-parent +* **Tree Operations** + * `tree.ts` - Tree structure management + * `branches.ts` - Branch operations + * `consistency_checks.ts` - Tree integrity +* **Search** + * `search/search.ts` - Main search engine + * `search/expressions/` - Search expression parsing + * `search/services/` - Search utilities +* **Sync** + * `sync.ts` - Synchronization protocol + * `sync_update.ts` - Update handling + * `sync_mutex.ts` - Concurrency control +* **Scripting** + * `backend_script_api.ts` - Backend script API + * `script_context.ts` - Script execution context +* **Import/Export** + * `import/` - Various import formats + * `export/` - Export to different formats + * `zip.ts` - Archive handling +* **Security** + * `encryption.ts` - Note encryption + * `protected_session.ts` - Session management + * `password.ts` - Password handling + +### Route Structure + +Located at: `apps/server/src/routes/` + +``` +routes/ +├── index.ts # Route registration +├── api/ # REST API endpoints +│ ├── notes.ts +│ ├── branches.ts +│ ├── attributes.ts +│ ├── search.ts +│ ├── login.ts +│ └── ... +└── custom/ # Special endpoints + ├── setup.ts + ├── share.ts + └── ... +``` + +**API Endpoint Pattern:** + +```typescript +router.get('/api/notes/:noteId', (req, res) => { + const noteId = req.params.noteId + const note = becca.getNote(noteId) + res.json(note.getPojoWithContent()) +}) +``` + +### Middleware + +Key middleware components: + +* `auth.ts` - Authentication +* `csrf.ts` - CSRF protection +* `request_context.ts` - Request-scoped data +* `error_handling.ts` - Error responses \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Client-server architecture/Frontend.md b/docs/Developer Guide/Developer Guide/Architecture/Client-server architecture/Frontend.md new file mode 100644 index 000000000..9ef0acbdf --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Architecture/Client-server architecture/Frontend.md @@ -0,0 +1,61 @@ +# Frontend +### Application Entry Point + +**Desktop:** `apps/client/src/desktop.ts` **Web:** `apps/client/src/index.ts` + +### Service Layer + +Located at: `apps/client/src/services/` + +Key services: + +* `froca.ts` - Frontend cache +* `server.ts` - API communication +* `ws.ts` - WebSocket connection +* `tree_service.ts` - Note tree management +* `note_context.ts` - Active note tracking +* `protected_session.ts` - Encryption key management +* `link.ts` - Note linking and navigation +* `export.ts` - Note export functionality + +### UI Components + +**Component Locations:** + +* `widgets/containers/` - Layout containers +* `widgets/buttons/` - Toolbar buttons +* `widgets/dialogs/` - Modal dialogs +* `widgets/ribbon_widgets/` - Tab widgets +* `widgets/type_widgets/` - Note type editors + +### Event System + +**Application Events:** + +```typescript +// Subscribe to events +appContext.addBeforeUnloadListener(() => { + // Cleanup before page unload +}) + +// Trigger events +appContext.trigger('noteTreeLoaded') +``` + +**Note Context Events:** + +```typescript +// NoteContextAwareWidget automatically receives: +- noteSwitched() +- noteChanged() +- refresh() +``` + +### State Management + +Trilium uses **custom state management** rather than Redux/MobX: + +* `note_context.ts` - Active note and context +* `froca.ts` - Entity cache +* Component local state +* URL parameters for shareable state \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Database.md b/docs/Developer Guide/Developer Guide/Architecture/Database.md new file mode 100644 index 000000000..bb80ccdb1 --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Architecture/Database.md @@ -0,0 +1,40 @@ +# Database +Trilium uses **SQLite** as its database engine, managed via `better-sqlite3`. + +Schema location: `apps/server/src/assets/db/schema.sql` + +### Data Access Patterns + +**Direct SQL:** + +```typescript +// apps/server/src/services/sql.ts +sql.getRows("SELECT * FROM notes WHERE type = ?", ['text']) +sql.execute("UPDATE notes SET title = ? WHERE noteId = ?", [title, noteId]) +``` + +**Through Becca:** + +```typescript +// Recommended approach - uses cache +const note = becca.getNote('noteId') +note.title = 'New Title' +note.save() +``` + +**Through Froca (Frontend):** + +```typescript +// Read-only access +const note = froca.getNote('noteId') +console.log(note.title) +``` + +### Database Migrations + +* The migration system is in `server/src/migrations/migrations.ts` (actual definitions) and `src/services/migration.ts`. +* Both SQLite and TypeScript migrations are supported. + * Small migrations are contained directly in `src/migrations/migrations.ts`. + * Bigger TypeScript migrations are sequentially numbered (e.g., `XXXX_migration_name.ts`) and dynamically imported by `migrations.ts`. +* Automatic execution on version upgrade. +* Schema version tracked in options table. \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Database structure/attachments.md b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/attachments.md similarity index 90% rename from docs/Developer Guide/Developer Guide/Architecture/Database structure/attachments.md rename to docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/attachments.md index 4312832dd..09ae80d4b 100644 --- a/docs/Developer Guide/Developer Guide/Architecture/Database structure/attachments.md +++ b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/attachments.md @@ -6,11 +6,11 @@ | `role` | Text | Non-null | | The role of the attachment: `image` for images that are attached to a note, `file` for uploaded files. | | `mime` | Text | Non-null | | The MIME type of the attachment (e.g. `image/png`) | | `title` | Text | Non-null | | The title of the attachment. | -| `isProtected` | Integer | Non-null | 0 | `1` if the entity is [protected](../Protected%20entities.md), `0` otherwise. | +| `isProtected` | Integer | Non-null | 0 | `1` if the entity is [protected](../../../Concepts/Protected%20entities.md), `0` otherwise. | | `position` | Integer | Non-null | 0 | Not sure where the position is relevant for attachments (saw it with values of 10 and 0). | | `blobId` | Text | Nullable | `null` | The corresponding `blobId` from the blobs table. | | `dateModified` | Text | Non-null | | Localized modification date (e.g. `2023-11-08 18:43:44.204+0200`) | | `utcDateModified` | Text | Non-null | | Modification date in UTC format (e.g. `2023-11-08 16:43:44.204Z`) | | `utcDateScheduledForErasure` | Text | Nullable | `null` | | -| `isDeleted` | Integer | Non-null | | `1` if the entity is [deleted](../Deleted%20notes.md), `0` otherwise. | +| `isDeleted` | Integer | Non-null | | `1` if the entity is [deleted](../../../Concepts/Deleted%20notes.md), `0` otherwise. | | `deleteId` | Text | Nullable | `null` | | \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Database structure/attributes.md b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/attributes.md similarity index 84% rename from docs/Developer Guide/Developer Guide/Architecture/Database structure/attributes.md rename to docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/attributes.md index 959073ec8..f1cbb99c4 100644 --- a/docs/Developer Guide/Developer Guide/Architecture/Database structure/attributes.md +++ b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/attributes.md @@ -1,2 +1,2 @@ # attributes -

      Column NameData TypeNullityDefault valueDescription
      attributeIdTextNon-null Unique Id of the attribute (e.g. qhC1vzU4nwSE), can also have a special unique ID for Special notes (e.g. _lbToday_liconClass).
      noteIdTextNon-null The ID of the note this atttribute belongs to
      typeTextNon-null The type of attribute (label or relation).
      nameTextNon-null The name/key of the attribute.
      valueTextNon-null""
      • For label attributes, a free-form value of the attribute.
      • For relation attributes, the ID of the note the relation is pointing to.
      positionIntegerNon-null0The position of the attribute compared to the other attributes. Some predefined attributes such as originalFileName have a value of 1000.
      utcDateModifiedTextNon-null Modification date in UTC format (e.g. 2023-11-08 16:43:44.204Z)
      isDeletedIntegerNon-null 1 if the entity is deleted, 0 otherwise.
      deleteIdTextNullablenull 
      isInheritableIntegerNullable0 
      \ No newline at end of file +
      Column NameData TypeNullityDefault valueDescription
      attributeIdTextNon-null Unique Id of the attribute (e.g. qhC1vzU4nwSE), can also have a special unique ID for Special notes (e.g. _lbToday_liconClass).
      noteIdTextNon-null The ID of the note this atttribute belongs to
      typeTextNon-null The type of attribute (label or relation).
      nameTextNon-null The name/key of the attribute.
      valueTextNon-null""
      • For label attributes, a free-form value of the attribute.
      • For relation attributes, the ID of the note the relation is pointing to.
      positionIntegerNon-null0The position of the attribute compared to the other attributes. Some predefined attributes such as originalFileName have a value of 1000.
      utcDateModifiedTextNon-null Modification date in UTC format (e.g. 2023-11-08 16:43:44.204Z)
      isDeletedIntegerNon-null 1 if the entity is deleted, 0 otherwise.
      deleteIdTextNullablenull 
      isInheritableIntegerNullable0 
      \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Database structure/blobs.md b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/blobs.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Database structure/blobs.md rename to docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/blobs.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Database structure/branches.md b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/branches.md similarity index 82% rename from docs/Developer Guide/Developer Guide/Architecture/Database structure/branches.md rename to docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/branches.md index d9b12dea5..18cf29090 100644 --- a/docs/Developer Guide/Developer Guide/Architecture/Database structure/branches.md +++ b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/branches.md @@ -5,8 +5,8 @@ | `noteId` | Text | Non-null | | The ID of the [note](notes.md). | | `parentNoteId` | Text | Non-null | | The ID of the parent [note](notes.md) the note belongs to. | | `notePosition` | Integer | Non-null | | The position of the branch within the same level of hierarchy, the value is usually a multiple of 10. | -| `prefix` | Text | Nullable | | The [branch prefix](../Branch%20prefixes.md) if any, or `NULL` otherwise. | +| `prefix` | Text | Nullable | | The [branch prefix](../../../Concepts/Branch%20prefixes.md) if any, or `NULL` otherwise. | | `isExpanded` | Integer | Non-null | 0 | Whether the branch should appear expanded (its children shown) to the user. | -| `isDeleted` | Integer | Non-null | 0 | `1` if the entity is [deleted](../Deleted%20notes.md), `0` otherwise. | +| `isDeleted` | Integer | Non-null | 0 | `1` if the entity is [deleted](../../../Concepts/Deleted%20notes.md), `0` otherwise. | | `deleteId` | Text | Nullable | `null` | | | `utcDateModified` | Text | Non-null | | Modification date in UTC format (e.g. `2023-11-08 16:43:44.204Z`) | \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Database structure/entity_changes.md b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/entity_changes.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Database structure/entity_changes.md rename to docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/entity_changes.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Database structure/etapi_tokens.md b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/etapi_tokens.md similarity index 90% rename from docs/Developer Guide/Developer Guide/Architecture/Database structure/etapi_tokens.md rename to docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/etapi_tokens.md index 25fe4e5da..3f11dffa9 100644 --- a/docs/Developer Guide/Developer Guide/Architecture/Database structure/etapi_tokens.md +++ b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/etapi_tokens.md @@ -6,4 +6,4 @@ | `tokenHash` | Text | Non-null | | The token itself. | | `utcDateCreated` | Text | Non-null | | Creation date in UTC format (e.g. `2023-11-08 16:43:44.204Z`) | | `utcDateModified` | Text | Non-null | | Modification date in UTC format (e.g. `2023-11-08 16:43:44.204Z`) | -| `isDeleted` | Integer | Non-null | 0 | `1` if the entity is [deleted](../Deleted%20notes.md), `0` otherwise. | \ No newline at end of file +| `isDeleted` | Integer | Non-null | 0 | `1` if the entity is [deleted](../../../Concepts/Deleted%20notes.md), `0` otherwise. | \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Database structure/notes.md b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/notes.md similarity index 90% rename from docs/Developer Guide/Developer Guide/Architecture/Database structure/notes.md rename to docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/notes.md index 572c7f6db..19cb4a322 100644 --- a/docs/Developer Guide/Developer Guide/Architecture/Database structure/notes.md +++ b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/notes.md @@ -3,10 +3,10 @@ | --- | --- | --- | --- | --- | | `noteId` | Text | Non-null | | The unique ID of the note (e.g. `2LJrKqIhr0Pe`). | | `title` | Text | Non-null | `"note"` | The title of the note, as defined by the user. | -| `isProtected` | Integer | Non-null | 0 | `1` if the entity is [protected](../Protected%20entities.md), `0` otherwise. | +| `isProtected` | Integer | Non-null | 0 | `1` if the entity is [protected](../../../Concepts/Protected%20entities.md), `0` otherwise. | | `type` | Text | Non-null | `"text"` | The type of note (i.e. `text`, `file`, `code`, `relationMap`, `mermaid`, `canvas`). | | `mime` | Text | Non-null | `"text/html"` | The MIME type of the note (e.g. `text/html`).. Note that it can be an empty string in some circumstances, but not null. | -| `isDeleted` | Integer | Nullable | 0 | `1` if the entity is [deleted](../Deleted%20notes.md), `0` otherwise. | +| `isDeleted` | Integer | Nullable | 0 | `1` if the entity is [deleted](../../../Concepts/Deleted%20notes.md), `0` otherwise. | | `deleteId` | Text | Non-null | `null` | | | `dateCreated` | Text | Non-null | | Localized creation date (e.g. `2023-11-08 18:43:44.204+0200`) | | `dateModified` | Text | Non-null | | Localized modification date (e.g. `2023-11-08 18:43:44.204+0200`) | diff --git a/docs/Developer Guide/Developer Guide/Architecture/Database structure/options.md b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/options.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Database structure/options.md rename to docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/options.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Database structure/recent_notes.md b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/recent_notes.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Database structure/recent_notes.md rename to docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/recent_notes.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Database structure/revisions.md b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/revisions.md similarity index 94% rename from docs/Developer Guide/Developer Guide/Architecture/Database structure/revisions.md rename to docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/revisions.md index 210e05356..fef9ed1ed 100644 --- a/docs/Developer Guide/Developer Guide/Architecture/Database structure/revisions.md +++ b/docs/Developer Guide/Developer Guide/Architecture/Database/Database structure/revisions.md @@ -6,7 +6,7 @@ | `type` | Text | Non-null | `""` | The type of note (i.e. `text`, `file`, `code`, `relationMap`, `mermaid`, `canvas`). | | `mime` | Text | Non-null | `""` | The MIME type of the note (e.g. `text/html`). | | `title` | Text | Non-null | | The title of the note, as defined by the user. | -| `isProtected` | Integer | Non-null | 0 | `1` if the entity is [protected](../Protected%20entities.md), `0` otherwise. | +| `isProtected` | Integer | Non-null | 0 | `1` if the entity is [protected](../../../Concepts/Protected%20entities.md), `0` otherwise. | | `blobId` | Text | Nullable | `null` | The corresponding ID from blobs. Although it can theoretically be `NULL`, haven't found any such note yet. | | `utcDateLastEdited` | Text | Non-null | | **Not sure how it differs from modification date.** | | `utcDateCreated` | Text | Non-null | | Creation date in UTC format (e.g. `2023-11-08 16:43:44.204Z`) | diff --git a/docs/Developer Guide/Developer Guide/Architecture/Protected entities.md b/docs/Developer Guide/Developer Guide/Architecture/Protected entities.md deleted file mode 100644 index f7d4e5b2e..000000000 --- a/docs/Developer Guide/Developer Guide/Architecture/Protected entities.md +++ /dev/null @@ -1,6 +0,0 @@ -# Protected entities -The following entities can be made protected, via their `isProtected` flag: - -* attachments -* notes -* revisions \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Backlinks.md b/docs/Developer Guide/Developer Guide/Concepts/Backlinks.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Backlinks.md rename to docs/Developer Guide/Developer Guide/Concepts/Backlinks.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Branch prefixes.md b/docs/Developer Guide/Developer Guide/Concepts/Branch prefixes.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Branch prefixes.md rename to docs/Developer Guide/Developer Guide/Concepts/Branch prefixes.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/CI/1_Main_image.png b/docs/Developer Guide/Developer Guide/Concepts/CI/1_Main_image.png similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/CI/1_Main_image.png rename to docs/Developer Guide/Developer Guide/Concepts/CI/1_Main_image.png diff --git a/docs/Developer Guide/Developer Guide/Architecture/CI/Main.md b/docs/Developer Guide/Developer Guide/Concepts/CI/Main.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/CI/Main.md rename to docs/Developer Guide/Developer Guide/Concepts/CI/Main.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/CI/Main_image.png b/docs/Developer Guide/Developer Guide/Concepts/CI/Main_image.png similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/CI/Main_image.png rename to docs/Developer Guide/Developer Guide/Concepts/CI/Main_image.png diff --git a/docs/Developer Guide/Developer Guide/Concepts/Cache.md b/docs/Developer Guide/Developer Guide/Concepts/Cache.md new file mode 100644 index 000000000..91a6f8b6d --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Concepts/Cache.md @@ -0,0 +1,111 @@ +# Cache +### Three-Layer Cache System + +Trilium implements a sophisticated **three-tier caching system** to optimize performance and enable offline functionality: + +#### 1\. Becca (Backend Cache) + +Located at: `apps/server/src/becca/` + +```typescript +// Becca caches all entities in memory +class Becca { + notes: Record + branches: Record + attributes: Record + attachments: Record + // ... other entity collections +} +``` + +**Responsibilities:** + +* Server-side entity cache +* Maintains complete note tree in memory +* Handles entity relationships and integrity +* Provides fast lookups without database queries +* Manages entity lifecycle (create, update, delete) + +**Key Files:** + +* `becca.ts` - Main cache instance +* `becca_loader.ts` - Loads entities from database +* `becca_service.ts` - Cache management operations +* `entities/` - Entity classes (BNote, BBranch, etc.) + +#### 2\. Froca (Frontend Cache) + +Located at: `apps/client/src/services/froca.ts` + +```typescript +// Froca is a read-only mirror of backend data +class Froca { + notes: Record + branches: Record + attributes: Record + // ... other entity collections +} +``` + +**Responsibilities:** + +* Frontend read-only cache +* Lazy loading of note tree +* Minimizes API calls +* Enables fast UI rendering +* Synchronizes with backend via WebSocket + +**Loading Strategy:** + +* Initial load: root notes and immediate children +* Lazy load: notes loaded when accessed +* When note is loaded, all parent and child branches load +* Deleted entities tracked via missing branches + +#### 3\. Shaca (Share Cache) + +Located at: `apps/server/src/share/` + +**Responsibilities:** + +* Optimized cache for shared/published notes +* Handles public note access without authentication +* Performance-optimized for high-traffic scenarios +* Separate from main Becca to isolate concerns + +### Cache Invalidation + +**Server-Side:** + +* Entities automatically update cache on save +* WebSocket broadcasts changes to all clients +* Synchronization updates trigger cache refresh + +**Client-Side:** + +* WebSocket listeners update Froca +* Manual reload via `froca.loadSubTree(noteId)` +* Full reload on protected session changes + +### Cache Consistency + +**Entity Change Tracking:** + +```typescript +// Every entity modification tracked +entity_changes ( + entityName: 'notes', + entityId: 'note123', + hash: 'abc...', + changeId: 'change456', + utcDateChanged: '2025-11-02...' +) +``` + +**Sync Protocol:** + +1. Client requests changes since last sync +2. Server returns entity\_changes records +3. Client applies changes to Froca +4. Client sends local changes to server +5. Server updates Becca and database \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Deleted notes.md b/docs/Developer Guide/Developer Guide/Concepts/Deleted notes.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Deleted notes.md rename to docs/Developer Guide/Developer Guide/Concepts/Deleted notes.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Demo document.md b/docs/Developer Guide/Developer Guide/Concepts/Demo document.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Demo document.md rename to docs/Developer Guide/Developer Guide/Concepts/Demo document.md diff --git a/docs/Developer Guide/Developer Guide/Concepts/Entities.md b/docs/Developer Guide/Developer Guide/Concepts/Entities.md new file mode 100644 index 000000000..56d954c94 --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Concepts/Entities.md @@ -0,0 +1,109 @@ +# Entities +### Entity System + +Trilium's data model is based on five core entities: + +```mermaid +graph TD + Note[Note
      BNote] + Branch[Branch
      BBranch] + Attribute[Attribute
      BAttribute] + Revision[Revision
      BRevision] + Attachment[Attachment
      BAttachment] + + Note -->|linked by| Branch + Note -.->|metadata| Attribute + Branch -->|creates| Revision + Note -->|has| Attachment + + style Note fill:#e1f5ff + style Branch fill:#fff4e1 + style Attribute fill:#ffe1f5 + style Revision fill:#f5ffe1 + style Attachment fill:#ffe1e1 +``` + +#### Entity Definitions + +**1\. BNote** (`apps/server/src/becca/entities/bnote.ts`) + +* Represents a note with title, content, and metadata +* Type can be: text, code, file, image, canvas, mermaid, etc. +* Contains content via blob reference +* Can be protected (encrypted) +* Has creation and modification timestamps + +**2\. BBranch** (`apps/server/src/becca/entities/bbranch.ts`) + +* Represents parent-child relationship between notes +* Enables note cloning (multiple parents) +* Contains positioning information +* Has optional prefix for customization +* Tracks expansion state in tree + +**3\. BAttribute** (`apps/server/src/becca/entities/battribute.ts`) + +* Key-value metadata attached to notes +* Two types: labels (tags) and relations (links) +* Can be inheritable to child notes +* Used for search, organization, and scripting +* Supports promoted attributes (displayed prominently) + +**4\. BRevision** (`apps/server/src/becca/entities/brevision.ts`) + +* Stores historical versions of note content +* Automatic versioning on edits +* Retains title, type, and content +* Enables note history browsing and restoration + +**5\. BAttachment** (`apps/server/src/becca/entities/battachment.ts`) + +* File attachments linked to notes +* Has owner (note), role, and mime type +* Content stored in blobs +* Can be protected (encrypted) + +**6\. BBlob** (`apps/server/src/becca/entities/bblob.ts`) + +* Binary large object storage +* Stores actual note content and attachments +* Referenced by notes, revisions, and attachments +* Supports encryption for protected content + +### Widget-Based UI + +The frontend uses a **widget system** for modular, reusable UI components. + +Located at: `apps/client/src/widgets/` + +```typescript +// Widget Hierarchy +BasicWidget +├── NoteContextAwareWidget (responds to note changes) +│ ├── RightPanelWidget (displayed in right sidebar) +│ └── Type-specific widgets +├── Container widgets (tabs, ribbons, etc.) +└── Specialized widgets (search, calendar, etc.) +``` + +**Base Classes:** + +1. **BasicWidget** (`basic_widget.ts`) + * Base class for all UI components + * Lifecycle: construction → rendering → events → destruction + * Handles DOM manipulation + * Event subscription management + * Child widget management +2. **NoteContextAwareWidget** (`note_context_aware_widget.ts`) + * Extends BasicWidget + * Automatically updates when active note changes + * Accesses current note context + * Used for note-dependent UI +3. **RightPanelWidget** + * Widgets displayed in right sidebar + * Collapsible sections + * Context-specific tools and information + +**Type-Specific Widgets:** + +Each note type has a dedicated widget, which are located in `apps/client/src/widgets/type_widgets`. \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Hidden notes.md b/docs/Developer Guide/Developer Guide/Concepts/Hidden notes.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Hidden notes.md rename to docs/Developer Guide/Developer Guide/Concepts/Hidden notes.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Icons.md b/docs/Developer Guide/Developer Guide/Concepts/Icons.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Icons.md rename to docs/Developer Guide/Developer Guide/Concepts/Icons.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Internationalisation Translat.md b/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translat.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Internationalisation Translat.md rename to docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translat.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Internationalisation Translations/Guidelines.md b/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Guidelines.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Internationalisation Translations/Guidelines.md rename to docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Guidelines.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Internationalisation Translations/Server translations.md b/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Server translations.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Internationalisation Translations/Server translations.md rename to docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Server translations.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Internationalisation Translations/i18n-ally.md b/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/i18n-ally.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Internationalisation Translations/i18n-ally.md rename to docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/i18n-ally.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Launchers.md b/docs/Developer Guide/Developer Guide/Concepts/Launchers.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Launchers.md rename to docs/Developer Guide/Developer Guide/Concepts/Launchers.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Note Revisions.md b/docs/Developer Guide/Developer Guide/Concepts/Note Revisions.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Note Revisions.md rename to docs/Developer Guide/Developer Guide/Concepts/Note Revisions.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/Copy image reference to the cl.md b/docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/Copy image reference to the cl.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/Copy image reference to the cl.md rename to docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/Copy image reference to the cl.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/Export diagram as SVG.md b/docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/Export diagram as SVG.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/Export diagram as SVG.md rename to docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/Export diagram as SVG.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/First steps.md b/docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/First steps.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/First steps.md rename to docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/First steps.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/First steps/mind_map.js b/docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/First steps/mind_map.js similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/First steps/mind_map.js rename to docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/First steps/mind_map.js diff --git a/docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/Loading data.md b/docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/Loading data.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/Loading data.md rename to docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/Loading data.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/Note type checklist.md b/docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/Note type checklist.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/Note type checklist.md rename to docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/Note type checklist.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/SVG rendering.md b/docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/SVG rendering.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/SVG rendering.md rename to docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/SVG rendering.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/Saving data via spaced update.md b/docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/Saving data via spaced update.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Note Types/Adding a new note type/Saving data via spaced update.md rename to docs/Developer Guide/Developer Guide/Concepts/Note Types/Adding a new note type/Saving data via spaced update.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Options.md b/docs/Developer Guide/Developer Guide/Concepts/Options.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Options.md rename to docs/Developer Guide/Developer Guide/Concepts/Options.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Options/Creating a new option.md b/docs/Developer Guide/Developer Guide/Concepts/Options/Creating a new option.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Options/Creating a new option.md rename to docs/Developer Guide/Developer Guide/Concepts/Options/Creating a new option.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Printing and exporting to PDF.md b/docs/Developer Guide/Developer Guide/Concepts/Printing and exporting to PDF.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Printing and exporting to PDF.md rename to docs/Developer Guide/Developer Guide/Concepts/Printing and exporting to PDF.md diff --git a/docs/Developer Guide/Developer Guide/Concepts/Protected entities.md b/docs/Developer Guide/Developer Guide/Concepts/Protected entities.md new file mode 100644 index 000000000..31d5250c0 --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Concepts/Protected entities.md @@ -0,0 +1,6 @@ +# Protected entities +The following entities can be made protected, via their `isProtected` flag: + +* attachments +* notes +* revisions \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Share.md b/docs/Developer Guide/Developer Guide/Concepts/Share.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Share.md rename to docs/Developer Guide/Developer Guide/Concepts/Share.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Synchronisation/Content hashing.md b/docs/Developer Guide/Developer Guide/Concepts/Synchronisation/Content hashing.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Synchronisation/Content hashing.md rename to docs/Developer Guide/Developer Guide/Concepts/Synchronisation/Content hashing.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Syntax highlighting.md b/docs/Developer Guide/Developer Guide/Concepts/Syntax highlighting.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Syntax highlighting.md rename to docs/Developer Guide/Developer Guide/Concepts/Syntax highlighting.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Themes.md b/docs/Developer Guide/Developer Guide/Concepts/Themes.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Themes.md rename to docs/Developer Guide/Developer Guide/Concepts/Themes.md diff --git a/docs/Developer Guide/Developer Guide/Documentation.md b/docs/Developer Guide/Developer Guide/Documentation.md index 4874aaa1d..611bdd69a 100644 --- a/docs/Developer Guide/Developer Guide/Documentation.md +++ b/docs/Developer Guide/Developer Guide/Documentation.md @@ -1,5 +1,5 @@ # Documentation -There are multiple types of documentation for Trilium: +There are multiple types of documentation for Trilium: * The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing F1. * The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers. diff --git a/docs/Developer Guide/Developer Guide/Environment Setup.md b/docs/Developer Guide/Developer Guide/Environment Setup.md index 95d14cd50..bf7f44adb 100644 --- a/docs/Developer Guide/Developer Guide/Environment Setup.md +++ b/docs/Developer Guide/Developer Guide/Environment Setup.md @@ -33,4 +33,4 @@ Run `pnpm i` at the top of the `Notes` repository to install the dependencies. Our recommended IDE for working on Trilium is Visual Studio Code (or VSCodium if you are looking for a fully open-source alternative). -By default we include a number of suggested extensions which should appear when opening the repository in VS Code. Most of the extensions are for integrating various technologies we are using such as Playwright and Vitest for testing or for Internationalisation / Translations. \ No newline at end of file +By default we include a number of suggested extensions which should appear when opening the repository in VS Code. Most of the extensions are for integrating various technologies we are using such as Playwright and Vitest for testing or for Internationalisation / Translations. \ No newline at end of file diff --git a/docs/User Guide/!!!meta.json b/docs/User Guide/!!!meta.json index a38998354..0a573e808 100644 --- a/docs/User Guide/!!!meta.json +++ b/docs/User Guide/!!!meta.json @@ -14616,21 +14616,21 @@ { "type": "relation", "name": "internalLink", - "value": "zEY4DaJG4YT5", + "value": "GLks18SNjxmC", "isInheritable": false, "position": 10 }, { "type": "relation", "name": "internalLink", - "value": "SynTBQiBsdYJ", + "value": "zEY4DaJG4YT5", "isInheritable": false, "position": 20 }, { "type": "relation", "name": "internalLink", - "value": "GLks18SNjxmC", + "value": "SynTBQiBsdYJ", "isInheritable": false, "position": 30 }, @@ -14787,19 +14787,19 @@ "isInheritable": false, "position": 30 }, - { - "type": "label", - "name": "shareAlias", - "value": "widget-basics", - "isInheritable": false, - "position": 20 - }, { "type": "relation", "name": "internalLink", "value": "s8alTXmpFR61", "isInheritable": false, "position": 40 + }, + { + "type": "label", + "name": "shareAlias", + "value": "widget-basics", + "isInheritable": false, + "position": 20 } ], "format": "markdown", @@ -14949,21 +14949,21 @@ { "type": "relation", "name": "internalLink", - "value": "m1lbrzyKDaRB", + "value": "yIhgI5H7A2Sm", "isInheritable": false, "position": 50 }, { "type": "relation", "name": "internalLink", - "value": "s8alTXmpFR61", + "value": "m1lbrzyKDaRB", "isInheritable": false, "position": 60 }, { "type": "relation", "name": "internalLink", - "value": "yIhgI5H7A2Sm", + "value": "s8alTXmpFR61", "isInheritable": false, "position": 70 }, From 7369f9d532de998e7f3c836c2537193278dced60 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 4 Nov 2025 17:24:38 +0200 Subject: [PATCH 6/8] docs(dev): integrate architecture guide --- docs/ARCHITECTURE.md | 279 ------------------ docs/Developer Guide/!!!meta.json | 133 +++++++-- docs/Developer Guide/Developer Guide.md | 13 +- .../Developer Guide/Architecture.md | 7 +- .../Developer Guide/Architecture/API.md | 2 +- .../Arhitecture Decision Records.md | 62 ++++ .../Architecture/Security Architecture.md | 79 +++++ .../Developer Guide/Concepts/Entities.md | 2 +- .../Developer Guide/Documentation.md | 2 +- .../Developer Guide/Testing.md | 46 ++- .../Testing/End-to-end tests.md | 13 +- .../Developer Guide/Testing/Unit tests.md | 7 + docs/User Guide/!!!meta.json | 42 +++ docs/User Guide/User Guide.md | 20 +- 14 files changed, 392 insertions(+), 315 deletions(-) delete mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/Developer Guide/Developer Guide/Architecture/Arhitecture Decision Records.md create mode 100644 docs/Developer Guide/Developer Guide/Architecture/Security Architecture.md create mode 100644 docs/Developer Guide/Developer Guide/Testing/Unit tests.md diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md deleted file mode 100644 index 1c1d6638b..000000000 --- a/docs/ARCHITECTURE.md +++ /dev/null @@ -1,279 +0,0 @@ -## API Architecture - -## Build System - -### Package Manager: pnpm - -**Why pnpm:** -- Fast, disk-efficient -- Strict dependency isolation -- Native monorepo support via workspaces -- Patch package support - -**Workspace Configuration:** -```yaml -# pnpm-workspace.yaml -packages: - - 'apps/*' - - 'packages/*' -``` - -### Build Tools - -**Vite** (Development & Production) -- Fast HMR for development -- Optimized production builds -- Asset handling -- Plugin ecosystem - -**ESBuild** (TypeScript compilation) -- Fast TypeScript transpilation -- Bundling support -- Minification - -**TypeScript** -- Project references for monorepo -- Strict type checking -- Shared `tsconfig.base.json` - -### Build Scripts - -**Root `package.json` scripts:** -```json -{ - "server:start": "pnpm run --filter server dev", - "server:build": "pnpm run --filter server build", - "client:build": "pnpm run --filter client build", - "desktop:build": "pnpm run --filter desktop build", - "test:all": "pnpm test:parallel && pnpm test:sequential" -} -``` - -### Build Process - -**Development:** -```bash -pnpm install # Install dependencies -pnpm server:start # Start dev server (port 8080) -# or -pnpm desktop:start # Start Electron dev -``` - -**Production (Server):** -```bash -pnpm server:build # Build server + client -node apps/server/dist/main.js -``` - -**Production (Desktop):** -```bash -pnpm desktop:build # Build Electron app -# Creates distributable in apps/desktop/out/make/ -``` - -**Docker:** -```bash -docker build -t trilium . -docker run -p 8080:8080 trilium -``` - -### Asset Pipeline - -**Client Assets:** -- Entry: `apps/client/src/index.html` -- Bundled by Vite -- Output: `apps/client/dist/` - -**Server Static:** -- Serves client assets in production -- Public directory: `apps/server/public/` - -**Desktop:** -- Packages client assets -- Electron main process: `apps/desktop/src/main.ts` -- Electron renderer: loads client app - ---- - -## Testing Strategy - -### Test Organization - -**Parallel Tests** (can run simultaneously): -- Client tests -- Package tests -- E2E tests (isolated databases) - -**Sequential Tests** (shared resources): -- Server tests (shared database) -- CKEditor plugin tests - -### Test Frameworks - -- **Vitest** - Unit and integration tests -- **Playwright** - E2E tests -- **Happy-DOM** - DOM testing environment - -### Running Tests - -```bash -pnpm test:all # All tests -pnpm test:parallel # Fast parallel tests -pnpm test:sequential # Sequential tests only -pnpm coverage # With coverage reports -``` - -### Test Locations - -``` -apps/ -├── server/ -│ └── src/**/*.spec.ts # Server tests -├── client/ -│ └── src/**/*.spec.ts # Client tests -└── server-e2e/ - └── tests/**/*.spec.ts # E2E tests -``` - -### E2E Testing - -**Server E2E:** -- Tests full REST API -- Tests WebSocket functionality -- Tests sync protocol - -**Desktop E2E:** -- Playwright with Electron -- Tests full desktop app -- Screenshot comparison - ---- - -## Security Architecture - -### Encryption System - -**Per-Note Encryption:** -- Notes can be individually protected -- AES-256 encryption -- Password-derived encryption key (PBKDF2) -- Separate protected session management - -**Protected Session:** -- Time-limited access to protected notes -- Automatic timeout -- Re-authentication required -- Frontend: `protected_session.ts` -- Backend: `protected_session.ts` - -### Authentication - -**Password Auth:** -- PBKDF2 key derivation -- Salt per installation -- Hash verification - -**OpenID Connect:** -- External identity provider support -- OAuth 2.0 flow -- Configurable providers - -**TOTP (2FA):** -- Time-based one-time passwords -- QR code setup -- Backup codes - -### Authorization - -**Single-User Model:** -- Desktop: single user (owner) -- Server: single user per installation - -**Share Notes:** -- Public access without authentication -- Separate Shaca cache -- Read-only access - -### CSRF Protection - -**CSRF Tokens:** -- Required for state-changing operations -- Token in header or cookie -- Validation middleware - -### Input Sanitization - -**XSS Prevention:** -- DOMPurify for HTML sanitization -- CKEditor content filtering -- CSP headers - -**SQL Injection:** -- Parameterized queries only -- Better-sqlite3 prepared statements -- No string concatenation in SQL - -### Dependency Security - -**Vulnerability Scanning:** -- Renovate bot for updates -- npm audit integration -- Override vulnerable sub-dependencies - ---- - -## Related Documentation - -### User Documentation -- [User Guide](User%20Guide/User%20Guide/) - End-user features and usage -- [Installation Guide](User%20Guide/User%20Guide/Installation%20&%20Setup/) -- [Basic Concepts](User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/) - -### Developer Documentation -- [Developer Guide](Developer%20Guide/Developer%20Guide/) - Development setup -- [Environment Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) -- [Project Structure](Developer%20Guide/Developer%20Guide/Project%20Structure.md) -- [Adding Note Types](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Adding%20a%20new%20note%20type/) -- [Database Schema](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Database/) - -### API Documentation -- [Script API](Script%20API/) - User scripting API -- [ETAPI Documentation](https://triliumnext.github.io/Docs/Wiki/etapi) - External API - -### Additional Resources -- [CLAUDE.md](../CLAUDE.md) - AI assistant guidance -- [README.md](../README.md) - Project overview -- [SECURITY.md](../SECURITY.md) - Security policy - ---- - -## Appendices - -### Glossary - -- **Becca**: Backend Cache - server-side entity cache -- **Froca**: Frontend Cache - client-side entity mirror -- **Shaca**: Share Cache - cache for public shared notes -- **ETAPI**: External API for third-party integrations -- **Protected Note**: Encrypted note requiring password -- **Clone**: Note with multiple parent branches -- **Branch**: Parent-child relationship between notes -- **Attribute**: Metadata (label or relation) attached to note -- **Blob**: Binary large object containing note content - -### File Naming Conventions - -- `BEntity` - Backend entity (e.g., BNote, BBranch) -- `FEntity` - Frontend entity (e.g., FNote, FBranch) -- `*_widget.ts` - Widget classes -- `*_service.ts` - Service modules -- `*.spec.ts` - Test files -- `XXXX_*.sql` - Migration files - -### Architecture Decision Records - -For historical context on major architectural decisions, see: -- Migration to TypeScript monorepo -- Adoption of pnpm workspaces -- CKEditor 5 upgrade -- Entity change tracking system diff --git a/docs/Developer Guide/!!!meta.json b/docs/Developer Guide/!!!meta.json index a871c66f5..f19dbc16a 100644 --- a/docs/Developer Guide/!!!meta.json +++ b/docs/Developer Guide/!!!meta.json @@ -35,6 +35,20 @@ "value": "developer-guide", "isInheritable": false, "position": 30 + }, + { + "type": "relation", + "name": "internalLink", + "value": "T2W7WCZrYZBU", + "isInheritable": false, + "position": 40 + }, + { + "type": "relation", + "name": "internalLink", + "value": "cxfTSHIUQtt2", + "isInheritable": false, + "position": 50 } ], "format": "markdown", @@ -450,14 +464,14 @@ { "type": "relation", "name": "internalLink", - "value": "h8AsuFjSD4fB", + "value": "DSkl8C325tEC", "isInheritable": false, "position": 10 }, { "type": "relation", "name": "internalLink", - "value": "DSkl8C325tEC", + "value": "h8AsuFjSD4fB", "isInheritable": false, "position": 20 }, @@ -781,6 +795,60 @@ "format": "markdown", "dataFileName": "API.md", "attachments": [] + }, + { + "isClone": false, + "noteId": "Vk4zD1Iirarg", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "Vk4zD1Iirarg" + ], + "title": "Arhitecture Decision Records", + "notePosition": 40, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "relation", + "name": "internalLink", + "value": "Jg7clqogFOyD", + "isInheritable": false, + "position": 20 + } + ], + "format": "markdown", + "dataFileName": "Arhitecture Decision Records.md", + "attachments": [] + }, + { + "isClone": false, + "noteId": "QW1MB7RZB5Gf", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "QW1MB7RZB5Gf" + ], + "title": "Security Architecture", + "notePosition": 50, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "label", + "name": "shareAlias", + "value": "security-architecture", + "isInheritable": false, + "position": 20 + } + ], + "format": "markdown", + "dataFileName": "Security Architecture.md", + "attachments": [] } ] }, @@ -1555,7 +1623,7 @@ "tM3rIZQzlum4" ], "title": "Deleted notes", - "notePosition": 70, + "notePosition": 50, "prefix": null, "isExpanded": false, "type": "text", @@ -1589,7 +1657,7 @@ "oLhKpfi2kGON" ], "title": "Demo document", - "notePosition": 80, + "notePosition": 60, "prefix": null, "isExpanded": false, "type": "text", @@ -1623,7 +1691,7 @@ "TiUll0Osoaz6" ], "title": "Entities", - "notePosition": 90, + "notePosition": 70, "prefix": null, "isExpanded": false, "type": "text", @@ -1657,7 +1725,7 @@ "UzRirf46Xi46" ], "title": "Hidden notes", - "notePosition": 100, + "notePosition": 80, "prefix": null, "isExpanded": false, "type": "text", @@ -1691,7 +1759,7 @@ "m2W35hwSDUeh" ], "title": "Icons", - "notePosition": 110, + "notePosition": 90, "prefix": null, "isExpanded": false, "type": "text", @@ -1700,14 +1768,14 @@ { "type": "relation", "name": "internalLink", - "value": "oLhKpfi2kGON", + "value": "PXzm2t3sCdsP", "isInheritable": false, "position": 10 }, { "type": "relation", "name": "internalLink", - "value": "PXzm2t3sCdsP", + "value": "oLhKpfi2kGON", "isInheritable": false, "position": 20 }, @@ -1732,7 +1800,7 @@ "TLXJwBDo8Rdv" ], "title": "Internationalisation / Translations", - "notePosition": 120, + "notePosition": 100, "prefix": null, "isExpanded": false, "type": "text", @@ -1867,7 +1935,7 @@ "Usiyzn9C4WFv" ], "title": "Launchers", - "notePosition": 130, + "notePosition": 110, "prefix": null, "isExpanded": false, "type": "text", @@ -1894,7 +1962,7 @@ "qjQNyaYXSNWu" ], "title": "Note Revisions", - "notePosition": 140, + "notePosition": 120, "prefix": null, "isExpanded": false, "type": "text", @@ -1928,7 +1996,7 @@ "7RBJMqVz2EsJ" ], "title": "Note Types", - "notePosition": 150, + "notePosition": 130, "prefix": null, "isExpanded": false, "type": "text", @@ -2262,7 +2330,7 @@ "6dC7ha5vjqqS" ], "title": "Options", - "notePosition": 160, + "notePosition": 140, "prefix": null, "isExpanded": false, "type": "text", @@ -2327,7 +2395,7 @@ "W0msUwLxm40d" ], "title": "Printing and exporting to PDF", - "notePosition": 170, + "notePosition": 150, "prefix": null, "isExpanded": false, "type": "text", @@ -2368,7 +2436,7 @@ "UvXpeSqfYc6d" ], "title": "Protected entities", - "notePosition": 180, + "notePosition": 160, "prefix": null, "isExpanded": false, "type": "text", @@ -2423,7 +2491,7 @@ "vphziLmQeQHY" ], "title": "Share", - "notePosition": 190, + "notePosition": 170, "prefix": null, "isExpanded": false, "type": "text", @@ -2457,7 +2525,7 @@ "n9wYW9nUTynV" ], "title": "Synchronisation", - "notePosition": 200, + "notePosition": 180, "prefix": null, "isExpanded": false, "type": "text", @@ -2521,7 +2589,7 @@ "k7RavjuXQt8z" ], "title": "Syntax highlighting", - "notePosition": 210, + "notePosition": 190, "prefix": null, "isExpanded": false, "type": "text", @@ -2555,7 +2623,7 @@ "7BCukQTCm7fv" ], "title": "Themes", - "notePosition": 220, + "notePosition": 200, "prefix": null, "isExpanded": false, "type": "text", @@ -2712,6 +2780,25 @@ "attachments": [], "dirFileName": "Testing", "children": [ + { + "isClone": false, + "noteId": "ETeT5YO61DAW", + "notePath": [ + "jdjRLhLV3TtI", + "dtKC3FmoWOrv", + "ETeT5YO61DAW" + ], + "title": "Unit tests", + "notePosition": 10, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [], + "format": "markdown", + "dataFileName": "Unit tests.md", + "attachments": [] + }, { "isClone": false, "noteId": "C5MUQczZ5R9N", @@ -2721,7 +2808,7 @@ "C5MUQczZ5R9N" ], "title": "Integration testing", - "notePosition": 10, + "notePosition": 20, "prefix": null, "isExpanded": false, "type": "text", @@ -2748,7 +2835,7 @@ "bIfKwfCnqpeI" ], "title": "Test database", - "notePosition": 20, + "notePosition": 30, "prefix": null, "isExpanded": false, "type": "text", @@ -2775,7 +2862,7 @@ "w6gMvKh0UAVT" ], "title": "End-to-end tests", - "notePosition": 30, + "notePosition": 40, "prefix": null, "isExpanded": false, "type": "text", diff --git a/docs/Developer Guide/Developer Guide.md b/docs/Developer Guide/Developer Guide.md index 2dd9f42de..8f5721b30 100644 --- a/docs/Developer Guide/Developer Guide.md +++ b/docs/Developer Guide/Developer Guide.md @@ -1,4 +1,15 @@ # Developer Guide This documentation is intended for developers planning to implement new features or maintain the Trilium Notes application, as it describes the architecture of the application. -For the user-facing documentation, including how to write scripts and the various APIs, consult the [user guide](https://docs.triliumnotes.org/user-guide/) instead. \ No newline at end of file +For the user-facing documentation, including how to write scripts and the various APIs, consult the [user guide](https://docs.triliumnotes.org/user-guide/) instead. + +### Quick links + +* Environment Setup +* Project Structure + +### External links + +* The [Trilium Notes website](https://triliumnotes.org/), for a quick presentation of the application. +* [User Guide](https://docs.triliumnotes.org/user-guide/), to understand the concepts of the application itself. +* [GitHub Repository (TriliumNext/Trilium)](https://github.com/TriliumNext/Trilium/) \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture.md b/docs/Developer Guide/Developer Guide/Architecture.md index a11d27274..48a91fcde 100644 --- a/docs/Developer Guide/Developer Guide/Architecture.md +++ b/docs/Developer Guide/Developer Guide/Architecture.md @@ -15,7 +15,10 @@ Trilium Notes is a hierarchical note-taking application built as a TypeScript mo * **Runtime**: Node.js (backend), Browser/Electron (frontend) * **Language**: TypeScript, JavaScript * **Database**: SQLite (better-sqlite3) -* **Build Tools**: Vite, ESBuild, pnpm +* **Build Tools**: + * Client: Vite, + * Server: ESBuild (bundling) + * Package manager: pnpm * **UI Framework**: Custom widget-based system (vanilla HTML, CSS & JavaScript + jQuery), in the process of converting to React/Preact. * **Rich Text**: CKEditor 5 (customized) * **Code Editing**: CodeMirror 6 @@ -26,7 +29,7 @@ Trilium Notes is a hierarchical note-taking application built as a TypeScript mo Trilium follows a **client-server architecture** even in desktop mode, where Electron runs both the backend server and frontend client within the same process. -```mermaid +``` graph TB subgraph Frontend Widgets[Widgets
      System] diff --git a/docs/Developer Guide/Developer Guide/Architecture/API.md b/docs/Developer Guide/Developer Guide/Architecture/API.md index 57fc8c164..92f6fb813 100644 --- a/docs/Developer Guide/Developer Guide/Architecture/API.md +++ b/docs/Developer Guide/Developer Guide/Architecture/API.md @@ -43,7 +43,7 @@ Located at: `apps/server/src/etapi/` **Example:** -```sh +``` curl -H "Authorization: YOUR_TOKEN" \ https://trilium.example.com/etapi/notes/noteId ``` diff --git a/docs/Developer Guide/Developer Guide/Architecture/Arhitecture Decision Records.md b/docs/Developer Guide/Developer Guide/Architecture/Arhitecture Decision Records.md new file mode 100644 index 000000000..c524642cd --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Architecture/Arhitecture Decision Records.md @@ -0,0 +1,62 @@ +# Arhitecture Decision Records +## 🚀 Future milestones + +* [Mobile](https://github.com/TriliumNext/Trilium/issues/7447) +* [Multi-user](https://github.com/TriliumNext/Trilium/issues/4956) + +## Aug 2025 - present: Port the client to React + +- [x] [Type widgets](https://github.com/TriliumNext/Trilium/pull/7044) +- [x] [Collections](https://github.com/TriliumNext/Trilium/pull/6837) +- [x] [Various widgets](https://github.com/TriliumNext/Trilium/pull/6830) +- [x] [Floating buttons](https://github.com/TriliumNext/Trilium/pull/6811) +- [x] [Settings](https://github.com/TriliumNext/Trilium/pull/6660) + +## Aug 2025 - Move away from NX + +We took the decision of moving away from the NX monorepo tool, due to: + +* Various issues with the cache, especially after an update of the NX dependencies which required periodical `nx reset` to get rid of. +* Various issues with memory and CPU consumption along the way, due to the NX daemon (including it remaining as a background process after closing the IDE). +* On Windows, almost always there was a freeze on every second build. +* Various hacks that were needed to achieve what we needed (especially for artifacts since NX would not copy assets if they were in `.gitignore` for some arbitrary reason and requiring a patch that made it difficult to maintain across updates). + +As a result, we decided to switch to… nothing. Why? + +* `pnpm` (which we were already using) covers the basic needs of a monorepo via workspaces on its own. +* Our client-side solution, Vite already supports navigating through projects without requiring built artifacts. This makes the build process slightly faster (especially cold starts) at a slighter bigger RAM consumption. +* ESBuild, on the server-side, also seems happy to go across projects without an issue. + +Apart from this: + +* In dev mode, the server now runs directly using `tsx` and not built and then run. This means that it'll run much faster. +* We're back to an architecture where the `server` and the `desktop` host their own Vite instance as a middleware. What this means that there is no `client:dev` and no separate port to handle. + * This makes it possible to easily test on mobile in dev mode, since there's a single port to access. + * The downside is that the initial start up time is longer while Vite is spinning up. Nevertheless, it's still slightly faster than it used to be anyway. +* No more asset copying, which should also improve performance. +* No more messing around with the native dependency of `better-sqlite3` that caused those dreaded mismatches when running between server and desktop. We have (hopefully) found a permanent solution for it that involves no user input. +* A decent solution was put in place to allow easier development on NixOS for the desktop application. +* The desktop version has also gained back the ability to automatically refresh the client when a change is made, including live changes for React components. + +Migration steps, as a developer: + +1. In VS Code, uninstall the NX Console unless you plan to use it for other projects. +2. Remove `.nx` at project level. +3. It's ideal to clean up all your `node_modules` in the project (do note that it's not just the top-level one, but also in `apps/client`, `apps/server`, `apps/desktop`, etc.). +4. Run a `pnpm i` to set up the new dependencies and the installation +5. Instead of `nx run server:serve`, now you can simply run `pnpm dev` while in `apps/server`, or `pnpm server:start` while in the root. +6. When first starting the server, it will take slightly longer than usual to see something on the screen since the dependencies are being rebuilt. Those are later cached so subsequent runs should work better. If you end up with a white screen, simply refresh the page a few times until it shows up correctly. + +## Apr 2025: NX-based monorepo + +* Goal: Restructure the application from a mix where the client was a subfolder within the server and other dependencies such as CKEditor were scattered in various repositories to a monorepo powered by NX. +* [Initial discussion](https://github.com/TriliumNext/Trilium/issues/4941) +* [Relevant PR](https://github.com/TriliumNext/Notes/pull/1773) + +## Dec 2024: Front-end conversion to TypeScript + +* [Relevant PRs on GitHub](https://github.com/TriliumNext/Notes/pulls?q=is%3Apr+is%3Aclosed+%22Port+frontend+to+TypeScript%22) + +## Apr 2024: Back-end conversion to TypeScript + +* [Relevant PRs on GitHub](https://github.com/TriliumNext/Notes/pulls?q=is%3Apr+%22convert+backend+to+typescript%22) \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Security Architecture.md b/docs/Developer Guide/Developer Guide/Architecture/Security Architecture.md new file mode 100644 index 000000000..ab0c88c06 --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Architecture/Security Architecture.md @@ -0,0 +1,79 @@ +# Security Architecture +### Encryption System + +**Per-Note Encryption:** + +* Notes can be individually protected +* AES-128-CBC encryption for encrypted notes. +* Separate protected session management + +**Protected Session:** + +* Time-limited access to protected notes +* Automatic timeout +* Re-authentication required +* Frontend: `protected_session.ts` +* Backend: `protected_session.ts` + +### Authentication + +**Password Auth:** + +* PBKDF2 key derivation +* Salt per installation +* Hash verification + +**OpenID Connect:** + +* External identity provider support +* OAuth 2.0 flow +* Configurable providers + +**TOTP (2FA):** + +* Time-based one-time passwords +* QR code setup +* Backup codes + +### Authorization + +**Single-User Model:** + +* Desktop: single user (owner) +* Server: single user per installation + +**Share Notes:** + +* Public access without authentication +* Separate Shaca cache +* Read-only access + +### CSRF Protection + +**CSRF Tokens:** + +* Required for state-changing operations +* Token in header or cookie +* Validation middleware + +### Input Sanitization + +**XSS Prevention:** + +* DOMPurify for HTML sanitization +* CKEditor content filtering +* CSP headers + +**SQL Injection:** + +* Parameterized queries only +* Better-sqlite3 prepared statements +* No string concatenation in SQL + +### Dependency Security + +**Vulnerability Scanning:** + +* Renovate bot for updates +* npm audit integration +* Override vulnerable sub-dependencies \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Concepts/Entities.md b/docs/Developer Guide/Developer Guide/Concepts/Entities.md index 56d954c94..faac90af7 100644 --- a/docs/Developer Guide/Developer Guide/Concepts/Entities.md +++ b/docs/Developer Guide/Developer Guide/Concepts/Entities.md @@ -3,7 +3,7 @@ Trilium's data model is based on five core entities: -```mermaid +``` graph TD Note[Note
      BNote] Branch[Branch
      BBranch] diff --git a/docs/Developer Guide/Developer Guide/Documentation.md b/docs/Developer Guide/Developer Guide/Documentation.md index 611bdd69a..4da87601a 100644 --- a/docs/Developer Guide/Developer Guide/Documentation.md +++ b/docs/Developer Guide/Developer Guide/Documentation.md @@ -1,5 +1,5 @@ # Documentation -There are multiple types of documentation for Trilium: +There are multiple types of documentation for Trilium: * The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing F1. * The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers. diff --git a/docs/Developer Guide/Developer Guide/Testing.md b/docs/Developer Guide/Developer Guide/Testing.md index a1f8a1983..570f52ded 100644 --- a/docs/Developer Guide/Developer Guide/Testing.md +++ b/docs/Developer Guide/Developer Guide/Testing.md @@ -1,4 +1,48 @@ # Testing +### Test Organization + +**Parallel Tests** (can run simultaneously): + +* Client tests +* Package tests +* E2E tests (isolated databases) + +**Sequential Tests** (shared resources): + +* Server tests (shared database) +* CKEditor plugin tests + +### Test Frameworks + +* **Vitest** - Unit and integration tests +* **Playwright** - E2E tests +* **Happy-DOM** - DOM testing environment + +## Test locations + +``` +apps/ +├── server/ +│ └── src/**/*.spec.ts # Server tests +├── client/ +│ └── src/**/*.spec.ts # Client tests +└── server-e2e/ +│ └── tests/**/*.spec.ts # E2E tests +└── desktop/ + └── e2e + └── tests/**/*.spec.ts # E2E tests +``` + +## Running tests + +At project root: + +``` +pnpm test:all # All tests +pnpm test:parallel # Fast parallel tests +pnpm test:sequential # Sequential tests only +``` + ## Unit testing and integration testing Using `vitest`, there are some unit and integration tests done for both the client and the server. @@ -27,4 +71,4 @@ These integration tests are run alongside unit tests. ## End-to-end testing -See e2e tests. \ No newline at end of file +See End-to-end tests. \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Testing/End-to-end tests.md b/docs/Developer Guide/Developer Guide/Testing/End-to-end tests.md index 387940158..60060adae 100644 --- a/docs/Developer Guide/Developer Guide/Testing/End-to-end tests.md +++ b/docs/Developer Guide/Developer Guide/Testing/End-to-end tests.md @@ -1,6 +1,15 @@ # End-to-end tests -* This tests both the client and the server, by running the server and then using Playwright to query the state of the page. -* These can be found in `apps/server-e2e` and `apps/desktop/e2e`. +**Server E2E:** + +* Tests the entire ETAPI. +* Tests WebSocket functionality + +**Desktop E2E:** + +* Playwright with Electron +* Tests some basic functionality such as creating a new document. + +These can be found in `apps/server-e2e` and `apps/desktop/e2e`. ## First-time run diff --git a/docs/Developer Guide/Developer Guide/Testing/Unit tests.md b/docs/Developer Guide/Developer Guide/Testing/Unit tests.md new file mode 100644 index 000000000..a009b8d92 --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Testing/Unit tests.md @@ -0,0 +1,7 @@ +# Unit tests +At project level: + +* `pnpm server:test` +* `pnpm client:test` + +Unit tests are stored in the same directory as the source code being tested, with the `.spec.ts` suffix. \ No newline at end of file diff --git a/docs/User Guide/!!!meta.json b/docs/User Guide/!!!meta.json index 0a573e808..86ddd9589 100644 --- a/docs/User Guide/!!!meta.json +++ b/docs/User Guide/!!!meta.json @@ -56,6 +56,48 @@ "value": "bx bx-help-circle", "isInheritable": false, "position": 30 + }, + { + "type": "relation", + "name": "internalLink", + "value": "poXkQfguuA0U", + "isInheritable": false, + "position": 40 + }, + { + "type": "relation", + "name": "internalLink", + "value": "WOcw2SLH6tbX", + "isInheritable": false, + "position": 50 + }, + { + "type": "relation", + "name": "internalLink", + "value": "gh7bpGYxajRS", + "isInheritable": false, + "position": 60 + }, + { + "type": "relation", + "name": "internalLink", + "value": "Q2z6av6JZVWm", + "isInheritable": false, + "position": 70 + }, + { + "type": "relation", + "name": "internalLink", + "value": "MEtfsqa5VwNi", + "isInheritable": false, + "position": 80 + }, + { + "type": "relation", + "name": "internalLink", + "value": "9qPsTWBorUhQ", + "isInheritable": false, + "position": 90 } ], "format": "markdown", diff --git a/docs/User Guide/User Guide.md b/docs/User Guide/User Guide.md index 0225eeccc..3e5537135 100644 --- a/docs/User Guide/User Guide.md +++ b/docs/User Guide/User Guide.md @@ -1,13 +1,25 @@ # User Guide Trilium is an open-source solution for note-taking and organizing a personal knowledge base. Use it locally on your desktop, or sync it with your self-hosted server to keep your notes everywhere you go. -For a quick overview of the application, visit our website at [triliumnotes.org](https://triliumnotes.org/). - > [!TIP] > The same documentation can be accessed locally from within the Trilium Notes application by pressing F1. ## Getting started 1. See Quick Start. -2. Understand Notes. -3. Browse through Collections. \ No newline at end of file +2. Go through Basic Concepts and Features. +3. Understand Notes. +4. Browse through Collections. + +## Quick links + +* Desktop Installation +* Server Installation +* Frontend API or Backend API +* [ETAPI reference](User%20Guide/Advanced%20Usage/ETAPI%20\(REST%20API\)/API%20Reference.dat) + +## External links + +* The [Trilium Notes website](https://triliumnotes.org/), for a quick presentation of the application. +* [Developer Guide](https://docs.triliumnotes.org/developer-guide/), to understand the architecture and processes behind the development of Trilium Notes. +* [GitHub Repository (TriliumNext/Trilium)](https://github.com/TriliumNext/Trilium/) \ No newline at end of file From 7131d44d03b45d43df8becbd0bc0232aa673b72f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 4 Nov 2025 18:16:20 +0200 Subject: [PATCH 7/8] docs(dev): integrate rest of the documentation --- docs/DATABASE.md | 736 ---------------- docs/Developer Guide/!!!meta.json | 168 ++-- .../Developer Guide/Architecture.md | 83 +- .../Architecture/{API.md => APIs.md} | 2 +- .../Backend.md | 0 .../Developer Guide/Architecture/Database.md | 2 +- .../Frontend.md | 0 .../Architecture/Security Architecture.md | 79 -- .../Developer Guide/Architecture/Security.md | 464 ++++++++++ .../Concepts/Synchronisation.md} | 323 +++---- docs/QUICK_REFERENCE.md | 155 ---- docs/SCRIPTING.md | 734 --------------- docs/SECURITY_ARCHITECTURE.md | 834 ------------------ docs/TECHNICAL_DOCUMENTATION.md | 423 --------- 14 files changed, 772 insertions(+), 3231 deletions(-) delete mode 100644 docs/DATABASE.md rename docs/Developer Guide/Developer Guide/Architecture/{API.md => APIs.md} (99%) rename docs/Developer Guide/Developer Guide/Architecture/{Client-server architecture => }/Backend.md (100%) rename docs/Developer Guide/Developer Guide/Architecture/{Client-server architecture => }/Frontend.md (100%) delete mode 100644 docs/Developer Guide/Developer Guide/Architecture/Security Architecture.md create mode 100644 docs/Developer Guide/Developer Guide/Architecture/Security.md rename docs/{SYNCHRONIZATION.md => Developer Guide/Developer Guide/Concepts/Synchronisation.md} (52%) delete mode 100644 docs/QUICK_REFERENCE.md delete mode 100644 docs/SCRIPTING.md delete mode 100644 docs/SECURITY_ARCHITECTURE.md delete mode 100644 docs/TECHNICAL_DOCUMENTATION.md diff --git a/docs/DATABASE.md b/docs/DATABASE.md deleted file mode 100644 index 52df42e0b..000000000 --- a/docs/DATABASE.md +++ /dev/null @@ -1,736 +0,0 @@ -# Trilium Database Architecture - -> **Related:** [ARCHITECTURE.md](ARCHITECTURE.md) | [Database Schema](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Database/) - -## Overview - -Trilium uses **SQLite** as its embedded database engine, providing a reliable, file-based storage system that requires no separate database server. The database stores all notes, their relationships, metadata, and configuration. - -## Database File - -**Location:** -- Desktop: `~/.local/share/trilium-data/document.db` (Linux/Mac) or `%APPDATA%/trilium-data/document.db` (Windows) -- Server: Configured via `TRILIUM_DATA_DIR` environment variable -- Docker: Mounted volume at `/home/node/trilium-data/` - -**Characteristics:** -- Single-file database -- Embedded (no server required) -- ACID compliant -- Cross-platform -- Supports up to 281 TB database size -- Efficient for 100k+ notes - -## Database Driver - -**Library:** `better-sqlite3` - -**Why better-sqlite3:** -- Native performance (C++ bindings) -- Synchronous API (simpler code) -- Prepared statements -- Transaction support -- Type safety - -**Usage:** -```typescript -// apps/server/src/services/sql.ts -import Database from 'better-sqlite3' - -const db = new Database('document.db') -const stmt = db.prepare('SELECT * FROM notes WHERE noteId = ?') -const note = stmt.get(noteId) -``` - -## Schema Overview - -Schema location: `apps/server/src/assets/db/schema.sql` - -**Entity Tables:** -- `notes` - Core note data -- `branches` - Tree relationships -- `attributes` - Metadata (labels/relations) -- `revisions` - Version history -- `attachments` - File attachments -- `blobs` - Binary content storage - -**System Tables:** -- `options` - Application configuration -- `entity_changes` - Change tracking for sync -- `recent_notes` - Recently accessed notes -- `etapi_tokens` - API authentication tokens -- `user_data` - User credentials -- `sessions` - Web session storage - -## Entity Tables - -### Notes Table - -```sql -CREATE TABLE notes ( - noteId TEXT NOT NULL PRIMARY KEY, - title TEXT NOT NULL DEFAULT "note", - isProtected INT NOT NULL DEFAULT 0, - type TEXT NOT NULL DEFAULT 'text', - mime TEXT NOT NULL DEFAULT 'text/html', - blobId TEXT DEFAULT NULL, - isDeleted INT NOT NULL DEFAULT 0, - deleteId TEXT DEFAULT NULL, - dateCreated TEXT NOT NULL, - dateModified TEXT NOT NULL, - utcDateCreated TEXT NOT NULL, - utcDateModified TEXT NOT NULL -); - --- Indexes for performance -CREATE INDEX IDX_notes_title ON notes (title); -CREATE INDEX IDX_notes_type ON notes (type); -CREATE INDEX IDX_notes_dateCreated ON notes (dateCreated); -CREATE INDEX IDX_notes_dateModified ON notes (dateModified); -CREATE INDEX IDX_notes_utcDateModified ON notes (utcDateModified); -CREATE INDEX IDX_notes_blobId ON notes (blobId); -``` - -**Field Descriptions:** - -| Field | Type | Description | -|-------|------|-------------| -| `noteId` | TEXT | Unique identifier (UUID or custom) | -| `title` | TEXT | Note title (displayed in tree) | -| `isProtected` | INT | 1 if encrypted, 0 if not | -| `type` | TEXT | Note type: text, code, file, image, etc. | -| `mime` | TEXT | MIME type: text/html, application/json, etc. | -| `blobId` | TEXT | Reference to content in blobs table | -| `isDeleted` | INT | Soft delete flag | -| `deleteId` | TEXT | Unique delete operation ID | -| `dateCreated` | TEXT | Creation date (local timezone) | -| `dateModified` | TEXT | Last modified (local timezone) | -| `utcDateCreated` | TEXT | Creation date (UTC) | -| `utcDateModified` | TEXT | Last modified (UTC) | - -**Note Types:** -- `text` - Rich text with HTML -- `code` - Source code -- `file` - Binary file -- `image` - Image file -- `search` - Saved search -- `render` - Custom HTML rendering -- `relation-map` - Relationship diagram -- `canvas` - Excalidraw drawing -- `mermaid` - Mermaid diagram -- `book` - Container for documentation -- `web-view` - Embedded web page -- `mindmap` - Mind map -- `geomap` - Geographical map - -### Branches Table - -```sql -CREATE TABLE branches ( - branchId TEXT NOT NULL PRIMARY KEY, - noteId TEXT NOT NULL, - parentNoteId TEXT NOT NULL, - notePosition INTEGER NOT NULL, - prefix TEXT, - isExpanded INTEGER NOT NULL DEFAULT 0, - isDeleted INTEGER NOT NULL DEFAULT 0, - deleteId TEXT DEFAULT NULL, - utcDateModified TEXT NOT NULL -); - --- Indexes -CREATE INDEX IDX_branches_noteId_parentNoteId ON branches (noteId, parentNoteId); -CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId); -``` - -**Field Descriptions:** - -| Field | Type | Description | -|-------|------|-------------| -| `branchId` | TEXT | Unique identifier for this branch | -| `noteId` | TEXT | Child note ID | -| `parentNoteId` | TEXT | Parent note ID | -| `notePosition` | INT | Sort order among siblings | -| `prefix` | TEXT | Optional prefix text (e.g., "Chapter 1:") | -| `isExpanded` | INT | Tree expansion state | -| `isDeleted` | INT | Soft delete flag | -| `deleteId` | TEXT | Delete operation ID | -| `utcDateModified` | TEXT | Last modified (UTC) | - -**Key Concepts:** -- **Cloning:** A note can have multiple branches (multiple parents) -- **Position:** Siblings ordered by `notePosition` -- **Prefix:** Display text before note title in tree -- **Soft Delete:** Allows sync before permanent deletion - -### Attributes Table - -```sql -CREATE TABLE attributes ( - attributeId TEXT NOT NULL PRIMARY KEY, - noteId TEXT NOT NULL, - type TEXT NOT NULL, - name TEXT NOT NULL, - value TEXT DEFAULT '' NOT NULL, - position INT DEFAULT 0 NOT NULL, - utcDateModified TEXT NOT NULL, - isDeleted INT NOT NULL, - deleteId TEXT DEFAULT NULL, - isInheritable INT DEFAULT 0 NULL -); - --- Indexes -CREATE INDEX IDX_attributes_name_value ON attributes (name, value); -CREATE INDEX IDX_attributes_noteId ON attributes (noteId); -CREATE INDEX IDX_attributes_value ON attributes (value); -``` - -**Field Descriptions:** - -| Field | Type | Description | -|-------|------|-------------| -| `attributeId` | TEXT | Unique identifier | -| `noteId` | TEXT | Note this attribute belongs to | -| `type` | TEXT | 'label' or 'relation' | -| `name` | TEXT | Attribute name | -| `value` | TEXT | Attribute value (text for labels, noteId for relations) | -| `position` | INT | Display order | -| `utcDateModified` | TEXT | Last modified (UTC) | -| `isDeleted` | INT | Soft delete flag | -| `deleteId` | TEXT | Delete operation ID | -| `isInheritable` | INT | Inherited by child notes | - -**Attribute Types:** - -**Labels** (key-value pairs): -```sql --- Example: #priority=high -INSERT INTO attributes (attributeId, noteId, type, name, value) -VALUES ('attr1', 'note123', 'label', 'priority', 'high') -``` - -**Relations** (links to other notes): -```sql --- Example: ~author=[[noteId]] -INSERT INTO attributes (attributeId, noteId, type, name, value) -VALUES ('attr2', 'note123', 'relation', 'author', 'author-note-id') -``` - -**Special Attributes:** -- `#run=frontendStartup` - Execute script on frontend load -- `#run=backendStartup` - Execute script on backend load -- `#customWidget` - Custom widget implementation -- `#iconClass` - Custom tree icon -- `#cssClass` - CSS class for note -- `#sorted` - Auto-sort children -- `#hideChildrenOverview` - Don't show child list - -### Revisions Table - -```sql -CREATE TABLE revisions ( - revisionId TEXT NOT NULL PRIMARY KEY, - noteId TEXT NOT NULL, - type TEXT DEFAULT '' NOT NULL, - mime TEXT DEFAULT '' NOT NULL, - title TEXT NOT NULL, - isProtected INT NOT NULL DEFAULT 0, - blobId TEXT DEFAULT NULL, - utcDateLastEdited TEXT NOT NULL, - utcDateCreated TEXT NOT NULL, - utcDateModified TEXT NOT NULL, - dateLastEdited TEXT NOT NULL, - dateCreated TEXT NOT NULL -); - --- Indexes -CREATE INDEX IDX_revisions_noteId ON revisions (noteId); -CREATE INDEX IDX_revisions_utcDateCreated ON revisions (utcDateCreated); -CREATE INDEX IDX_revisions_utcDateLastEdited ON revisions (utcDateLastEdited); -CREATE INDEX IDX_revisions_blobId ON revisions (blobId); -``` - -**Revision Strategy:** -- Automatic revision created on note modification -- Configurable interval (default: daily max) -- Stores complete note snapshot -- Allows reverting to previous versions -- Can be disabled with `#disableVersioning` - -### Attachments Table - -```sql -CREATE TABLE attachments ( - attachmentId TEXT NOT NULL PRIMARY KEY, - ownerId TEXT NOT NULL, - role TEXT NOT NULL, - mime TEXT NOT NULL, - title TEXT NOT NULL, - isProtected INT NOT NULL DEFAULT 0, - position INT DEFAULT 0 NOT NULL, - blobId TEXT DEFAULT NULL, - dateModified TEXT NOT NULL, - utcDateModified TEXT NOT NULL, - utcDateScheduledForErasureSince TEXT DEFAULT NULL, - isDeleted INT NOT NULL, - deleteId TEXT DEFAULT NULL -); - --- Indexes -CREATE INDEX IDX_attachments_ownerId_role ON attachments (ownerId, role); -CREATE INDEX IDX_attachments_blobId ON attachments (blobId); -``` - -**Attachment Roles:** -- `file` - Regular file attachment -- `image` - Image file -- `cover-image` - Note cover image -- Custom roles for specific purposes - -### Blobs Table - -```sql -CREATE TABLE blobs ( - blobId TEXT NOT NULL PRIMARY KEY, - content TEXT NULL DEFAULT NULL, - dateModified TEXT NOT NULL, - utcDateModified TEXT NOT NULL -); -``` - -**Blob Usage:** -- Stores actual content (text or binary) -- Referenced by notes, revisions, attachments -- Deduplication via hash-based blobId -- TEXT type stores both text and binary (base64) - -**Content Types:** -- **Text notes:** HTML content -- **Code notes:** Plain text source code -- **Binary notes:** Base64 encoded data -- **Protected notes:** Encrypted content - -## System Tables - -### Options Table - -```sql -CREATE TABLE options ( - name TEXT NOT NULL PRIMARY KEY, - value TEXT NOT NULL, - isSynced INTEGER DEFAULT 0 NOT NULL, - utcDateModified TEXT NOT NULL -); -``` - -**Key Options:** -- `documentId` - Unique installation ID -- `dbVersion` - Schema version -- `syncVersion` - Sync protocol version -- `passwordVerificationHash` - Password verification -- `encryptedDataKey` - Encryption key (encrypted) -- `theme` - UI theme -- Various feature flags and settings - -**Synced Options:** -- `isSynced = 1` - Synced across devices -- `isSynced = 0` - Local to this installation - -### Entity Changes Table - -```sql -CREATE TABLE entity_changes ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - entityName TEXT NOT NULL, - entityId TEXT NOT NULL, - hash TEXT NOT NULL, - isErased INT NOT NULL, - changeId TEXT NOT NULL, - componentId TEXT NOT NULL, - instanceId TEXT NOT NULL, - isSynced INTEGER NOT NULL, - utcDateChanged TEXT NOT NULL -); - --- Indexes -CREATE UNIQUE INDEX IDX_entityChanges_entityName_entityId - ON entity_changes (entityName, entityId); -CREATE INDEX IDX_entity_changes_changeId ON entity_changes (changeId); -``` - -**Purpose:** Track all entity modifications for synchronization - -**Entity Types:** -- `notes` -- `branches` -- `attributes` -- `revisions` -- `attachments` -- `options` -- `etapi_tokens` - -### Recent Notes Table - -```sql -CREATE TABLE recent_notes ( - noteId TEXT NOT NULL PRIMARY KEY, - notePath TEXT NOT NULL, - utcDateCreated TEXT NOT NULL -); -``` - -**Purpose:** Track recently accessed notes for quick access - -### Sessions Table - -```sql -CREATE TABLE sessions ( - sid TEXT PRIMARY KEY, - sess TEXT NOT NULL, - expired TEXT NOT NULL -); -``` - -**Purpose:** HTTP session storage for web interface - -### User Data Table - -```sql -CREATE TABLE user_data ( - tmpID INT PRIMARY KEY, - username TEXT, - email TEXT, - userIDEncryptedDataKey TEXT, - userIDVerificationHash TEXT, - salt TEXT, - derivedKey TEXT, - isSetup TEXT DEFAULT "false" -); -``` - -**Purpose:** Store user authentication credentials - -### ETAPI Tokens Table - -```sql -CREATE TABLE etapi_tokens ( - etapiTokenId TEXT PRIMARY KEY NOT NULL, - name TEXT NOT NULL, - tokenHash TEXT NOT NULL, - utcDateCreated TEXT NOT NULL, - utcDateModified TEXT NOT NULL, - isDeleted INT NOT NULL DEFAULT 0 -); -``` - -**Purpose:** API token authentication for external access - -## Data Relationships - -```mermaid -graph TB - Notes[Notes] - Branches[Branches] - Attributes[Attributes] - Attachments[Attachments] - Blobs[(Blobs)] - Revisions[Revisions] - - Notes --> Branches - Notes --> Attributes - Notes --> Attachments - Notes --> Blobs - Notes --> Revisions - - Branches --> Blobs - Attachments --> Blobs - Revisions --> Blobs - - style Notes fill:#e1f5ff - style Blobs fill:#ffe1e1 -``` - -**Relationships:** -- Notes ↔ Branches (many-to-many via noteId) -- Notes → Attributes (one-to-many) -- Notes → Blobs (one-to-one) -- Notes → Revisions (one-to-many) -- Notes → Attachments (one-to-many) -- Attachments → Blobs (one-to-one) -- Revisions → Blobs (one-to-one) - -## Database Access Patterns - -### Direct SQL Access - -**Location:** `apps/server/src/services/sql.ts` - -```typescript -// Execute query (returns rows) -const notes = sql.getRows('SELECT * FROM notes WHERE type = ?', ['text']) - -// Execute query (returns single row) -const note = sql.getRow('SELECT * FROM notes WHERE noteId = ?', [noteId]) - -// Execute statement (no return) -sql.execute('UPDATE notes SET title = ? WHERE noteId = ?', [title, noteId]) - -// Insert -sql.insert('notes', { - noteId: 'new-note-id', - title: 'New Note', - type: 'text', - // ... -}) - -// Transactions -sql.transactional(() => { - sql.execute('UPDATE ...') - sql.execute('INSERT ...') -}) -``` - -### Entity-Based Access (Recommended) - -**Via Becca Cache:** - -```typescript -// Get entity from cache -const note = becca.getNote(noteId) - -// Modify and save -note.title = 'Updated Title' -note.save() // Writes to database - -// Create new -const newNote = becca.createNote({ - parentNoteId: 'root', - title: 'New Note', - type: 'text', - content: 'Hello World' -}) - -// Delete -note.markAsDeleted() -``` - -## Database Migrations - -**Location:** `apps/server/src/migrations/` - -**Migration Files:** -- Format: `XXXX_migration_name.sql` or `XXXX_migration_name.js` -- Executed in numerical order -- Version tracked in `options.dbVersion` - -**SQL Migration Example:** -```sql --- 0280_add_new_column.sql -ALTER TABLE notes ADD COLUMN newField TEXT DEFAULT NULL; - -UPDATE options SET value = '280' WHERE name = 'dbVersion'; -``` - -**JavaScript Migration Example:** -```javascript -// 0285_complex_migration.js -module.exports = () => { - const notes = sql.getRows('SELECT * FROM notes WHERE type = ?', ['old-type']) - - for (const note of notes) { - sql.execute('UPDATE notes SET type = ? WHERE noteId = ?', - ['new-type', note.noteId]) - } -} -``` - -**Migration Process:** -1. Server checks `dbVersion` on startup -2. Compares with latest migration number -3. Executes pending migrations in order -4. Updates `dbVersion` after each -5. Restarts if migrations ran - -## Database Maintenance - -### Backup - -**Full Backup:** -```bash -# Copy database file -cp document.db document.db.backup - -# Or use Trilium's backup feature -# Settings → Backup -``` - -**Automatic Backups:** -- Daily backup (configurable) -- Stored in `backup/` directory -- Retention policy (keep last N backups) - -### Vacuum - -**Purpose:** Reclaim unused space, defragment - -```sql -VACUUM; -``` - -**When to vacuum:** -- After deleting many notes -- Database file size larger than expected -- Performance degradation - -### Integrity Check - -```sql -PRAGMA integrity_check; -``` - -**Result:** "ok" or list of errors - -### Consistency Checks - -**Built-in Consistency Checks:** - -Location: `apps/server/src/services/consistency_checks.ts` - -- Orphaned branches -- Missing parent notes -- Circular dependencies -- Invalid entity references -- Blob reference integrity - -**Run Checks:** -```typescript -// Via API -POST /api/consistency-check - -// Or from backend script -api.runConsistencyChecks() -``` - -## Performance Optimization - -### Indexes - -**Existing Indexes:** -- `notes.title` - Fast title searches -- `notes.type` - Filter by type -- `notes.dateCreated/Modified` - Time-based queries -- `branches.noteId_parentNoteId` - Tree navigation -- `attributes.name_value` - Attribute searches - -**Query Optimization:** -```sql --- Use indexed columns in WHERE clause -SELECT * FROM notes WHERE type = 'text' -- Uses index - --- Avoid functions on indexed columns -SELECT * FROM notes WHERE LOWER(title) = 'test' -- No index - --- Better -SELECT * FROM notes WHERE title = 'Test' -- Uses index -``` - -### Connection Settings - -```typescript -// apps/server/src/services/sql.ts -const db = new Database('document.db', { - // Enable WAL mode for better concurrency - verbose: console.log -}) - -db.pragma('journal_mode = WAL') -db.pragma('synchronous = NORMAL') -db.pragma('cache_size = -64000') // 64MB cache -db.pragma('temp_store = MEMORY') -``` - -**WAL Mode Benefits:** -- Better concurrency (readers don't block writers) -- Faster commits -- More robust - -### Query Performance - -**Use EXPLAIN QUERY PLAN:** -```sql -EXPLAIN QUERY PLAN -SELECT * FROM notes -WHERE type = 'text' - AND dateCreated > '2025-01-01' -``` - -**Analyze slow queries:** -- Check index usage -- Avoid SELECT * -- Use prepared statements -- Batch operations in transactions - -## Database Size Management - -**Typical Sizes:** -- 1,000 notes: ~5-10 MB -- 10,000 notes: ~50-100 MB -- 100,000 notes: ~500 MB - 1 GB - -**Size Reduction Strategies:** - -1. **Delete old revisions** -2. **Remove large attachments** -3. **Vacuum database** -4. **Compact blobs** -5. **Archive old notes** - -**Blob Deduplication:** -- Blobs identified by content hash -- Identical content shares one blob -- Automatic deduplication on insert - -## Security Considerations - -### Protected Notes Encryption - -**Encryption Process:** -```typescript -// Encrypt blob content -const encryptedContent = encrypt(content, dataKey) -blob.content = encryptedContent - -// Store encrypted -sql.insert('blobs', { blobId, content: encryptedContent }) -``` - -**Encryption Details:** -- Algorithm: AES-256-CBC -- Key derivation: PBKDF2 (10,000 iterations) -- Per-note encryption -- Master key encrypted with user password - -### SQL Injection Prevention - -**Always use parameterized queries:** -```typescript -// GOOD - Safe from SQL injection -sql.execute('SELECT * FROM notes WHERE title = ?', [userInput]) - -// BAD - Vulnerable to SQL injection -sql.execute(`SELECT * FROM notes WHERE title = '${userInput}'`) -``` - -### Database File Protection - -**File Permissions:** -- Owner read/write only -- No group/other access -- Located in user-specific directory - ---- - -**See Also:** -- [ARCHITECTURE.md](ARCHITECTURE.md) - Overall architecture -- [Database Schema Files](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Database/) -- [Migration Scripts](../apps/server/src/migrations/) diff --git a/docs/Developer Guide/!!!meta.json b/docs/Developer Guide/!!!meta.json index f19dbc16a..397bdcab4 100644 --- a/docs/Developer Guide/!!!meta.json +++ b/docs/Developer Guide/!!!meta.json @@ -172,64 +172,57 @@ "children": [ { "isClone": false, - "noteId": "2DJZgzpTJ078", + "noteId": "dsMq2EIOMOBU", "notePath": [ "jdjRLhLV3TtI", "MhwWMgxwDTZL", - "2DJZgzpTJ078" + "dsMq2EIOMOBU" ], - "title": "Client-server architecture", + "title": "Frontend", "notePosition": 10, "prefix": null, "isExpanded": false, "type": "text", "mime": "text/html", - "attributes": [], - "format": "markdown", - "attachments": [], - "dirFileName": "Client-server architecture", - "children": [ + "attributes": [ { - "isClone": false, - "noteId": "dsMq2EIOMOBU", - "notePath": [ - "jdjRLhLV3TtI", - "MhwWMgxwDTZL", - "2DJZgzpTJ078", - "dsMq2EIOMOBU" - ], - "title": "Frontend", - "notePosition": 10, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [], - "format": "markdown", - "dataFileName": "Frontend.md", - "attachments": [] - }, - { - "isClone": false, - "noteId": "tsswRlmHEnYW", - "notePath": [ - "jdjRLhLV3TtI", - "MhwWMgxwDTZL", - "2DJZgzpTJ078", - "tsswRlmHEnYW" - ], - "title": "Backend", - "notePosition": 20, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [], - "format": "markdown", - "dataFileName": "Backend.md", - "attachments": [] + "type": "label", + "name": "shareAlias", + "value": "frontend", + "isInheritable": false, + "position": 20 } - ] + ], + "format": "markdown", + "dataFileName": "Frontend.md", + "attachments": [] + }, + { + "isClone": false, + "noteId": "tsswRlmHEnYW", + "notePath": [ + "jdjRLhLV3TtI", + "MhwWMgxwDTZL", + "tsswRlmHEnYW" + ], + "title": "Backend", + "notePosition": 20, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "attributes": [ + { + "type": "label", + "name": "shareAlias", + "value": "backend", + "isInheritable": false, + "position": 20 + } + ], + "format": "markdown", + "dataFileName": "Backend.md", + "attachments": [] }, { "isClone": false, @@ -240,7 +233,7 @@ "pRZhrVIGCbMu" ], "title": "Database", - "notePosition": 20, + "notePosition": 40, "prefix": null, "isExpanded": false, "type": "text", @@ -785,15 +778,23 @@ "MhwWMgxwDTZL", "Wxn82Em8B7U5" ], - "title": "API", - "notePosition": 30, + "title": "APIs", + "notePosition": 50, "prefix": null, "isExpanded": false, "type": "text", "mime": "text/html", - "attributes": [], + "attributes": [ + { + "type": "label", + "name": "shareAlias", + "value": "api", + "isInheritable": false, + "position": 20 + } + ], "format": "markdown", - "dataFileName": "API.md", + "dataFileName": "APIs.md", "attachments": [] }, { @@ -805,7 +806,7 @@ "Vk4zD1Iirarg" ], "title": "Arhitecture Decision Records", - "notePosition": 40, + "notePosition": 60, "prefix": null, "isExpanded": false, "type": "text", @@ -817,6 +818,13 @@ "value": "Jg7clqogFOyD", "isInheritable": false, "position": 20 + }, + { + "type": "label", + "name": "shareAlias", + "value": "adr", + "isInheritable": false, + "position": 30 } ], "format": "markdown", @@ -825,14 +833,14 @@ }, { "isClone": false, - "noteId": "QW1MB7RZB5Gf", + "noteId": "RHbKw3xiwk3S", "notePath": [ "jdjRLhLV3TtI", "MhwWMgxwDTZL", - "QW1MB7RZB5Gf" + "RHbKw3xiwk3S" ], - "title": "Security Architecture", - "notePosition": 50, + "title": "Security", + "notePosition": 80, "prefix": null, "isExpanded": false, "type": "text", @@ -841,13 +849,13 @@ { "type": "label", "name": "shareAlias", - "value": "security-architecture", + "value": "security", "isInheritable": false, "position": 20 } ], "format": "markdown", - "dataFileName": "Security Architecture.md", + "dataFileName": "Security.md", "attachments": [] } ] @@ -1153,6 +1161,13 @@ "value": "bx bx-rocket", "isInheritable": false, "position": 30 + }, + { + "type": "label", + "name": "shareAlias", + "value": "releasing", + "isInheritable": false, + "position": 40 } ], "format": "markdown", @@ -1181,6 +1196,13 @@ "value": "bx bxs-component", "isInheritable": false, "position": 20 + }, + { + "type": "label", + "name": "shareAlias", + "value": "dependencies", + "isInheritable": false, + "position": 30 } ], "format": "markdown", @@ -1527,6 +1549,13 @@ "value": "bx bx-microchip", "isInheritable": false, "position": 20 + }, + { + "type": "label", + "name": "shareAlias", + "value": "cache", + "isInheritable": false, + "position": 30 } ], "format": "markdown", @@ -2001,7 +2030,15 @@ "isExpanded": false, "type": "text", "mime": "text/html", - "attributes": [], + "attributes": [ + { + "type": "label", + "name": "shareAlias", + "value": "note-types", + "isInheritable": false, + "position": 20 + } + ], "format": "markdown", "attachments": [], "dirFileName": "Note Types", @@ -2547,6 +2584,7 @@ } ], "format": "markdown", + "dataFileName": "Synchronisation.md", "attachments": [], "dirFileName": "Synchronisation", "children": [ @@ -2794,7 +2832,15 @@ "isExpanded": false, "type": "text", "mime": "text/html", - "attributes": [], + "attributes": [ + { + "type": "label", + "name": "shareAlias", + "value": "unit-tests", + "isInheritable": false, + "position": 20 + } + ], "format": "markdown", "dataFileName": "Unit tests.md", "attachments": [] diff --git a/docs/Developer Guide/Developer Guide/Architecture.md b/docs/Developer Guide/Developer Guide/Architecture.md index 48a91fcde..66cb82eea 100644 --- a/docs/Developer Guide/Developer Guide/Architecture.md +++ b/docs/Developer Guide/Developer Guide/Architecture.md @@ -118,4 +118,85 @@ desktop → client → commons server → client → commons client → ckeditor5, codemirror, highlightjs ckeditor5 → ckeditor5-* plugins -``` \ No newline at end of file +``` + +## Security summary + +### Encryption System + +**Per-Note Encryption:** + +* Notes can be individually protected +* AES-128-CBC encryption for encrypted notes. +* Separate protected session management + +**Protected Session:** + +* Time-limited access to protected notes +* Automatic timeout +* Re-authentication required +* Frontend: `protected_session.ts` +* Backend: `protected_session.ts` + +### Authentication + +**Password Auth:** + +* PBKDF2 key derivation +* Salt per installation +* Hash verification + +**OpenID Connect:** + +* External identity provider support +* OAuth 2.0 flow +* Configurable providers + +**TOTP (2FA):** + +* Time-based one-time passwords +* QR code setup +* Backup codes + +### Authorization + +**Single-User Model:** + +* Desktop: single user (owner) +* Server: single user per installation + +**Share Notes:** + +* Public access without authentication +* Separate Shaca cache +* Read-only access + +### CSRF Protection + +**CSRF Tokens:** + +* Required for state-changing operations +* Token in header or cookie +* Validation middleware + +### Input Sanitization + +**XSS Prevention:** + +* DOMPurify for HTML sanitization +* CKEditor content filtering +* CSP headers + +**SQL Injection:** + +* Parameterized queries only +* Better-sqlite3 prepared statements +* No string concatenation in SQL + +### Dependency Security + +**Vulnerability Scanning:** + +* Renovate bot for updates +* npm audit integration +* Override vulnerable sub-dependencies \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/API.md b/docs/Developer Guide/Developer Guide/Architecture/APIs.md similarity index 99% rename from docs/Developer Guide/Developer Guide/Architecture/API.md rename to docs/Developer Guide/Developer Guide/Architecture/APIs.md index 92f6fb813..cdd954a61 100644 --- a/docs/Developer Guide/Developer Guide/Architecture/API.md +++ b/docs/Developer Guide/Developer Guide/Architecture/APIs.md @@ -1,4 +1,4 @@ -# API +# APIs ### Internal API **REST Endpoints** (`/api/*`) diff --git a/docs/Developer Guide/Developer Guide/Architecture/Client-server architecture/Backend.md b/docs/Developer Guide/Developer Guide/Architecture/Backend.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Client-server architecture/Backend.md rename to docs/Developer Guide/Developer Guide/Architecture/Backend.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Database.md b/docs/Developer Guide/Developer Guide/Architecture/Database.md index bb80ccdb1..b8836b56d 100644 --- a/docs/Developer Guide/Developer Guide/Architecture/Database.md +++ b/docs/Developer Guide/Developer Guide/Architecture/Database.md @@ -1,5 +1,5 @@ # Database -Trilium uses **SQLite** as its database engine, managed via `better-sqlite3`. +Trilium uses **SQLite** (via `better-sqlite3`) as its embedded database engine, providing a reliable, file-based storage system that requires no separate database server. The database stores all notes, their relationships, metadata, and configuration. Schema location: `apps/server/src/assets/db/schema.sql` diff --git a/docs/Developer Guide/Developer Guide/Architecture/Client-server architecture/Frontend.md b/docs/Developer Guide/Developer Guide/Architecture/Frontend.md similarity index 100% rename from docs/Developer Guide/Developer Guide/Architecture/Client-server architecture/Frontend.md rename to docs/Developer Guide/Developer Guide/Architecture/Frontend.md diff --git a/docs/Developer Guide/Developer Guide/Architecture/Security Architecture.md b/docs/Developer Guide/Developer Guide/Architecture/Security Architecture.md deleted file mode 100644 index ab0c88c06..000000000 --- a/docs/Developer Guide/Developer Guide/Architecture/Security Architecture.md +++ /dev/null @@ -1,79 +0,0 @@ -# Security Architecture -### Encryption System - -**Per-Note Encryption:** - -* Notes can be individually protected -* AES-128-CBC encryption for encrypted notes. -* Separate protected session management - -**Protected Session:** - -* Time-limited access to protected notes -* Automatic timeout -* Re-authentication required -* Frontend: `protected_session.ts` -* Backend: `protected_session.ts` - -### Authentication - -**Password Auth:** - -* PBKDF2 key derivation -* Salt per installation -* Hash verification - -**OpenID Connect:** - -* External identity provider support -* OAuth 2.0 flow -* Configurable providers - -**TOTP (2FA):** - -* Time-based one-time passwords -* QR code setup -* Backup codes - -### Authorization - -**Single-User Model:** - -* Desktop: single user (owner) -* Server: single user per installation - -**Share Notes:** - -* Public access without authentication -* Separate Shaca cache -* Read-only access - -### CSRF Protection - -**CSRF Tokens:** - -* Required for state-changing operations -* Token in header or cookie -* Validation middleware - -### Input Sanitization - -**XSS Prevention:** - -* DOMPurify for HTML sanitization -* CKEditor content filtering -* CSP headers - -**SQL Injection:** - -* Parameterized queries only -* Better-sqlite3 prepared statements -* No string concatenation in SQL - -### Dependency Security - -**Vulnerability Scanning:** - -* Renovate bot for updates -* npm audit integration -* Override vulnerable sub-dependencies \ No newline at end of file diff --git a/docs/Developer Guide/Developer Guide/Architecture/Security.md b/docs/Developer Guide/Developer Guide/Architecture/Security.md new file mode 100644 index 000000000..61fdc7377 --- /dev/null +++ b/docs/Developer Guide/Developer Guide/Architecture/Security.md @@ -0,0 +1,464 @@ +# Security +Trilium implements a **defense-in-depth security model** with multiple layers of protection for user data. The security architecture covers authentication, authorization, encryption, input sanitization, and secure communication. + +## Security Principles + +1. **Data Privacy**: User data is protected at rest and in transit +2. **Encryption**: Per-note encryption for sensitive content +3. **Authentication**: Multiple authentication methods supported +4. **Authorization**: Single-user model with granular protected sessions +5. **Input Validation**: All user input sanitized +6. **Secure Defaults**: Security features enabled by default +7. **Transparency**: Open source allows security audits + +## Threat Model + +### Threats Considered + +1. **Unauthorized Access** + * Physical access to device + * Network eavesdropping + * Stolen credentials + * Session hijacking +2. **Data Exfiltration** + * Malicious scripts + * XSS attacks + * SQL injection + * CSRF attacks +3. **Data Corruption** + * Malicious modifications + * Database tampering + * Sync conflicts +4. **Privacy Leaks** + * Unencrypted backups + * Search indexing + * Temporary files + * Memory dumps + +### Out of Scope + +* Nation-state attackers +* Zero-day vulnerabilities in dependencies +* Hardware vulnerabilities (Spectre, Meltdown) +* Physical access with unlimited time +* Quantum computing attacks + +## Authentication + +### Password Authentication + +**Implementation:** `apps/server/src/services/password.ts` + +### TOTP (Two-Factor Authentication) + +**Implementation:** `apps/server/src/routes/api/login.ts` + +### OpenID Connect + +**Implementation:** `apps/server/src/routes/api/login.ts` + +**Supported Providers:** + +* Any OpenID Connect compatible provider +* Google, GitHub, Auth0, etc. + +**Flow:** + +```typescript +// 1. Redirect to provider +GET /api/login/openid + +// 2. Provider redirects back with code +GET /api/login/openid/callback?code=... + +// 3. Exchange code for tokens +const tokens = await openidClient.callback(redirectUri, req.query) + +// 4. Verify ID token +const claims = tokens.claims() + +// 5. Create session +req.session.loggedIn = true +``` + +### Session Management + +**Session Storage:** SQLite database (sessions table) + +**Session Configuration:** + +```typescript +app.use(session({ + secret: sessionSecret, + resave: false, + saveUninitialized: false, + rolling: true, + cookie: { + maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days + httpOnly: true, + secure: isHttps, + sameSite: 'lax' + }, + store: new SqliteStore({ + db: db, + table: 'sessions' + }) +})) +``` + +**Session Invalidation:** + +* Automatic timeout after inactivity +* Manual logout clears session +* Server restart invalidates all sessions (optional) + +## Authorization + +### Single-User Model + +**Desktop:** + +* Single user (owner of device) +* No multi-user support +* Full access to all notes + +**Server:** + +* Single user per installation +* Authentication required for all operations +* No user roles or permissions + +### Protected Sessions + +**Purpose:** Temporary access to encrypted (protected) notes + +**Implementation:** `apps/server/src/services/protected_session.ts` + +**Workflow:** + +```typescript +// 1. User enters password for protected notes +POST /api/protected-session/enter +Body: { password: "protected-password" } + +// 2. Derive encryption key +const protectedDataKey = deriveKey(password) + +// 3. Verify password (decrypt known encrypted value) +const decrypted = decrypt(testValue, protectedDataKey) +if (decrypted === expectedValue) { + // 4. Store in memory (not in session) + protectedSessionHolder.setProtectedDataKey(protectedDataKey) + + // 5. Set timeout + setTimeout(() => { + protectedSessionHolder.clearProtectedDataKey() + }, timeout) +} +``` + +**Protected Session Timeout:** + +* Default: 10 minutes (configurable) +* Extends on activity +* Cleared on browser close +* Separate from main session + +### API Authorization + +**Internal API:** + +* Requires authenticated session +* CSRF token validation +* Same-origin policy + +**ETAPI (External API):** + +* Token-based authentication +* No session required +* Rate limiting + +## Encryption + +### Note Encryption + +**Encryption Algorithm:** AES-256-CBC + +**Key Hierarchy:** + +``` +User Password + ↓ (scrypt) +Data Key (for protected notes) + ↓ (AES-128) +Protected Note Content +``` + +**Protected Note Metadata:** + +* Content IS encrypted +* Type and MIME are NOT encrypted +* Attributes are NOT encrypted + +### Data Key Management + +**Key Rotation:** + +* Not currently supported +* Requires re-encrypting all protected notes + +### Transport Encryption + +**HTTPS:** + +* Recommended for server installations +* TLS 1.2+ only +* Strong cipher suites preferred +* Certificate validation enabled + +**Desktop:** + +* Local communication (no network) +* No HTTPS required + +### Backup Encryption + +**Database Backups:** + +* Protected notes remain encrypted in backup +* Backup file should be protected separately +* Consider encrypting backup storage location + +## Input Sanitization + +### XSS Prevention + +* **HTML Sanitization** +* **CKEditor Configuration:** + + ``` + // apps/client/src/widgets/type_widgets/text_type_widget.ts + ClassicEditor.create(element, { + // Restrict allowed content + htmlSupport: { + allow: [ + { name: /./, attributes: true, classes: true, styles: true } + ], + disallow: [ + { name: 'script' }, + { name: 'iframe', attributes: /^(?!src$).*/ } + ] + } + }) + ``` +* Content Security Policy + +### SQL Injection Prevention + +**Parameterized Queries:** + +```typescript +const notes = sql.getRows( + 'SELECT * FROM notes WHERE title = ?', + [userInput] +) +``` + +**ORM Usage:** + +```typescript +// Entity-based access prevents SQL injection +const note = becca.getNote(noteId) +note.title = userInput // Sanitized by entity +note.save() // Parameterized query +``` + +### CSRF Prevention + +**CSRF Token Validation:** + +Location: `apps/server/src/routes/csrf_protection.ts` + +Stateless CSRF using [Double Submit Cookie Pattern](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie) via [`csrf-csrf`](https://github.com/Psifi-Solutions/csrf-csrf). + +### File Upload Validation + +**Validation:** + +```typescript +// Validate file size +const maxSize = 100 * 1024 * 1024 // 100 MB +if (file.size > maxSize) { + throw new Error('File too large') +} +``` + +## Network Security + +### HTTPS Configuration + +**Certificate Validation:** + +* Require valid certificates in production +* Self-signed certificates allowed for development +* Certificate pinning not implemented + +### Rate Limiting + +**Login Rate Limiting:** + +```typescript +const loginLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 10, // 10 failed attempts + skipSuccessfulRequests: true +}) + +app.post('/api/login/password', loginLimiter, loginHandler) +``` + +## Data Security + +### Secure Data Deletion + +**Soft Delete:** + +```typescript +// Mark as deleted (sync first) +note.isDeleted = 1 +note.deleteId = generateUUID() +note.save() + +// Entity change tracked for sync +addEntityChange('notes', noteId, note) +``` + +**Hard Delete (Erase):** + +```typescript +// After sync completed +sql.execute('DELETE FROM notes WHERE noteId = ?', [noteId]) +sql.execute('DELETE FROM branches WHERE noteId = ?', [noteId]) +sql.execute('DELETE FROM attributes WHERE noteId = ?', [noteId]) + +// Mark entity change as erased +sql.execute('UPDATE entity_changes SET isErased = 1 WHERE entityId = ?', [noteId]) +``` + +**Blob Cleanup:** + +```typescript +// Find orphaned blobs (not referenced by any note/revision/attachment) +const orphanedBlobs = sql.getRows(` + SELECT blobId FROM blobs + WHERE blobId NOT IN (SELECT blobId FROM notes WHERE blobId IS NOT NULL) + AND blobId NOT IN (SELECT blobId FROM revisions WHERE blobId IS NOT NULL) + AND blobId NOT IN (SELECT blobId FROM attachments WHERE blobId IS NOT NULL) +`) + +// Delete orphaned blobs +for (const blob of orphanedBlobs) { + sql.execute('DELETE FROM blobs WHERE blobId = ?', [blob.blobId]) +} +``` + +### Memory Security + +**Protected Data in Memory:** + +* Protected data keys stored in memory only +* Cleared on timeout +* Not written to disk +* Not in session storage + +## Dependency Security + +### Vulnerability Scanning + +**Tools:** + +* Renovate bot - Automatic dependency updates +* `pnpm audit` - Check for known vulnerabilities +* GitHub Dependabot alerts + +**Process:** + +```sh +# Check for vulnerabilities +npm audit + +# Fix automatically +npm audit fix + +# Manual review for breaking changes +npm audit fix --force +``` + +### Dependency Pinning + +**package.json:** + +``` +{ + "dependencies": { + "express": "4.18.2", // Exact version + "better-sqlite3": "^9.2.2" // Compatible versions + } +} +``` + +**pnpm Overrides:** + +``` +{ + "pnpm": { + "overrides": { + "lodash@<4.17.21": ">=4.17.21", // Force minimum version + "axios@<0.21.2": ">=0.21.2" + } + } +} +``` + +### Patch Management + +**pnpm Patches:** + +```sh +# Create patch +pnpm patch @ckeditor/ckeditor5 + +# Edit files in temporary directory +# ... + +# Generate patch file +pnpm patch-commit /tmp/ckeditor5-patch + +# Patch applied automatically on install +``` + +## Security Auditing + +### Logs + +**Security Events Logged:** + +* Login attempts (success/failure) +* Protected session access +* Password changes +* ETAPI token usage +* Failed CSRF validations + +**Log Location:** + +* Desktop: Console output +* Server: Log files or stdout + +### Monitoring + +**Metrics to Monitor:** + +* Failed login attempts +* API error rates +* Unusual database changes +* Large exports/imports \ No newline at end of file diff --git a/docs/SYNCHRONIZATION.md b/docs/Developer Guide/Developer Guide/Concepts/Synchronisation.md similarity index 52% rename from docs/SYNCHRONIZATION.md rename to docs/Developer Guide/Developer Guide/Concepts/Synchronisation.md index 0945e533a..f97bd3c4e 100644 --- a/docs/SYNCHRONIZATION.md +++ b/docs/Developer Guide/Developer Guide/Concepts/Synchronisation.md @@ -1,16 +1,11 @@ -# Trilium Synchronization Architecture +# Synchronisation +Trilium implements a **bidirectional synchronization system** that allows users to sync their note databases across multiple devices (desktop clients and server instances). The sync protocol is designed to handle: -> **Related:** [ARCHITECTURE.md](ARCHITECTURE.md) | [User Guide: Synchronization](https://triliumnext.github.io/Docs/Wiki/synchronization) - -## Overview - -Trilium implements a sophisticated **bidirectional synchronization system** that allows users to sync their note databases across multiple devices (desktop clients and server instances). The sync protocol is designed to handle: - -- Concurrent modifications across devices -- Conflict resolution -- Partial sync (only changed entities) -- Protected note synchronization -- Efficient bandwidth usage +* Concurrent modifications across devices +* Simple conflict resolution (without “merge conflict” indication). +* Partial sync (only changed entities) +* Protected note synchronization +* Efficient bandwidth usage ## Sync Architecture @@ -35,7 +30,7 @@ graph TB Every modification to any entity (note, branch, attribute, etc.) creates an **entity change** record: -```sql +``` entity_changes ( id, -- Auto-increment ID entityName, -- 'notes', 'branches', 'attributes', etc. @@ -43,7 +38,7 @@ entity_changes ( hash, -- Content hash for integrity isErased, -- If entity was erased (deleted permanently) changeId, -- Unique change identifier - componentId, -- Installation identifier + componentId, -- Unique component/widget identifier instanceId, -- Process instance identifier isSynced, -- Whether synced to server utcDateChanged -- When change occurred @@ -51,17 +46,19 @@ entity_changes ( ``` **Key Properties:** -- **changeId**: Globally unique identifier (UUID) for the change -- **componentId**: Unique per Trilium installation (persists across restarts) -- **instanceId**: Unique per process (changes on restart) -- **hash**: SHA-256 hash of entity data for integrity verification + +* **changeId**: Globally unique identifier (UUID) for the change +* **componentId**: Unique identifier of the component/widget that generated to change (can be used to avoid refreshing the widget being edited). +* **instanceId**: Unique per process (changes on restart) +* **hash**: SHA-256 hash of entity data for integrity verification ### Sync Versions Each Trilium installation tracks: -- **Local sync version**: Highest change ID seen locally -- **Server sync version**: Highest change ID on server -- **Entity versions**: Last sync version for each entity type + +* **Local sync version**: Highest change ID seen locally +* **Server sync version**: Highest change ID on server +* **Entity versions**: Last sync version for each entity type ### Change Tracking @@ -87,11 +84,12 @@ function addEntityChange(entityName, entityId, entity) { ``` **Entity modification triggers:** -- Note content update -- Note metadata change -- Branch creation/deletion/reorder -- Attribute addition/removal -- Options modification + +* Note content update +* Note metadata change +* Branch creation/deletion/reorder +* Attribute addition/removal +* Options modification ## Sync Protocol @@ -122,9 +120,9 @@ Response: **Step 3: Decision** -- If `entityChanges > 0`: Pull changes from server -- If `outstandingPushCount > 0`: Push changes to server -- Both can happen in sequence +* If `entityChanges > 0`: Pull changes from server +* If `outstandingPushCount > 0`: Push changes to server +* Both can happen in sequence ### Pull Sync (Server → Client) @@ -159,10 +157,10 @@ Response: **Client Processing:** -1. Apply entity changes to local database -2. Update Froca cache -3. Update local sync version -4. Trigger UI refresh +1. Apply entity changes to local database +2. Update Froca cache +3. Update local sync version +4. Trigger UI refresh ### Push Sync (Client → Server) @@ -191,12 +189,12 @@ POST /api/sync/push **Server Processing:** -1. Validate changes -2. Check for conflicts -3. Apply changes to database -4. Update Becca cache -5. Mark as synced -6. Broadcast to other connected clients via WebSocket +1. Validate changes +2. Check for conflicts +3. Apply changes to database +4. Update Becca cache +5. Mark as synced +6. Broadcast to other connected clients via WebSocket **Conflict Detection:** @@ -215,21 +213,25 @@ if (serverLastModified > clientSyncVersion) { ### Conflict Types -**1. Content Conflict** -- Both client and server modified same note content -- **Resolution**: Last-write-wins based on `utcDateModified` +**1\. Content Conflict** -**2. Structure Conflict** -- Branch moved/deleted on one side, modified on other -- **Resolution**: Tombstone records, reconciliation +* Both client and server modified same note content +* **Resolution**: Last-write-wins based on `utcDateModified` -**3. Attribute Conflict** -- Same attribute modified differently -- **Resolution**: Last-write-wins +**2\. Structure Conflict** + +* Branch moved/deleted on one side, modified on other +* **Resolution**: Tombstone records, reconciliation + +**3\. Attribute Conflict** + +* Same attribute modified differently +* **Resolution**: Last-write-wins ### Conflict Resolution Strategy **Last-Write-Wins:** + ```typescript if (clientEntity.utcDateModified > serverEntity.utcDateModified) { // Client wins, apply client changes @@ -241,9 +243,10 @@ if (clientEntity.utcDateModified > serverEntity.utcDateModified) { ``` **Tombstone Records:** -- Deleted entities leave tombstone in `entity_changes` -- Prevents re-sync of deleted items -- `isErased = 1` for permanent deletions + +* Deleted entities leave tombstone in `entity_changes` +* Prevents re-sync of deleted items +* `isErased = 1` for permanent deletions ### Protected Notes Sync @@ -251,43 +254,26 @@ if (clientEntity.utcDateModified > serverEntity.utcDateModified) { **Solution:** -1. **Protected session required**: User must unlock protected notes -2. **Encrypted sync**: Content synced in encrypted form -3. **Hash verification**: Integrity checked without decryption -4. **Lazy decryption**: Only decrypt when accessed - -**Sync Flow:** - -```typescript -// Client side -if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) { - // Skip protected notes if session not active - continue -} - -// Server side -if (note.isProtected) { - // Sync encrypted blob - // Don't decrypt for sync - syncEncryptedBlob(note.blobId) -} -``` +1. **Encrypted sync**: Content synced in encrypted form +2. **Hash verification**: Integrity checked without decryption +3. **Lazy decryption**: Only decrypt when accessed ## Sync States ### Connection States -- **Connected**: WebSocket connection active -- **Disconnected**: No connection to sync server -- **Syncing**: Actively transferring data -- **Conflict**: Sync paused due to conflict +* **Connected**: WebSocket connection active +* **Disconnected**: No connection to sync server +* **Syncing**: Actively transferring data +* **Conflict**: Sync paused due to conflict ### Entity Sync States Each entity can be in: -- **Synced**: In sync with server -- **Pending**: Local changes not yet pushed -- **Conflict**: Conflicting changes detected + +* **Synced**: In sync with server +* **Pending**: Local changes not yet pushed +* **Conflict**: Conflicting changes detected ### UI Indicators @@ -299,8 +285,6 @@ class SyncStatusWidget { showIcon('synced') } else if (isSyncing) { showIcon('syncing-spinner') - } else if (hasConflicts) { - showIcon('conflict-warning') } else { showIcon('not-synced') } @@ -314,7 +298,7 @@ class SyncStatusWidget { Only entities changed since last sync are transferred: -```sql +``` SELECT * FROM entity_changes WHERE id > :lastSyncedChangeId ORDER BY id ASC @@ -357,26 +341,12 @@ res.send(gzip(syncData)) ### Network Errors -**Retry Strategy:** -```typescript -const RETRY_DELAYS = [1000, 2000, 5000, 10000, 30000] - -async function syncWithRetry(attempt = 0) { - try { - await performSync() - } catch (error) { - if (attempt < RETRY_DELAYS.length) { - setTimeout(() => { - syncWithRetry(attempt + 1) - }, RETRY_DELAYS[attempt]) - } - } -} -``` +Reported to the user and the sync will be retried after the interval passes. ### Sync Integrity Checks **Hash Verification:** + ```typescript // Verify entity hash matches const calculatedHash = calculateHash(entity) @@ -388,16 +358,18 @@ if (calculatedHash !== receivedHash) { ``` **Consistency Checks:** -- Orphaned branches detection -- Missing parent notes -- Invalid entity references -- Circular dependencies + +* Orphaned branches detection +* Missing parent notes +* Invalid entity references +* Circular dependencies ## Sync Server Configuration ### Server Setup **Required Options:** + ```javascript { "syncServerHost": "https://sync.example.com", @@ -407,59 +379,26 @@ if (calculatedHash !== receivedHash) { ``` **Authentication:** -- Username/password or -- Sync token (generated on server) -### Client Setup - -**Desktop Client:** -```javascript -// Settings → Sync -{ - "syncServerHost": "https://sync.example.com", - "username": "user@example.com", - "password": "********" -} -``` - -**Test Connection:** -```typescript -POST /api/sync/test -Response: { "success": true } -``` +* Username/password or +* Sync token (generated on server) ## Sync API Endpoints Located at: `apps/server/src/routes/api/sync.ts` -**Endpoints:** - -- `POST /api/sync/check` - Check sync status -- `POST /api/sync/pull` - Pull changes from server -- `POST /api/sync/push` - Push changes to server -- `POST /api/sync/finished` - Mark sync complete -- `POST /api/sync/test` - Test connection -- `GET /api/sync/stats` - Sync statistics - ## WebSocket Sync Updates Real-time sync via WebSocket: ```typescript // Server broadcasts change to all connected clients -ws.broadcast('entity-change', { - entityName: 'notes', - entityId: 'abc123', - changeId: 'change-uuid', - sourceId: 'originating-component-id' +ws.broadcast('frontend-update', { + lastSyncedPush, + entityChanges }) -// Client receives and applies -ws.on('entity-change', (data) => { - if (data.sourceId !== myComponentId) { - froca.processEntityChange(data) - } -}) +// Client receives and processed the information. ``` ## Sync Scheduling @@ -467,107 +406,79 @@ ws.on('entity-change', (data) => { ### Automatic Sync **Desktop:** -- Sync on startup -- Periodic sync (configurable interval, default: 60s) -- Sync before shutdown + +* Sync on startup +* Periodic sync (configurable interval, default: 60s) **Server:** -- Sync on entity modification -- WebSocket push to connected clients + +* Sync on entity modification +* WebSocket push to connected clients ### Manual Sync User can trigger: -- Full sync -- Sync now -- Sync specific subtree + +* Full sync +* Sync now +* Sync specific subtree ## Troubleshooting ### Common Issues **Sync stuck:** -```sql + +``` -- Reset sync state UPDATE entity_changes SET isSynced = 0; DELETE FROM options WHERE name LIKE 'sync%'; ``` **Hash mismatch:** -- Data corruption detected -- Re-sync from backup -- Check database integrity + +* Data corruption detected +* Re-sync from backup +* Check database integrity **Conflict loop:** -- Manual intervention required -- Export conflicting notes -- Choose winning version -- Re-sync -### Sync Diagnostics - -**Check sync status:** -```typescript -GET /api/sync/stats -Response: { - "unsyncedChanges": 0, - "lastSyncDate": "2025-11-02T12:00:00Z", - "syncVersion": 12890 -} -``` - -**Entity change log:** -```sql -SELECT * FROM entity_changes -WHERE isSynced = 0 -ORDER BY id DESC; -``` +* Manual intervention required +* Export conflicting notes +* Choose winning version +* Re-sync ## Security Considerations ### Encrypted Sync -- Protected notes synced encrypted -- No plain text over network -- Server cannot read protected content +* Protected notes synced encrypted +* No plain text over network +* Server cannot read protected content ### Authentication -- Username/password over HTTPS only -- Sync tokens for token-based auth -- Session cookies with CSRF protection +* Username/password over HTTPS only +* Sync tokens for token-based auth +* Session cookies with CSRF protection ### Authorization -- Users can only sync their own data -- No cross-user sync support -- Sync server validates ownership +* Users can only sync their own data +* No cross-user sync support +* Sync server validates ownership ## Performance Metrics **Typical Sync Performance:** -- 1000 changes: ~2-5 seconds -- 10000 changes: ~20-50 seconds -- Initial full sync (100k notes): ~5-10 minutes + +* 1000 changes: ~2-5 seconds +* 10000 changes: ~20-50 seconds +* Initial full sync (100k notes): ~5-10 minutes **Factors:** -- Network latency -- Database size -- Number of protected notes -- Attachment sizes -## Future Improvements - -**Planned Enhancements:** -- Differential sync (binary diff) -- Peer-to-peer sync (no central server) -- Multi-server sync -- Partial sync (subtree only) -- Sync over Tor/I2P - ---- - -**See Also:** -- [ARCHITECTURE.md](ARCHITECTURE.md) - Overall architecture -- [Sync User Guide](https://triliumnext.github.io/Docs/Wiki/synchronization) -- [Sync API Source](../apps/server/src/routes/api/sync.ts) +* Network latency +* Database size +* Number of protected notes +* Attachment sizes \ No newline at end of file diff --git a/docs/QUICK_REFERENCE.md b/docs/QUICK_REFERENCE.md deleted file mode 100644 index 396cc0dd6..000000000 --- a/docs/QUICK_REFERENCE.md +++ /dev/null @@ -1,155 +0,0 @@ -# Trilium Technical Documentation - Quick Reference - -> **Start here:** [TECHNICAL_DOCUMENTATION.md](TECHNICAL_DOCUMENTATION.md) - Complete index of all documentation - -## 📖 Documentation Files - -| Document | Description | Size | Lines | -|----------|-------------|------|-------| -| [TECHNICAL_DOCUMENTATION.md](TECHNICAL_DOCUMENTATION.md) | Main index and navigation hub | 13KB | 423 | -| [ARCHITECTURE.md](ARCHITECTURE.md) | Complete system architecture | 30KB | 1,016 | -| [DATABASE.md](DATABASE.md) | Database schema and operations | 19KB | 736 | -| [SYNCHRONIZATION.md](SYNCHRONIZATION.md) | Sync protocol and implementation | 14KB | 583 | -| [SCRIPTING.md](SCRIPTING.md) | User scripting system guide | 17KB | 734 | -| [SECURITY_ARCHITECTURE.md](SECURITY_ARCHITECTURE.md) | Security implementation details | 19KB | 834 | - -**Total:** 112KB of comprehensive documentation across 4,326 lines! - -## 🎯 Quick Access by Role - -### 👤 End Users -- **Getting Started:** [User Guide](User%20Guide/User%20Guide/) -- **Scripting:** [SCRIPTING.md](SCRIPTING.md) -- **Sync Setup:** [SYNCHRONIZATION.md](SYNCHRONIZATION.md) - -### 💻 Developers -- **Architecture:** [ARCHITECTURE.md](ARCHITECTURE.md) -- **Development Setup:** [Developer Guide](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) -- **Database:** [DATABASE.md](DATABASE.md) - -### 🔒 Security Auditors -- **Security:** [SECURITY_ARCHITECTURE.md](SECURITY_ARCHITECTURE.md) -- **Encryption:** [SECURITY_ARCHITECTURE.md#encryption](SECURITY_ARCHITECTURE.md#encryption) -- **Auth:** [SECURITY_ARCHITECTURE.md#authentication](SECURITY_ARCHITECTURE.md#authentication) - -### 🏗️ System Architects -- **Overall Design:** [ARCHITECTURE.md](ARCHITECTURE.md) -- **Cache System:** [ARCHITECTURE.md#three-layer-cache-system](ARCHITECTURE.md#three-layer-cache-system) -- **Entity Model:** [ARCHITECTURE.md#entity-system](ARCHITECTURE.md#entity-system) - -### 🔧 DevOps Engineers -- **Server Installation:** [User Guide - Server Installation](User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md) -- **Docker:** [Developer Guide - Docker](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Docker.md) -- **Sync Server:** [SYNCHRONIZATION.md#sync-server-configuration](SYNCHRONIZATION.md#sync-server-configuration) - -### 📊 Database Administrators -- **Schema:** [DATABASE.md#database-schema](DATABASE.md#database-schema) -- **Maintenance:** [DATABASE.md#database-maintenance](DATABASE.md#database-maintenance) -- **Performance:** [DATABASE.md#performance-optimization](DATABASE.md#performance-optimization) - -## 🔍 Quick Topic Finder - -### Core Concepts -- **Becca Cache:** [ARCHITECTURE.md#1-becca-backend-cache](ARCHITECTURE.md#1-becca-backend-cache) -- **Froca Cache:** [ARCHITECTURE.md#2-froca-frontend-cache](ARCHITECTURE.md#2-froca-frontend-cache) -- **Entity System:** [ARCHITECTURE.md#entity-system](ARCHITECTURE.md#entity-system) -- **Widget System:** [ARCHITECTURE.md#widget-based-ui](ARCHITECTURE.md#widget-based-ui) - -### Database -- **Schema Overview:** [DATABASE.md#schema-overview](DATABASE.md#schema-overview) -- **Notes Table:** [DATABASE.md#notes-table](DATABASE.md#notes-table) -- **Branches Table:** [DATABASE.md#branches-table](DATABASE.md#branches-table) -- **Migrations:** [DATABASE.md#database-migrations](DATABASE.md#database-migrations) - -### Synchronization -- **Sync Protocol:** [SYNCHRONIZATION.md#sync-protocol](SYNCHRONIZATION.md#sync-protocol) -- **Conflict Resolution:** [SYNCHRONIZATION.md#conflict-resolution](SYNCHRONIZATION.md#conflict-resolution) -- **Entity Changes:** [SYNCHRONIZATION.md#entity-changes](SYNCHRONIZATION.md#entity-changes) - -### Scripting -- **Frontend Scripts:** [SCRIPTING.md#frontend-scripts](SCRIPTING.md#frontend-scripts) -- **Backend Scripts:** [SCRIPTING.md#backend-scripts](SCRIPTING.md#backend-scripts) -- **Script Examples:** [SCRIPTING.md#script-examples](SCRIPTING.md#script-examples) -- **API Reference:** [SCRIPTING.md#script-api](SCRIPTING.md#script-api) - -### Security -- **Authentication:** [SECURITY_ARCHITECTURE.md#authentication](SECURITY_ARCHITECTURE.md#authentication) -- **Encryption:** [SECURITY_ARCHITECTURE.md#encryption](SECURITY_ARCHITECTURE.md#encryption) -- **Input Sanitization:** [SECURITY_ARCHITECTURE.md#input-sanitization](SECURITY_ARCHITECTURE.md#input-sanitization) -- **Best Practices:** [SECURITY_ARCHITECTURE.md#security-best-practices](SECURITY_ARCHITECTURE.md#security-best-practices) - -## 📚 Learning Paths - -### New to Trilium Development -1. Read [ARCHITECTURE.md](ARCHITECTURE.md) - System overview -2. Setup environment: [Environment Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) -3. Explore [DATABASE.md](DATABASE.md) - Understand data model -4. Check [Developer Guide](Developer%20Guide/Developer%20Guide/) - -### Want to Create Scripts -1. Read [SCRIPTING.md](SCRIPTING.md) - Complete guide -2. Check [Script API](Script%20API/) - API reference -3. Review examples: [SCRIPTING.md#script-examples](SCRIPTING.md#script-examples) -4. Explore [Advanced Showcases](https://triliumnext.github.io/Docs/Wiki/advanced-showcases) - -### Setting Up Sync -1. Understand protocol: [SYNCHRONIZATION.md](SYNCHRONIZATION.md) -2. Configure server: [SYNCHRONIZATION.md#sync-server-configuration](SYNCHRONIZATION.md#sync-server-configuration) -3. Setup clients: [SYNCHRONIZATION.md#client-setup](SYNCHRONIZATION.md#client-setup) -4. Troubleshoot: [SYNCHRONIZATION.md#troubleshooting](SYNCHRONIZATION.md#troubleshooting) - -### Security Review -1. Read threat model: [SECURITY_ARCHITECTURE.md#threat-model](SECURITY_ARCHITECTURE.md#threat-model) -2. Review authentication: [SECURITY_ARCHITECTURE.md#authentication](SECURITY_ARCHITECTURE.md#authentication) -3. Check encryption: [SECURITY_ARCHITECTURE.md#encryption](SECURITY_ARCHITECTURE.md#encryption) -4. Verify best practices: [SECURITY_ARCHITECTURE.md#security-best-practices](SECURITY_ARCHITECTURE.md#security-best-practices) - -## 🗺️ Documentation Map - -``` -docs/ -├── TECHNICAL_DOCUMENTATION.md ← START HERE (Index) -│ -├── Core Technical Docs -│ ├── ARCHITECTURE.md (System design) -│ ├── DATABASE.md (Data layer) -│ ├── SYNCHRONIZATION.md (Sync system) -│ ├── SCRIPTING.md (User scripting) -│ └── SECURITY_ARCHITECTURE.md (Security) -│ -├── Developer Guide/ -│ └── Developer Guide/ (Development setup) -│ -├── User Guide/ -│ └── User Guide/ (End-user docs) -│ -└── Script API/ (API reference) -``` - -## 💡 Tips for Reading Documentation - -1. **Start with the index:** [TECHNICAL_DOCUMENTATION.md](TECHNICAL_DOCUMENTATION.md) provides an overview -2. **Use search:** Press Ctrl+F / Cmd+F to find specific topics -3. **Follow links:** Documents are cross-referenced for easy navigation -4. **Code examples:** Most docs include practical code examples -5. **See Also sections:** Check bottom of each doc for related resources - -## 🔗 External Resources - -- **Website:** https://triliumnotes.org -- **Online Docs:** https://docs.triliumnotes.org -- **GitHub:** https://github.com/TriliumNext/Trilium -- **Discussions:** https://github.com/TriliumNext/Trilium/discussions -- **Matrix Chat:** https://matrix.to/#/#triliumnext:matrix.org - -## 🤝 Contributing to Documentation - -Found an error or want to improve the docs? See: -- [Contributing Guide](../README.md#-contribute) -- [Documentation Standards](TECHNICAL_DOCUMENTATION.md#documentation-conventions) - ---- - -**Version:** 0.99.3 -**Last Updated:** November 2025 -**Maintained by:** TriliumNext Team diff --git a/docs/SCRIPTING.md b/docs/SCRIPTING.md deleted file mode 100644 index 49e548c50..000000000 --- a/docs/SCRIPTING.md +++ /dev/null @@ -1,734 +0,0 @@ -# Trilium Scripting System - -> **Related:** [ARCHITECTURE.md](ARCHITECTURE.md) | [Script API Documentation](Script%20API/) - -## Overview - -Trilium features a **powerful scripting system** that allows users to extend and customize the application without modifying source code. Scripts are written in JavaScript and can execute both in the **frontend (browser)** and **backend (Node.js)** contexts. - -## Script Types - -### Frontend Scripts - -**Location:** Attached to notes with `#run=frontendStartup` attribute - -**Execution Context:** Browser environment - -**Access:** -- Trilium Frontend API -- Browser APIs (DOM, localStorage, etc.) -- Froca (frontend cache) -- UI widgets -- No direct file system access - -**Lifecycle:** -- `frontendStartup` - Run once when Trilium loads -- `frontendReload` - Run on every note context change - -**Example:** -```javascript -// Attach to note with #run=frontendStartup -const api = window.api - -// Add custom button to toolbar -api.addButtonToToolbar({ - title: 'My Button', - icon: 'star', - action: () => { - api.showMessage('Hello from frontend!') - } -}) -``` - -### Backend Scripts - -**Location:** Attached to notes with `#run=backendStartup` attribute - -**Execution Context:** Node.js server environment - -**Access:** -- Trilium Backend API -- Node.js APIs (fs, http, etc.) -- Becca (backend cache) -- Database (SQL) -- External libraries (via require) - -**Lifecycle:** -- `backendStartup` - Run once when server starts -- Event handlers (custom events) - -**Example:** -```javascript -// Attach to note with #run=backendStartup -const api = require('@triliumnext/api') - -// Listen for note creation -api.dayjs // Example: access dayjs library - -api.onNoteCreated((note) => { - if (note.title.includes('TODO')) { - note.setLabel('priority', 'high') - } -}) -``` - -### Render Scripts - -**Location:** Attached to notes with `#customWidget` or similar attributes - -**Purpose:** Custom note rendering/widgets - -**Example:** -```javascript -// Custom widget for a note -class MyWidget extends api.NoteContextAwareWidget { - doRender() { - this.$widget = $('
      ') - .text('Custom widget content') - return this.$widget - } -} - -module.exports = MyWidget -``` - -## Script API - -### Frontend API - -**Location:** `apps/client/src/services/frontend_script_api.ts` - -**Global Access:** `window.api` - -**Key Methods:** - -```typescript -// Note Operations -api.getNote(noteId) // Get note object -api.getBranch(branchId) // Get branch object -api.getActiveNote() // Currently displayed note -api.openNote(noteId, activateNote) // Open note in UI - -// UI Operations -api.showMessage(message) // Show toast notification -api.showDialog() // Show modal dialog -api.confirm(message) // Show confirmation dialog -api.prompt(message, defaultValue) // Show input prompt - -// Tree Operations -api.getTree() // Get note tree structure -api.expandTree(noteId) // Expand tree branch -api.collapseTree(noteId) // Collapse tree branch - -// Search -api.searchForNotes(searchQuery) // Search notes -api.searchForNote(searchQuery) // Get single note - -// Navigation -api.openTabWithNote(noteId) // Open note in new tab -api.closeActiveTab() // Close current tab -api.activateNote(noteId) // Switch to note - -// Attributes -api.getAttribute(noteId, type, name) // Get attribute -api.getAttributes(noteId, type, name) // Get all matching attributes - -// Custom Widgets -api.addButtonToToolbar(def) // Add toolbar button -api.addCustomWidget(def) // Add custom widget - -// Events -api.runOnNoteOpened(callback) // Note opened event -api.runOnNoteContentChange(callback) // Content changed event - -// Utilities -api.dayjs // Date/time library -api.formatDate(date) // Format date -api.log(message) // Console log -``` - -### Backend API - -**Location:** `apps/server/src/services/backend_script_api.ts` - -**Access:** `require('@triliumnext/api')` or global `api` - -**Key Methods:** - -```typescript -// Note Operations -api.getNote(noteId) // Get note from Becca -api.getNoteWithContent(noteId) // Get note with content -api.createNote(parentNoteId, title) // Create new note -api.deleteNote(noteId) // Delete note - -// Branch Operations -api.getBranch(branchId) // Get branch -api.createBranch(noteId, parentNoteId) // Create branch (clone) - -// Attribute Operations -api.getAttribute(noteId, type, name) // Get attribute -api.createAttribute(noteId, type, name, value) // Create attribute - -// Database Access -api.sql.getRow(query, params) // Execute SQL query (single row) -api.sql.getRows(query, params) // Execute SQL query (multiple rows) -api.sql.execute(query, params) // Execute SQL statement - -// Events -api.onNoteCreated(callback) // Note created event -api.onNoteUpdated(callback) // Note updated event -api.onNoteDeleted(callback) // Note deleted event -api.onAttributeCreated(callback) // Attribute created event - -// Search -api.searchForNotes(searchQuery) // Search notes - -// Date/Time -api.dayjs // Date/time library -api.now() // Current date/time - -// Logging -api.log(message) // Log message -api.error(message) // Log error - -// External Communication -api.axios // HTTP client library - -// Utilities -api.backup.backupNow() // Trigger backup -api.export.exportSubtree(noteId) // Export notes -``` - -## Script Attributes - -### Execute Attributes - -- `#run=frontendStartup` - Execute on frontend startup -- `#run=backendStartup` - Execute on backend startup -- `#run=hourly` - Execute every hour -- `#run=daily` - Execute daily - -### Widget Attributes - -- `#customWidget` - Custom note widget -- `#widget` - Standard widget integration - -### Other Attributes - -- `#disableVersioning` - Disable automatic versioning for this note -- `#hideChildrenOverview` - Hide children in overview -- `#iconClass` - Custom icon for note - -## Entity Classes - -### Frontend Entities - -**FNote** (`apps/client/src/entities/fnote.ts`) - -```typescript -class FNote { - noteId: string - title: string - type: string - mime: string - - // Relationships - getParentNotes(): FNote[] - getChildNotes(): FNote[] - getBranches(): FBranch[] - - // Attributes - getAttribute(type, name): FAttribute - getAttributes(type?, name?): FAttribute[] - hasLabel(name): boolean - getLabelValue(name): string - - // Content - getContent(): Promise - - // Navigation - open(): void -} -``` - -**FBranch** - -```typescript -class FBranch { - branchId: string - noteId: string - parentNoteId: string - prefix: string - notePosition: number - - getNote(): FNote - getParentNote(): FNote -} -``` - -**FAttribute** - -```typescript -class FAttribute { - attributeId: string - noteId: string - type: 'label' | 'relation' - name: string - value: string - - getNote(): FNote - getTargetNote(): FNote // For relations -} -``` - -### Backend Entities - -**BNote** (`apps/server/src/becca/entities/bnote.ts`) - -```typescript -class BNote { - noteId: string - title: string - type: string - mime: string - isProtected: boolean - - // Content - getContent(): string | Buffer - setContent(content: string | Buffer): void - - // Relationships - getParentNotes(): BNote[] - getChildNotes(): BNote[] - getBranches(): BBranch[] - - // Attributes - getAttribute(type, name): BAttribute - getAttributes(type?, name?): BAttribute[] - setLabel(name, value): BAttribute - setRelation(name, targetNoteId): BAttribute - hasLabel(name): boolean - getLabelValue(name): string - - // Operations - save(): void - markAsDeleted(): void -} -``` - -**BBranch** - -```typescript -class BBranch { - branchId: string - noteId: string - parentNoteId: string - prefix: string - notePosition: number - - getNote(): BNote - getParentNote(): BNote - save(): void -} -``` - -**BAttribute** - -```typescript -class BAttribute { - attributeId: string - noteId: string - type: 'label' | 'relation' - name: string - value: string - - getNote(): BNote - getTargetNote(): BNote // For relations - save(): void -} -``` - -## Script Examples - -### Frontend Examples - -**1. Custom Toolbar Button** - -```javascript -// #run=frontendStartup -api.addButtonToToolbar({ - title: 'Export to PDF', - icon: 'file-export', - action: async () => { - const note = api.getActiveNote() - if (note) { - await api.runOnBackend('exportToPdf', [note.noteId]) - api.showMessage('Export started') - } - } -}) -``` - -**2. Auto-Save Reminder** - -```javascript -// #run=frontendStartup -let saveTimer -api.runOnNoteContentChange(() => { - clearTimeout(saveTimer) - saveTimer = setTimeout(() => { - api.showMessage('Remember to save your work!') - }, 300000) // 5 minutes -}) -``` - -**3. Note Statistics Widget** - -```javascript -// #customWidget -class StatsWidget extends api.NoteContextAwareWidget { - doRender() { - this.$widget = $('
      ') - return this.$widget - } - - async refreshWithNote(note) { - const content = await note.getContent() - const words = content.split(/\s+/).length - const chars = content.length - - this.$widget.html(` -
      Words: ${words}
      -
      Characters: ${chars}
      - `) - } -} - -module.exports = StatsWidget -``` - -### Backend Examples - -**1. Auto-Tagging on Note Creation** - -```javascript -// #run=backendStartup -api.onNoteCreated((note) => { - // Auto-tag TODO notes - if (note.title.includes('TODO')) { - note.setLabel('type', 'todo') - note.setLabel('priority', 'normal') - } - - // Auto-tag meeting notes by date - if (note.title.match(/Meeting \d{4}-\d{2}-\d{2}/)) { - note.setLabel('type', 'meeting') - const dateMatch = note.title.match(/(\d{4}-\d{2}-\d{2})/) - if (dateMatch) { - note.setLabel('date', dateMatch[1]) - } - } -}) -``` - -**2. Daily Backup Reminder** - -```javascript -// #run=daily -const todayNote = api.getTodayNote() -todayNote.setLabel('backupDone', 'false') - -// Create reminder note -api.createNote(todayNote.noteId, '🔔 Backup Reminder', { - content: 'Remember to verify today\'s backup!', - type: 'text' -}) -``` - -**3. External API Integration** - -```javascript -// #run=backendStartup -api.onNoteCreated(async (note) => { - // Sync new notes to external service - if (note.hasLabel('sync-external')) { - try { - await api.axios.post('https://external-api.com/sync', { - noteId: note.noteId, - title: note.title, - content: note.getContent() - }) - note.setLabel('lastSync', api.dayjs().format()) - } catch (error) { - api.log('Sync failed: ' + error.message) - } - } -}) -``` - -**4. Database Cleanup** - -```javascript -// #run=weekly -// Clean up old revisions -const cutoffDate = api.dayjs().subtract(90, 'days').format() - -const oldRevisions = api.sql.getRows(` - SELECT revisionId FROM revisions - WHERE utcDateCreated < ? -`, [cutoffDate]) - -api.log(`Deleting ${oldRevisions.length} old revisions`) - -for (const row of oldRevisions) { - api.sql.execute('DELETE FROM revisions WHERE revisionId = ?', [row.revisionId]) -} -``` - -## Script Storage - -**Storage Location:** Scripts are stored as regular notes - -**Identifying Scripts:** -- Have `#run` attribute or `#customWidget` attribute -- Type is typically `code` with MIME `application/javascript` - -**Script Note Structure:** -``` -📁 Scripts (folder note) -├── 📜 Frontend Scripts -│ ├── Custom Toolbar Button (#run=frontendStartup) -│ └── Statistics Widget (#customWidget) -└── 📜 Backend Scripts - ├── Auto-Tagger (#run=backendStartup) - └── Daily Backup (#run=daily) -``` - -## Script Execution - -### Frontend Execution - -**Timing:** -1. Trilium frontend loads -2. Froca cache initializes -3. Script notes with `#run=frontendStartup` are found -4. Scripts execute in dependency order - -**Isolation:** -- Each script runs in separate context -- Shared `window.api` object -- Can access global window object - -### Backend Execution - -**Timing:** -1. Server starts -2. Becca cache loads -3. Script notes with `#run=backendStartup` are found -4. Scripts execute in dependency order - -**Isolation:** -- Each script is a separate module -- Can require Node.js modules -- Shared `api` global - -### Error Handling - -**Frontend:** -```javascript -try { - // Script code -} catch (error) { - api.showError('Script error: ' + error.message) - console.error(error) -} -``` - -**Backend:** -```javascript -try { - // Script code -} catch (error) { - api.log('Script error: ' + error.message) - console.error(error) -} -``` - -## Security Considerations - -### Frontend Scripts - -**Risks:** -- Can access all notes via Froca -- Can manipulate DOM -- Can make API calls -- Limited by browser security model - -**Mitigations:** -- User must trust scripts they add -- Scripts run with user privileges -- No access to file system - -### Backend Scripts - -**Risks:** -- Full Node.js access -- Can execute system commands -- Can access file system -- Can make network requests - -**Mitigations:** -- Scripts are user-created (trusted) -- Single-user model (no privilege escalation) -- Review scripts before adding `#run` attribute - -### Best Practices - -1. **Review script code** before adding execution attributes -2. **Use specific attributes** rather than wildcard searches -3. **Avoid eval()** and dynamic code execution -4. **Validate inputs** in scripts -5. **Handle errors** gracefully -6. **Log important actions** for audit trail - -## Performance Considerations - -### Optimization Tips - -**1. Cache Results:** -```javascript -// Bad: Re-query on every call -function getConfig() { - return api.getNote('config').getContent() -} - -// Good: Cache the result -let cachedConfig -function getConfig() { - if (!cachedConfig) { - cachedConfig = api.getNote('config').getContent() - } - return cachedConfig -} -``` - -**2. Use Efficient Queries:** -```javascript -// Bad: Load all notes and filter -const todos = api.searchForNotes('#type=todo') - -// Good: Use specific search -const todos = api.searchForNotes('#type=todo #status=pending') -``` - -**3. Batch Operations:** -```javascript -// Bad: Save after each change -notes.forEach(note => { - note.title = 'Updated' - note.save() -}) - -// Good: Batch changes -notes.forEach(note => { - note.title = 'Updated' -}) -// Save happens in batch -``` - -**4. Debounce Event Handlers:** -```javascript -let timeout -api.runOnNoteContentChange(() => { - clearTimeout(timeout) - timeout = setTimeout(() => { - // Process change - }, 500) -}) -``` - -## Debugging Scripts - -### Frontend Debugging - -**Browser DevTools:** -```javascript -console.log('Debug info:', data) -debugger // Breakpoint -``` - -**Trilium Log:** -```javascript -api.log('Script executed') -``` - -### Backend Debugging - -**Console Output:** -```javascript -console.log('Backend debug:', data) -api.log('Script log message') -``` - -**Inspect Becca:** -```javascript -api.log('Note count:', Object.keys(api.becca.notes).length) -``` - -## Advanced Topics - -### Custom Note Types - -Scripts can implement custom note type handlers: - -```javascript -// Register custom type -api.registerNoteType({ - type: 'mytype', - mime: 'application/x-mytype', - renderNote: (note) => { - // Custom rendering - } -}) -``` - -### External Libraries - -**Frontend:** -```javascript -// Load external library -const myLib = await import('https://cdn.example.com/lib.js') -``` - -**Backend:** -```javascript -// Use Node.js require -const fs = require('fs') -const axios = require('axios') -``` - -### State Persistence - -**Frontend:** -```javascript -// Use localStorage -localStorage.setItem('myScript:data', JSON.stringify(data)) -const data = JSON.parse(localStorage.getItem('myScript:data')) -``` - -**Backend:** -```javascript -// Store in special note -const stateNote = api.getNote('script-state-note') -stateNote.setContent(JSON.stringify(data)) - -const data = JSON.parse(stateNote.getContent()) -``` - ---- - -**See Also:** -- [Script API Documentation](Script%20API/) - Complete API reference -- [Advanced Showcases](https://triliumnext.github.io/Docs/Wiki/advanced-showcases) - Example scripts -- [ARCHITECTURE.md](ARCHITECTURE.md) - Overall architecture diff --git a/docs/SECURITY_ARCHITECTURE.md b/docs/SECURITY_ARCHITECTURE.md deleted file mode 100644 index 27993deac..000000000 --- a/docs/SECURITY_ARCHITECTURE.md +++ /dev/null @@ -1,834 +0,0 @@ -# Trilium Security Architecture - -> **Related:** [ARCHITECTURE.md](ARCHITECTURE.md) | [SECURITY.md](../SECURITY.md) - -## Overview - -Trilium implements a **defense-in-depth security model** with multiple layers of protection for user data. The security architecture covers authentication, authorization, encryption, input sanitization, and secure communication. - -## Security Principles - -1. **Data Privacy**: User data is protected at rest and in transit -2. **Encryption**: Per-note encryption for sensitive content -3. **Authentication**: Multiple authentication methods supported -4. **Authorization**: Single-user model with granular protected sessions -5. **Input Validation**: All user input sanitized -6. **Secure Defaults**: Security features enabled by default -7. **Transparency**: Open source allows security audits - -## Threat Model - -### Threats Considered - -1. **Unauthorized Access** - - Physical access to device - - Network eavesdropping - - Stolen credentials - - Session hijacking - -2. **Data Exfiltration** - - Malicious scripts - - XSS attacks - - SQL injection - - CSRF attacks - -3. **Data Corruption** - - Malicious modifications - - Database tampering - - Sync conflicts - -4. **Privacy Leaks** - - Unencrypted backups - - Search indexing - - Temporary files - - Memory dumps - -### Out of Scope - -- Nation-state attackers -- Zero-day vulnerabilities in dependencies -- Hardware vulnerabilities (Spectre, Meltdown) -- Physical access with unlimited time -- Quantum computing attacks - -## Authentication - -### Password Authentication - -**Implementation:** `apps/server/src/services/password.ts` - -**Password Storage:** -```typescript -// Password is never stored directly -const salt = crypto.randomBytes(32) -const derivedKey = crypto.pbkdf2Sync(password, salt, 10000, 32, 'sha256') -const verificationHash = crypto.createHash('sha256') - .update(derivedKey) - .digest('hex') - -// Store only salt and verification hash -sql.insert('user_data', { - salt: salt.toString('hex'), - derivedKey: derivedKey.toString('hex') // Used for encryption -}) - -sql.insert('options', { - name: 'passwordVerificationHash', - value: verificationHash -}) -``` - -**Password Requirements:** -- Minimum length: 4 characters (configurable) -- No maximum length -- All characters allowed -- Can be changed by user - -**Login Process:** -```typescript -// 1. User submits password -POST /api/login/password -Body: { password: "user-password" } - -// 2. Server derives key -const derivedKey = crypto.pbkdf2Sync(password, salt, 10000, 32, 'sha256') - -// 3. Verify against stored hash -const verificationHash = crypto.createHash('sha256') - .update(derivedKey) - .digest('hex') - -if (verificationHash === storedHash) { - // 4. Create session - req.session.loggedIn = true - req.session.regenerate() -} -``` - -### TOTP (Two-Factor Authentication) - -**Implementation:** `apps/server/src/routes/api/login.ts` - -**Setup Process:** -```typescript -// 1. Generate secret -const secret = speakeasy.generateSecret({ - name: `Trilium (${username})`, - length: 32 -}) - -// 2. Store encrypted secret -const encryptedSecret = encrypt(secret.base32, dataKey) -sql.insert('options', { - name: 'totpSecret', - value: encryptedSecret -}) - -// 3. Generate QR code -const qrCodeUrl = secret.otpauth_url -``` - -**Verification:** -```typescript -// User submits TOTP token -POST /api/login/totp -Body: { token: "123456" } - -// Verify token -const secret = decrypt(encryptedSecret, dataKey) -const verified = speakeasy.totp.verify({ - secret: secret, - encoding: 'base32', - token: token, - window: 1 // Allow 1 time step tolerance -}) -``` - -### OpenID Connect - -**Implementation:** `apps/server/src/routes/api/login.ts` - -**Supported Providers:** -- Any OpenID Connect compatible provider -- Google, GitHub, Auth0, etc. - -**Flow:** -```typescript -// 1. Redirect to provider -GET /api/login/openid - -// 2. Provider redirects back with code -GET /api/login/openid/callback?code=... - -// 3. Exchange code for tokens -const tokens = await openidClient.callback(redirectUri, req.query) - -// 4. Verify ID token -const claims = tokens.claims() - -// 5. Create session -req.session.loggedIn = true -``` - -### Session Management - -**Session Storage:** SQLite database (sessions table) - -**Session Configuration:** -```typescript -app.use(session({ - secret: sessionSecret, - resave: false, - saveUninitialized: false, - rolling: true, - cookie: { - maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days - httpOnly: true, - secure: isHttps, - sameSite: 'lax' - }, - store: new SqliteStore({ - db: db, - table: 'sessions' - }) -})) -``` - -**Session Invalidation:** -- Automatic timeout after inactivity -- Manual logout clears session -- Server restart invalidates all sessions (optional) - -## Authorization - -### Single-User Model - -**Desktop:** -- Single user (owner of device) -- No multi-user support -- Full access to all notes - -**Server:** -- Single user per installation -- Authentication required for all operations -- No user roles or permissions - -### Protected Sessions - -**Purpose:** Temporary access to encrypted (protected) notes - -**Implementation:** `apps/server/src/services/protected_session.ts` - -**Workflow:** -```typescript -// 1. User enters password for protected notes -POST /api/protected-session/enter -Body: { password: "protected-password" } - -// 2. Derive encryption key -const protectedDataKey = deriveKey(password) - -// 3. Verify password (decrypt known encrypted value) -const decrypted = decrypt(testValue, protectedDataKey) -if (decrypted === expectedValue) { - // 4. Store in memory (not in session) - protectedSessionHolder.setProtectedDataKey(protectedDataKey) - - // 5. Set timeout - setTimeout(() => { - protectedSessionHolder.clearProtectedDataKey() - }, timeout) -} -``` - -**Protected Session Timeout:** -- Default: 10 minutes (configurable) -- Extends on activity -- Cleared on browser close -- Separate from main session - -### API Authorization - -**Internal API:** -- Requires authenticated session -- CSRF token validation -- Same-origin policy - -**ETAPI (External API):** -- Token-based authentication -- No session required -- Rate limiting - -## Encryption - -### Note Encryption - -**Encryption Algorithm:** AES-256-CBC - -**Key Hierarchy:** -``` -User Password - ↓ (PBKDF2) -Data Key (for protected notes) - ↓ (AES-256) -Protected Note Content -``` - -**Encryption Process:** -```typescript -// 1. Generate IV (initialization vector) -const iv = crypto.randomBytes(16) - -// 2. Encrypt content -const cipher = crypto.createCipheriv('aes-256-cbc', dataKey, iv) -let encrypted = cipher.update(content, 'utf8', 'base64') -encrypted += cipher.final('base64') - -// 3. Prepend IV to encrypted content -const encryptedBlob = iv.toString('base64') + ':' + encrypted - -// 4. Store in database -sql.insert('blobs', { - blobId: blobId, - content: encryptedBlob -}) -``` - -**Decryption Process:** -```typescript -// 1. Split IV and encrypted content -const [ivBase64, encryptedData] = encryptedBlob.split(':') -const iv = Buffer.from(ivBase64, 'base64') - -// 2. Decrypt -const decipher = crypto.createDecipheriv('aes-256-cbc', dataKey, iv) -let decrypted = decipher.update(encryptedData, 'base64', 'utf8') -decrypted += decipher.final('utf8') - -return decrypted -``` - -**Protected Note Metadata:** -- Title is NOT encrypted (for tree display) -- Type and MIME are NOT encrypted -- Content IS encrypted -- Attributes CAN be encrypted (optional) - -### Data Key Management - -**Master Data Key:** -```typescript -// Generated once during setup -const dataKey = crypto.randomBytes(32) // 256 bits - -// Encrypted with derived key from user password -const derivedKey = crypto.pbkdf2Sync(password, salt, 10000, 32, 'sha256') -const encryptedDataKey = encrypt(dataKey, derivedKey) - -// Stored in database -sql.insert('options', { - name: 'encryptedDataKey', - value: encryptedDataKey.toString('hex') -}) -``` - -**Key Rotation:** -- Not currently supported -- Requires re-encrypting all protected notes -- Planned for future version - -### Transport Encryption - -**HTTPS:** -- Required for server installations (recommended) -- TLS 1.2+ only -- Strong cipher suites preferred -- Certificate validation enabled - -**Desktop:** -- Local communication (no network) -- No HTTPS required - -### Backup Encryption - -**Database Backups:** -- Protected notes remain encrypted in backup -- Backup file should be protected separately -- Consider encrypting backup storage location - -## Input Sanitization - -### XSS Prevention - -**HTML Sanitization:** - -Location: `apps/client/src/services/dompurify.ts` - -```typescript -import DOMPurify from 'dompurify' - -// Configure DOMPurify -DOMPurify.setConfig({ - ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'div', ...], - ALLOWED_ATTR: ['href', 'title', 'class', 'id', ...], - ALLOW_DATA_ATTR: false -}) - -// Sanitize HTML before rendering -const cleanHtml = DOMPurify.sanitize(userHtml) -``` - -**CKEditor Configuration:** -```typescript -// apps/client/src/widgets/type_widgets/text_type_widget.ts -ClassicEditor.create(element, { - // Restrict allowed content - htmlSupport: { - allow: [ - { name: /./, attributes: true, classes: true, styles: true } - ], - disallow: [ - { name: 'script' }, - { name: 'iframe', attributes: /^(?!src$).*/ } - ] - } -}) -``` - -**Content Security Policy:** -```typescript -// apps/server/src/main.ts -app.use((req, res, next) => { - res.setHeader('Content-Security-Policy', - "default-src 'self'; " + - "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " + - "style-src 'self' 'unsafe-inline'; " + - "img-src 'self' data: blob:;" - ) - next() -}) -``` - -### SQL Injection Prevention - -**Parameterized Queries:** -```typescript -// GOOD - Safe from SQL injection -const notes = sql.getRows( - 'SELECT * FROM notes WHERE title = ?', - [userInput] -) - -// BAD - Vulnerable to SQL injection -const notes = sql.getRows( - `SELECT * FROM notes WHERE title = '${userInput}'` -) -``` - -**ORM Usage:** -```typescript -// Entity-based access prevents SQL injection -const note = becca.getNote(noteId) -note.title = userInput // Sanitized by entity -note.save() // Parameterized query -``` - -### CSRF Prevention - -**CSRF Token Validation:** - -Location: `apps/server/src/routes/middleware/csrf.ts` - -```typescript -// Generate CSRF token -const csrfToken = crypto.randomBytes(32).toString('hex') -req.session.csrfToken = csrfToken - -// Validate on state-changing requests -app.use((req, res, next) => { - if (['POST', 'PUT', 'DELETE'].includes(req.method)) { - const token = req.headers['x-csrf-token'] - if (token !== req.session.csrfToken) { - return res.status(403).json({ error: 'CSRF token mismatch' }) - } - } - next() -}) -``` - -**Client-Side:** -```typescript -// apps/client/src/services/server.ts -const csrfToken = getCsrfToken() - -fetch('/api/notes', { - method: 'POST', - headers: { - 'X-CSRF-Token': csrfToken, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data) -}) -``` - -### File Upload Validation - -**Validation:** -```typescript -// apps/server/src/routes/api/attachments.ts -const allowedMimeTypes = [ - 'image/jpeg', - 'image/png', - 'application/pdf', - // ... -] - -if (!allowedMimeTypes.includes(file.mimetype)) { - throw new Error('File type not allowed') -} - -// Validate file size -const maxSize = 100 * 1024 * 1024 // 100 MB -if (file.size > maxSize) { - throw new Error('File too large') -} - -// Sanitize filename -const sanitizedFilename = path.basename(file.originalname) - .replace(/[^a-z0-9.-]/gi, '_') -``` - -## Network Security - -### HTTPS Configuration - -**Server Setup:** -```typescript -// apps/server/src/main.ts -const httpsOptions = { - key: fs.readFileSync('server.key'), - cert: fs.readFileSync('server.cert') -} - -https.createServer(httpsOptions, app).listen(443) -``` - -**Certificate Validation:** -- Require valid certificates in production -- Self-signed certificates allowed for development -- Certificate pinning not implemented - -### Secure Headers - -```typescript -// apps/server/src/main.ts -app.use((req, res, next) => { - // Prevent clickjacking - res.setHeader('X-Frame-Options', 'SAMEORIGIN') - - // Prevent MIME sniffing - res.setHeader('X-Content-Type-Options', 'nosniff') - - // XSS protection - res.setHeader('X-XSS-Protection', '1; mode=block') - - // Referrer policy - res.setHeader('Referrer-Policy', 'same-origin') - - // HTTPS upgrade - if (req.secure) { - res.setHeader('Strict-Transport-Security', 'max-age=31536000') - } - - next() -}) -``` - -### Rate Limiting - -**API Rate Limiting:** -```typescript -// apps/server/src/routes/middleware/rate_limit.ts -const rateLimit = require('express-rate-limit') - -const apiLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 1000, // Limit each IP to 1000 requests per window - message: 'Too many requests from this IP' -}) - -app.use('/api/', apiLimiter) -``` - -**Login Rate Limiting:** -```typescript -const loginLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, - max: 5, // 5 failed attempts - skipSuccessfulRequests: true -}) - -app.post('/api/login/password', loginLimiter, loginHandler) -``` - -## Data Security - -### Secure Data Deletion - -**Soft Delete:** -```typescript -// Mark as deleted (sync first) -note.isDeleted = 1 -note.deleteId = generateUUID() -note.save() - -// Entity change tracked for sync -addEntityChange('notes', noteId, note) -``` - -**Hard Delete (Erase):** -```typescript -// After sync completed -sql.execute('DELETE FROM notes WHERE noteId = ?', [noteId]) -sql.execute('DELETE FROM branches WHERE noteId = ?', [noteId]) -sql.execute('DELETE FROM attributes WHERE noteId = ?', [noteId]) - -// Mark entity change as erased -sql.execute('UPDATE entity_changes SET isErased = 1 WHERE entityId = ?', [noteId]) -``` - -**Blob Cleanup:** -```typescript -// Find orphaned blobs (not referenced by any note/revision/attachment) -const orphanedBlobs = sql.getRows(` - SELECT blobId FROM blobs - WHERE blobId NOT IN (SELECT blobId FROM notes WHERE blobId IS NOT NULL) - AND blobId NOT IN (SELECT blobId FROM revisions WHERE blobId IS NOT NULL) - AND blobId NOT IN (SELECT blobId FROM attachments WHERE blobId IS NOT NULL) -`) - -// Delete orphaned blobs -for (const blob of orphanedBlobs) { - sql.execute('DELETE FROM blobs WHERE blobId = ?', [blob.blobId]) -} -``` - -### Memory Security - -**Protected Data in Memory:** -- Protected data keys stored in memory only -- Cleared on timeout -- Not written to disk -- Not in session storage - -**Memory Cleanup:** -```typescript -// Clear sensitive data -const clearSensitiveData = () => { - protectedDataKey = null - - // Force garbage collection if available - if (global.gc) { - global.gc() - } -} -``` - -### Temporary Files - -**Secure Temporary Files:** -```typescript -const tempDir = os.tmpdir() -const tempFile = path.join(tempDir, `trilium-${crypto.randomBytes(16).toString('hex')}`) - -// Write temp file -fs.writeFileSync(tempFile, data, { mode: 0o600 }) // Owner read/write only - -// Clean up after use -fs.unlinkSync(tempFile) -``` - -## Dependency Security - -### Vulnerability Scanning - -**Tools:** -- `npm audit` - Check for known vulnerabilities -- Renovate bot - Automatic dependency updates -- GitHub Dependabot alerts - -**Process:** -```bash -# Check for vulnerabilities -npm audit - -# Fix automatically -npm audit fix - -# Manual review for breaking changes -npm audit fix --force -``` - -### Dependency Pinning - -**package.json:** -```json -{ - "dependencies": { - "express": "4.18.2", // Exact version - "better-sqlite3": "^9.2.2" // Compatible versions - } -} -``` - -**pnpm Overrides:** -```json -{ - "pnpm": { - "overrides": { - "lodash@<4.17.21": ">=4.17.21", // Force minimum version - "axios@<0.21.2": ">=0.21.2" - } - } -} -``` - -### Patch Management - -**pnpm Patches:** -```bash -# Create patch -pnpm patch @ckeditor/ckeditor5 - -# Edit files in temporary directory -# ... - -# Generate patch file -pnpm patch-commit /tmp/ckeditor5-patch - -# Patch applied automatically on install -``` - -## Security Best Practices - -### For Users - -1. **Strong Passwords** - - Use unique password for Trilium - - Enable TOTP 2FA - - Protect password manager - -2. **Protected Notes** - - Use for sensitive information - - Set reasonable session timeout - - Don't leave sessions unattended - -3. **Backups** - - Regular backups to secure location - - Encrypt backup storage - - Test backup restoration - -4. **Server Setup** - - Use HTTPS only - - Keep software updated - - Firewall configuration - - Use reverse proxy (nginx, Caddy) - -5. **Scripts** - - Review scripts before using - - Be cautious with external scripts - - Understand script permissions - -### For Developers - -1. **Code Review** - - Review all security-related changes - - Test authentication/authorization changes - - Validate input sanitization - -2. **Testing** - - Write security tests - - Test edge cases - - Penetration testing - -3. **Dependencies** - - Regular updates - - Audit new dependencies - - Monitor security advisories - -4. **Secrets** - - No secrets in source code - - Use environment variables - - Secure key generation - -## Security Auditing - -### Logs - -**Security Events Logged:** -- Login attempts (success/failure) -- Protected session access -- Password changes -- ETAPI token usage -- Failed CSRF validations - -**Log Location:** -- Desktop: Console output -- Server: Log files or stdout - -### Monitoring - -**Metrics to Monitor:** -- Failed login attempts -- API error rates -- Unusual database changes -- Large exports/imports - -## Incident Response - -### Security Issue Reporting - -**Process:** -1. Email security@triliumnext.com -2. Include vulnerability details -3. Provide reproduction steps -4. Allow reasonable disclosure time - -**Response:** -1. Acknowledge within 48 hours -2. Investigate and validate -3. Develop fix -4. Coordinate disclosure -5. Release patch - -### Breach Response - -**If Compromised:** -1. Change password immediately -2. Review recent activity -3. Check for unauthorized changes -4. Restore from backup if needed -5. Update security settings - -## Future Security Enhancements - -**Planned:** -- Hardware security key support (U2F/WebAuthn) -- End-to-end encryption for sync -- Zero-knowledge architecture option -- Encryption key rotation -- Audit log enhancements -- Per-note access controls - -**Under Consideration:** -- Multi-user support with permissions -- Blockchain-based sync verification -- Homomorphic encryption for search -- Quantum-resistant encryption - ---- - -**See Also:** -- [SECURITY.md](../SECURITY.md) - Security policy -- [ARCHITECTURE.md](ARCHITECTURE.md) - Overall architecture -- [Protected Notes Guide](https://triliumnext.github.io/Docs/Wiki/protected-notes) diff --git a/docs/TECHNICAL_DOCUMENTATION.md b/docs/TECHNICAL_DOCUMENTATION.md deleted file mode 100644 index 445df0530..000000000 --- a/docs/TECHNICAL_DOCUMENTATION.md +++ /dev/null @@ -1,423 +0,0 @@ -# Trilium Notes - Technical Documentation Index - -Welcome to the comprehensive technical and architectural documentation for Trilium Notes. This index provides quick access to all technical documentation resources. - -## 📚 Core Architecture Documentation - -### [ARCHITECTURE.md](ARCHITECTURE.md) -**Main technical architecture document** covering the complete system design. - -**Topics Covered:** -- High-level architecture overview -- Monorepo structure and organization -- Core architecture patterns (Becca, Froca, Shaca) -- Entity system and data model -- Widget-based UI architecture -- Frontend and backend architecture -- API architecture (Internal, ETAPI, WebSocket) -- Build system and tooling -- Testing strategy -- Security overview - -**Audience:** Developers, architects, contributors - ---- - -### [DATABASE.md](DATABASE.md) -**Complete database architecture and schema documentation.** - -**Topics Covered:** -- SQLite database structure -- Entity tables (notes, branches, attributes, revisions, attachments, blobs) -- System tables (options, entity_changes, sessions) -- Data relationships and integrity -- Database access patterns -- Migrations and versioning -- Performance optimization -- Backup and maintenance -- Security considerations - -**Audience:** Backend developers, database administrators - ---- - -### [SYNCHRONIZATION.md](SYNCHRONIZATION.md) -**Detailed synchronization protocol and implementation.** - -**Topics Covered:** -- Sync architecture overview -- Entity change tracking -- Sync protocol (handshake, pull, push) -- Conflict resolution strategies -- Protected notes synchronization -- Performance optimizations -- Error handling and retry logic -- Sync server configuration -- WebSocket real-time updates -- Troubleshooting guide - -**Audience:** Advanced users, sync server administrators, contributors - ---- - -### [SCRIPTING.md](SCRIPTING.md) -**Comprehensive guide to the Trilium scripting system.** - -**Topics Covered:** -- Script types (frontend, backend, render) -- Frontend API reference -- Backend API reference -- Entity classes (FNote, BNote, etc.) -- Script examples and patterns -- Script storage and execution -- Security considerations -- Performance optimization -- Debugging techniques -- Advanced topics - -**Audience:** Power users, script developers, plugin creators - ---- - -### [SECURITY_ARCHITECTURE.md](SECURITY_ARCHITECTURE.md) -**In-depth security architecture and implementation.** - -**Topics Covered:** -- Security principles and threat model -- Authentication methods (password, TOTP, OpenID) -- Session management -- Authorization and protected sessions -- Encryption (notes, transport, backups) -- Input sanitization (XSS, SQL injection, CSRF) -- Network security (HTTPS, headers, rate limiting) -- Data security and secure deletion -- Dependency security -- Security best practices -- Incident response - -**Audience:** Security engineers, administrators, auditors - ---- - -## 🔧 Developer Documentation - -### [Developer Guide](Developer%20Guide/Developer%20Guide/) -Collection of developer-focused documentation for contributing to Trilium. - -**Key Documents:** -- [Environment Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) - Setting up development environment -- [Project Structure](Developer%20Guide/Developer%20Guide/Project%20Structure.md) - Monorepo organization -- [Development and Architecture](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/) - Various development topics - -**Topics Include:** -- Local development setup -- Building and deployment -- Adding new note types -- Database schema details -- Internationalization -- Icons and UI customization -- Docker development -- Troubleshooting - -**Audience:** Contributors, developers - ---- - -## 📖 User Documentation - -### [User Guide](User%20Guide/User%20Guide/) -Comprehensive end-user documentation for using Trilium. - -**Key Sections:** -- Installation & Setup -- Basic Concepts and Features -- Note Types -- Advanced Usage -- Synchronization -- Import/Export - -**Audience:** End users, administrators - ---- - -### [Script API](Script%20API/) -Complete API reference for user scripting. - -**Coverage:** -- Frontend API methods -- Backend API methods -- Entity properties and methods -- Event handlers -- Utility functions - -**Audience:** Script developers, power users - ---- - -## 🚀 Quick Start Guides - -### For Users -1. [Installation Guide](User%20Guide/User%20Guide/Installation%20&%20Setup/) - Get Trilium running -2. [Basic Concepts](User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/) - Learn the fundamentals -3. [Scripting Guide](SCRIPTING.md) - Extend Trilium with scripts - -### For Developers -1. [Environment Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) - Setup development environment -2. [Architecture Overview](ARCHITECTURE.md) - Understand the system -3. [Contributing Guide](../README.md#-contribute) - Start contributing - -### For Administrators -1. [Server Installation](User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md) - Deploy Trilium server -2. [Synchronization Setup](SYNCHRONIZATION.md) - Configure sync -3. [Security Best Practices](SECURITY_ARCHITECTURE.md#security-best-practices) - Secure your installation - ---- - -## 🔍 Documentation by Topic - -### Architecture & Design -- [Overall Architecture](ARCHITECTURE.md) -- [Monorepo Structure](ARCHITECTURE.md#monorepo-structure) -- [Three-Layer Cache System](ARCHITECTURE.md#three-layer-cache-system) -- [Entity System](ARCHITECTURE.md#entity-system) -- [Widget-Based UI](ARCHITECTURE.md#widget-based-ui) - -### Data & Storage -- [Database Architecture](DATABASE.md) -- [Entity Tables](DATABASE.md#entity-tables) -- [Data Relationships](DATABASE.md#data-relationships) -- [Blob Storage](DATABASE.md#blobs-table) -- [Database Migrations](DATABASE.md#database-migrations) - -### Synchronization -- [Sync Architecture](SYNCHRONIZATION.md#sync-architecture) -- [Sync Protocol](SYNCHRONIZATION.md#sync-protocol) -- [Conflict Resolution](SYNCHRONIZATION.md#conflict-resolution) -- [Protected Notes Sync](SYNCHRONIZATION.md#protected-notes-sync) -- [WebSocket Sync](SYNCHRONIZATION.md#websocket-sync-updates) - -### Security -- [Authentication](SECURITY_ARCHITECTURE.md#authentication) -- [Encryption](SECURITY_ARCHITECTURE.md#encryption) -- [Input Sanitization](SECURITY_ARCHITECTURE.md#input-sanitization) -- [Network Security](SECURITY_ARCHITECTURE.md#network-security) -- [Security Best Practices](SECURITY_ARCHITECTURE.md#security-best-practices) - -### Scripting & Extensibility -- [Script Types](SCRIPTING.md#script-types) -- [Frontend API](SCRIPTING.md#frontend-api) -- [Backend API](SCRIPTING.md#backend-api) -- [Script Examples](SCRIPTING.md#script-examples) -- [Custom Widgets](SCRIPTING.md#render-scripts) - -### Frontend -- [Client Architecture](ARCHITECTURE.md#frontend-architecture) -- [Widget System](ARCHITECTURE.md#widget-based-ui) -- [Event System](ARCHITECTURE.md#event-system) -- [Froca Cache](ARCHITECTURE.md#2-froca-frontend-cache) -- [UI Components](ARCHITECTURE.md#ui-components) - -### Backend -- [Server Architecture](ARCHITECTURE.md#backend-architecture) -- [Service Layer](ARCHITECTURE.md#service-layer) -- [Route Structure](ARCHITECTURE.md#route-structure) -- [Becca Cache](ARCHITECTURE.md#1-becca-backend-cache) -- [Middleware](ARCHITECTURE.md#middleware) - -### Build & Deploy -- [Build System](ARCHITECTURE.md#build-system) -- [Package Manager](ARCHITECTURE.md#package-manager-pnpm) -- [Build Tools](ARCHITECTURE.md#build-tools) -- [Docker](Developer%20Guide/Developer%20Guide/Development%20and%20architecture/Docker.md) -- [Deployment](Developer%20Guide/Developer%20Guide/Building%20and%20deployment/) - -### Testing -- [Testing Strategy](ARCHITECTURE.md#testing-strategy) -- [Test Organization](ARCHITECTURE.md#test-organization) -- [E2E Testing](ARCHITECTURE.md#e2e-testing) - ---- - -## 📋 Reference Documentation - -### File Locations -``` -trilium/ -├── apps/ -│ ├── client/ # Frontend application -│ ├── server/ # Backend server -│ ├── desktop/ # Electron app -│ └── ... -├── packages/ -│ ├── commons/ # Shared code -│ ├── ckeditor5/ # Rich text editor -│ └── ... -├── docs/ -│ ├── ARCHITECTURE.md # Main architecture doc -│ ├── DATABASE.md # Database documentation -│ ├── SYNCHRONIZATION.md # Sync documentation -│ ├── SCRIPTING.md # Scripting guide -│ ├── SECURITY_ARCHITECTURE.md # Security documentation -│ ├── Developer Guide/ # Developer docs -│ ├── User Guide/ # User docs -│ └── Script API/ # API reference -└── ... -``` - -### Key Source Files -- **Backend Entry:** `apps/server/src/main.ts` -- **Frontend Entry:** `apps/client/src/desktop.ts` / `apps/client/src/index.ts` -- **Becca Cache:** `apps/server/src/becca/becca.ts` -- **Froca Cache:** `apps/client/src/services/froca.ts` -- **Database Schema:** `apps/server/src/assets/db/schema.sql` -- **Backend API:** `apps/server/src/services/backend_script_api.ts` -- **Frontend API:** `apps/client/src/services/frontend_script_api.ts` - -### Important Directories -- **Entities:** `apps/server/src/becca/entities/` -- **Widgets:** `apps/client/src/widgets/` -- **Services:** `apps/server/src/services/` -- **Routes:** `apps/server/src/routes/` -- **Migrations:** `apps/server/src/migrations/` -- **Tests:** Various `*.spec.ts` files throughout - ---- - -## 🎯 Common Tasks - -### Understanding the Codebase -1. Read [ARCHITECTURE.md](ARCHITECTURE.md) for overview -2. Explore [Monorepo Structure](ARCHITECTURE.md#monorepo-structure) -3. Review [Entity System](ARCHITECTURE.md#entity-system) -4. Check [Key Files](ARCHITECTURE.md#key-files-for-understanding-architecture) - -### Adding Features -1. Review relevant architecture documentation -2. Check [Developer Guide](Developer%20Guide/Developer%20Guide/) -3. Follow existing patterns in codebase -4. Write tests -5. Update documentation - -### Debugging Issues -1. Check [Troubleshooting](Developer%20Guide/Developer%20Guide/Troubleshooting/) -2. Review [Database](DATABASE.md) for data issues -3. Check [Synchronization](SYNCHRONIZATION.md) for sync issues -4. Review [Security](SECURITY_ARCHITECTURE.md) for auth issues - -### Performance Optimization -1. [Database Performance](DATABASE.md#performance-optimization) -2. [Cache Optimization](ARCHITECTURE.md#caching-system) -3. [Build Optimization](ARCHITECTURE.md#build-system) -4. [Script Performance](SCRIPTING.md#performance-considerations) - ---- - -## 🔗 External Resources - -### Official Links -- **Website:** https://triliumnotes.org -- **Documentation:** https://docs.triliumnotes.org -- **GitHub:** https://github.com/TriliumNext/Trilium -- **Discussions:** https://github.com/TriliumNext/Trilium/discussions -- **Matrix Chat:** https://matrix.to/#/#triliumnext:matrix.org - -### Community Resources -- **Awesome Trilium:** https://github.com/Nriver/awesome-trilium -- **TriliumRocks:** https://trilium.rocks/ -- **Wiki:** https://triliumnext.github.io/Docs/Wiki/ - -### Related Projects -- **TriliumDroid:** https://github.com/FliegendeWurst/TriliumDroid -- **Web Clipper:** Included in main repository - ---- - -## 📝 Documentation Conventions - -### Document Structure -- Overview section -- Table of contents -- Main content with headings -- Code examples where relevant -- "See Also" references - -### Code Examples -```typescript -// TypeScript examples with comments -const example = 'value' -``` - -```sql --- SQL examples with formatting -SELECT * FROM notes WHERE noteId = ? -``` - -### Cross-References -- Use relative links: `[text](path/to/file.md)` -- Reference sections: `[text](file.md#section)` -- External links: Full URLs - -### Maintenance -- Review on major releases -- Update for architectural changes -- Add examples for new features -- Keep API references current - ---- - -## 🤝 Contributing to Documentation - -### What to Document -- New features and APIs -- Architecture changes -- Migration guides -- Performance tips -- Security considerations - -### How to Contribute -1. Edit markdown files in `docs/` -2. Follow existing structure and style -3. Include code examples -4. Test links and formatting -5. Submit pull request - -### Documentation Standards -- Clear, concise language -- Complete code examples -- Proper markdown formatting -- Cross-references to related docs -- Updated version numbers - ---- - -## 📅 Version Information - -- **Documentation Version:** 0.99.3 -- **Last Updated:** November 2025 -- **Trilium Version:** 0.99.3+ -- **Next Review:** When major architectural changes occur - ---- - -## 💡 Getting Help - -### For Users -- [User Guide](User%20Guide/User%20Guide/) -- [GitHub Discussions](https://github.com/TriliumNext/Trilium/discussions) -- [Matrix Chat](https://matrix.to/#/#triliumnext:matrix.org) - -### For Developers -- [Developer Guide](Developer%20Guide/Developer%20Guide/) -- [Architecture Docs](ARCHITECTURE.md) -- [GitHub Issues](https://github.com/TriliumNext/Trilium/issues) - -### For Contributors -- [Contributing Guidelines](../README.md#-contribute) -- [Code of Conduct](../CODE_OF_CONDUCT) -- [Developer Setup](Developer%20Guide/Developer%20Guide/Environment%20Setup.md) - ---- - -**Maintained by:** TriliumNext Team -**License:** AGPL-3.0-only -**Repository:** https://github.com/TriliumNext/Trilium From b80cb22985f2090ec060e545df0f810fe3dc5dac Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 4 Nov 2025 18:20:12 +0200 Subject: [PATCH 8/8] chore(deps): clean up package lock --- pnpm-lock.yaml | 184 ++----------------------------------------------- 1 file changed, 6 insertions(+), 178 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1054a98c4..d039fcef4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -838,9 +838,6 @@ importers: vite: specifier: 7.1.12 version: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) - vitest: - specifier: 4.0.6 - version: 4.0.6(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/ui@3.2.4)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@24.10.0)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) packages/ckeditor5: dependencies: @@ -5002,9 +4999,6 @@ packages: '@ssddanbrown/codemirror-lang-twig@1.0.0': resolution: {integrity: sha512-7WIMIh8Ssc54TooGCY57WU2rKEqZZrcV2tZSVRPtd0gKYsrDEKCSLWpQjUWEx7bdgh3NKHUjq1O4ugIzI/+dwQ==} - '@standard-schema/spec@1.0.0': - resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@stylistic/eslint-plugin@4.4.1': resolution: {integrity: sha512-CEigAk7eOLyHvdgmpZsKFwtiqS2wFwI1fn4j09IU9GmD4euFM4jEBAViWeCqaNLlbX2k2+A/Fq9cje4HQBXuJQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5803,9 +5797,6 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/expect@4.0.6': - resolution: {integrity: sha512-5j8UUlBVhOjhj4lR2Nt9sEV8b4WtbcYh8vnfhTNA2Kn5+smtevzjNq+xlBuVhnFGXiyPPNzGrOVvmyHWkS5QGg==} - '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: @@ -5817,41 +5808,18 @@ packages: vite: optional: true - '@vitest/mocker@4.0.6': - resolution: {integrity: sha512-3COEIew5HqdzBFEYN9+u0dT3i/NCwppLnO1HkjGfAP1Vs3vti1Hxm/MvcbC4DAn3Szo1M7M3otiAaT83jvqIjA==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/pretty-format@4.0.6': - resolution: {integrity: sha512-4vptgNkLIA1W1Nn5X4x8rLJBzPiJwnPc+awKtfBE5hNMVsoAl/JCCPPzNrbf+L4NKgklsis5Yp2gYa+XAS442g==} - '@vitest/runner@3.2.4': resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/runner@4.0.6': - resolution: {integrity: sha512-trPk5qpd7Jj+AiLZbV/e+KiiaGXZ8ECsRxtnPnCrJr9OW2mLB72Cb824IXgxVz/mVU3Aj4VebY+tDTPn++j1Og==} - '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/snapshot@4.0.6': - resolution: {integrity: sha512-PaYLt7n2YzuvxhulDDu6c9EosiRuIE+FI2ECKs6yvHyhoga+2TBWI8dwBjs+IeuQaMtZTfioa9tj3uZb7nev1g==} - '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/spy@4.0.6': - resolution: {integrity: sha512-g9jTUYPV1LtRPRCQfhbMintW7BTQz1n6WXYQYRQ25qkyffA4bjVXjkROokZnv7t07OqfaFKw1lPzqKGk1hmNuQ==} - '@vitest/ui@3.2.4': resolution: {integrity: sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==} peerDependencies: @@ -5860,9 +5828,6 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - '@vitest/utils@4.0.6': - resolution: {integrity: sha512-bG43VS3iYKrMIZXBo+y8Pti0O7uNju3KvNn6DrQWhQQKcLavMB+0NZfO1/QBAEbq0MaQ3QjNsnnXlGQvsh0Z6A==} - '@volar/language-core@2.4.13': resolution: {integrity: sha512-MnQJ7eKchJx5Oz+YdbqyFUk8BN6jasdJv31n/7r6/WwlOOv7qzvot6B66887l2ST3bUW4Mewml54euzpJWA6bg==} @@ -6644,10 +6609,6 @@ packages: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} engines: {node: '>=12'} - chai@6.2.0: - resolution: {integrity: sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==} - engines: {node: '>=18'} - chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -8275,10 +8236,6 @@ packages: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} - expect-type@1.2.2: - resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} - engines: {node: '>=12.0.0'} - exponential-backoff@3.1.2: resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} @@ -10187,9 +10144,6 @@ packages: magic-string@0.30.18: resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -13881,10 +13835,6 @@ packages: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} - tinyrainbow@3.0.3: - resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} - engines: {node: '>=14.0.0'} - tinyspy@4.0.3: resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} engines: {node: '>=14.0.0'} @@ -14499,40 +14449,6 @@ packages: jsdom: optional: true - vitest@4.0.6: - resolution: {integrity: sha512-gR7INfiVRwnEOkCk47faros/9McCZMp5LM+OMNWGLaDBSvJxIzwjgNFufkuePBNaesGRnLmNfW+ddbUJRZn0nQ==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.6 - '@vitest/browser-preview': 4.0.6 - '@vitest/browser-webdriverio': 4.0.6 - '@vitest/ui': 4.0.6 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - void-elements@2.0.1: resolution: {integrity: sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==} engines: {node: '>=0.10.0'} @@ -16068,6 +15984,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.1.0 ckeditor5: 47.1.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-classic@47.1.0': dependencies: @@ -16095,6 +16013,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.1.0 ckeditor5: 47.1.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-multi-root@47.1.0': dependencies: @@ -16117,6 +16037,8 @@ snapshots: '@ckeditor/ckeditor5-table': 47.1.0 '@ckeditor/ckeditor5-utils': 47.1.0 ckeditor5: 47.1.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-emoji@47.1.0': dependencies: @@ -20325,8 +20247,6 @@ snapshots: '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 - '@standard-schema/spec@1.0.0': {} - '@stylistic/eslint-plugin@4.4.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/utils': 8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) @@ -21310,15 +21230,6 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/expect@4.0.6': - dependencies: - '@standard-schema/spec': 1.0.0 - '@types/chai': 5.2.2 - '@vitest/spy': 4.0.6 - '@vitest/utils': 4.0.6 - chai: 6.2.0 - tinyrainbow: 3.0.3 - '@vitest/mocker@3.2.4(msw@2.7.5(@types/node@24.10.0)(typescript@5.9.3))(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 @@ -21328,52 +21239,26 @@ snapshots: msw: 2.7.5(@types/node@24.10.0)(typescript@5.9.3) vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) - '@vitest/mocker@4.0.6(msw@2.7.5(@types/node@24.10.0)(typescript@5.9.3))(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': - dependencies: - '@vitest/spy': 4.0.6 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - msw: 2.7.5(@types/node@24.10.0)(typescript@5.9.3) - vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) - '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/pretty-format@4.0.6': - dependencies: - tinyrainbow: 3.0.3 - '@vitest/runner@3.2.4': dependencies: '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.0.0 - '@vitest/runner@4.0.6': - dependencies: - '@vitest/utils': 4.0.6 - pathe: 2.0.3 - '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 magic-string: 0.30.18 pathe: 2.0.3 - '@vitest/snapshot@4.0.6': - dependencies: - '@vitest/pretty-format': 4.0.6 - magic-string: 0.30.21 - pathe: 2.0.3 - '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.3 - '@vitest/spy@4.0.6': {} - '@vitest/ui@3.2.4(vitest@3.2.4)': dependencies: '@vitest/utils': 3.2.4 @@ -21391,11 +21276,6 @@ snapshots: loupe: 3.1.4 tinyrainbow: 2.0.0 - '@vitest/utils@4.0.6': - dependencies: - '@vitest/pretty-format': 4.0.6 - tinyrainbow: 3.0.3 - '@volar/language-core@2.4.13': dependencies: '@volar/source-map': 2.4.13 @@ -22350,8 +22230,6 @@ snapshots: loupe: 3.1.4 pathval: 2.0.1 - chai@6.2.0: {} - chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -24566,8 +24444,6 @@ snapshots: expect-type@1.2.1: {} - expect-type@1.2.2: {} - exponential-backoff@3.1.2: {} exponential-backoff@3.1.3: {} @@ -26796,10 +26672,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.3.5: dependencies: '@babel/parser': 7.28.4 @@ -31330,8 +31202,6 @@ snapshots: tinyrainbow@2.0.0: {} - tinyrainbow@3.0.3: {} - tinyspy@4.0.3: {} tldts-core@6.1.86: @@ -32002,48 +31872,6 @@ snapshots: - tsx - yaml - vitest@4.0.6(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/ui@3.2.4)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@24.10.0)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): - dependencies: - '@vitest/expect': 4.0.6 - '@vitest/mocker': 4.0.6(msw@2.7.5(@types/node@24.10.0)(typescript@5.9.3))(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) - '@vitest/pretty-format': 4.0.6 - '@vitest/runner': 4.0.6 - '@vitest/snapshot': 4.0.6 - '@vitest/spy': 4.0.6 - '@vitest/utils': 4.0.6 - debug: 4.4.3(supports-color@6.0.0) - es-module-lexer: 1.7.0 - expect-type: 1.2.2 - magic-string: 0.30.21 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 24.10.0 - '@vitest/ui': 3.2.4(vitest@3.2.4) - happy-dom: 20.0.10 - jsdom: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - void-elements@2.0.1: {} void-elements@3.1.0: {}