mirror of
https://github.com/zadam/trilium.git
synced 2025-11-08 15:39:02 +01:00
Add comprehensive technical and architectural documentation (#7600)
This commit is contained in:
commit
d521bda6ab
2
apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI.html
generated
vendored
2
apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI.html
generated
vendored
@ -36,7 +36,7 @@ class="image image_resized" style="width:74.04%;">
|
||||
<p>To see what embedding models Ollama has available, you can check out
|
||||
<a
|
||||
href="https://ollama.com/search?c=embedding">this search</a>on their website, and then <code>pull</code> whichever one
|
||||
you want to try out. As of 4/15/25, my personal favorite is <code>mxbai-embed-large</code>.</p>
|
||||
you want to try out. A popular choice is <code>mxbai-embed-large</code>.</p>
|
||||
<p>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 <code>http://localhost:11434</code>.
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
<li>
|
||||
<p><a class="reference-link" href="#root/_help_HI6GBBIduIgv">Labels</a> 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.</p>
|
||||
the behavior of notes. Labels are also searchable, enhancing note retrieval.</p>
|
||||
<p>For more information, including predefined labels, see <a class="reference-link"
|
||||
href="#root/_help_HI6GBBIduIgv">Labels</a>.</p>
|
||||
</li>
|
||||
@ -21,7 +21,7 @@
|
||||
class="reference-link" href="#root/_help_Cq5X6iKQop6R">Relations</a>.</p>
|
||||
</li>
|
||||
</ol>
|
||||
<p>These attributes play a crucial role in organizing, categorising, and
|
||||
<p>These attributes play a crucial role in organizing, categorizing, and
|
||||
enhancing the functionality of notes.</p>
|
||||
<h2>Viewing the list of attributes</h2>
|
||||
<p>Both the labels and relations for the current note are displayed in the <em>Owned Attributes</em> section
|
||||
|
||||
@ -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</li>
|
||||
</ul>
|
||||
<h3>Explanation</h3>
|
||||
<p>Let's test this by using an HTTP client to send a request:</p><pre><code class="language-text-x-trilium-auto">POST http://my.trilium.org/custom/create-note
|
||||
<p>Let's test this by using an HTTP client to send a request:</p><pre><code class="language-text-x-trilium-auto">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 <a href="https://expressjs.com/en/api.html">documentation</a> for
|
||||
details.</p>
|
||||
<h3>Parameters</h3>
|
||||
<p>REST request paths often contain parameters in the URL, e.g.:</p><pre><code class="language-text-x-trilium-auto">http://my.trilium.org/custom/notes/123</code></pre>
|
||||
<p>REST request paths often contain parameters in the URL, e.g.:</p><pre><code class="language-text-x-trilium-auto">http://your-trilium-server/custom/notes/123</code></pre>
|
||||
<p>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 <code>customRequestHandler</code> value
|
||||
would match it:</p><pre><code class="language-text-x-trilium-auto">notes/([0-9]+)</code></pre>
|
||||
<p>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 <code>api.pathParams</code>:</p><pre><code class="language-text-x-trilium-auto">const noteId = api.pathParams[0];</code></pre>
|
||||
<p>Often you also need query params (as in e.g. <code>http://my.trilium.org/custom/notes?noteId=123</code>),
|
||||
<p>Often you also need query params (as in e.g. <code>http://your-trilium-server/custom/notes?noteId=123</code>),
|
||||
you can get those with standard express <code>req.query.noteId</code>.</p>
|
||||
@ -21,7 +21,7 @@
|
||||
<ol>
|
||||
<li>Set the text to search for in the <em>Search string</em> field.
|
||||
<ol>
|
||||
<li>Apart from searching for words ad-literam, there is also the possibility
|
||||
<li>Apart from searching for words literally, there is also the possibility
|
||||
to search for attributes or properties of notes.</li>
|
||||
<li>See the examples below for more information.</li>
|
||||
</ol>
|
||||
|
||||
@ -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.</p>
|
||||
<p>Clicking an undelete will recover the note, it's content and attributes
|
||||
<p>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.</p>
|
||||
<p>To be able to undelete a note, it is necessary that deleted note's parent
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
<li><em><strong>Editable</strong></em> changes whether the current note:
|
||||
<ul>
|
||||
<li>Enters <a href="#root/_help_CoFPLs3dRlXc">read-only mode</a> automatically if
|
||||
the note is too big (default behaviour).</li>
|
||||
the note is too big (default behavior).</li>
|
||||
<li>Is always in read-only mode (however it can still be edited temporarily).</li>
|
||||
<li>Is always editable, regardless of its size.</li>
|
||||
</ul>
|
||||
|
||||
6
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections.html
generated
vendored
6
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections.html
generated
vendored
@ -1,5 +1,5 @@
|
||||
<p>Collections are a unique type of notes that don't have a content, but
|
||||
instead display its child notes in various presentation methods.</p>
|
||||
<p>Collections are a unique type of note that don't have content, but instead
|
||||
display their child notes in various presentation methods.</p>
|
||||
<h2>Main collections</h2>
|
||||
<table>
|
||||
<thead>
|
||||
@ -94,7 +94,7 @@
|
||||
in the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>.</p>
|
||||
<h2>Archived notes</h2>
|
||||
<p>By default, <a href="#root/_help_MKmLg5x6xkor">archived notes</a> will not be
|
||||
shown in collections. This behaviour can be changed by going to <em>Collection Properties</em> in
|
||||
shown in collections. This behavior can be changed by going to <em>Collection Properties</em> in
|
||||
the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a> and
|
||||
checking <em>Show archived notes</em>.</p>
|
||||
<p>Archived notes will be generally indicated by being greyed out as opposed
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
any startup scripts that might cause the application to crash.</li>
|
||||
</ul>
|
||||
<h2>Synchronization</h2>
|
||||
<p>For Trilium desktp users who wish to synchronize their data with a server
|
||||
<p>For Trilium desktop users who wish to synchronize their data with a server
|
||||
instance, refer to the <a class="reference-link" href="#root/_help_cbkrhQjrkKrh">Synchronization</a> guide
|
||||
for detailed instructions.</p>
|
||||
@ -40,7 +40,7 @@
|
||||
<h3>Disabling / Modifying the Upload Limit</h3>
|
||||
<p>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 <code>TRILIUM_NO_UPLOAD_LIMIT</code> environment
|
||||
variable to <code>true</code> disable it completely:</p><pre><code class="language-text-x-trilium-auto">export TRILIUM_NO_UPLOAD_LIMIT=true </code></pre>
|
||||
variable to <code>true</code> to disable it completely:</p><pre><code class="language-text-x-trilium-auto">export TRILIUM_NO_UPLOAD_LIMIT=true </code></pre>
|
||||
<p>Or, if you'd simply like to <em>increase</em> the upload limit size to something
|
||||
beyond 250MB, you can set the <code>MAX_ALLOWED_FILE_SIZE_MB</code> environment
|
||||
variable to something larger than the integer <code>250</code> (e.g. <code>450</code> in
|
||||
|
||||
4
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types.html
generated
vendored
4
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types.html
generated
vendored
@ -1,5 +1,5 @@
|
||||
<p>One core features of Trilium is that it supports multiple types of notes,
|
||||
depending on the need.</p>
|
||||
<p>One of the core features of Trilium is that it supports multiple types
|
||||
of notes, depending on the need.</p>
|
||||
<h2>Creating a new note with a different type via the note tree</h2>
|
||||
<p>The default note type in Trilium (e.g. when creating a new note) is
|
||||
<a
|
||||
|
||||
6
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Scripting.html
generated
vendored
6
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Scripting.html
generated
vendored
@ -3,7 +3,7 @@
|
||||
it. Special case is JavaScript code notes which can also be executed inside
|
||||
Trilium which can in conjunction with <a class="reference-link" href="#root/_help_GLks18SNjxmC">Script API</a> provide
|
||||
extra functionality.</p>
|
||||
<h2>Scripting</h2>
|
||||
<h2>Architecture Overview</h2>
|
||||
<p>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:</p>
|
||||
<ul>
|
||||
@ -14,8 +14,8 @@
|
||||
</ul>
|
||||
<p>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.</p>
|
||||
this the fact, that we're able to create JavaScript <a class="reference-link"
|
||||
href="#root/_help_6f9hih2hXXZk">code notes</a> and we're onto something.</p>
|
||||
<h2>Use cases</h2>
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/_help_TjLYAo3JMO8X">"New Task" launcher button</a>
|
||||
|
||||
@ -16,11 +16,11 @@
|
||||
module.exports = new MyWidget();</code></pre>
|
||||
<p>To implement this widget:</p>
|
||||
<ol>
|
||||
<li data-list-item-id="ee416db068caeb5aebc3edf8d313cdbbf">Create a new <code>JS Frontend</code> note in Trilium and paste in the code
|
||||
<li>Create a new <code>JS Frontend</code> note in Trilium and paste in the code
|
||||
above.</li>
|
||||
<li data-list-item-id="ec82d101a82bd1bdbbc180294534aecaa">Assign the <code>#widget</code> <a href="#root/_help_zEY4DaJG4YT5">attribute</a> to
|
||||
<li>Assign the <code>#widget</code> <a href="#root/_help_zEY4DaJG4YT5">attribute</a> to
|
||||
the <a href="#root/_help_BFs8mudNFgCS">note</a>.</li>
|
||||
<li data-list-item-id="ee1c3b9d316a04a986cc0be3b57d4c569">Restart Trilium or reload the window.</li>
|
||||
<li>Restart Trilium or reload the window.</li>
|
||||
</ol>
|
||||
<p>To verify that the widget is working, open the developer tools (<code>Cmd</code> + <code>Shift</code> + <code>I</code>)
|
||||
and run <code>document.querySelector("#my-widget")</code>. If the element
|
||||
@ -89,16 +89,15 @@ module.exports = new MyWidget();</code></pre>
|
||||
module.exports = new MyWidget();</code></pre>
|
||||
<p><code>parentWidget()</code> can be given the following values:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="ebf9bc7b43e420c012d4c72b3b95a8cf0"><code>left-pane</code> - This renders the widget on the left side of the
|
||||
<li><code>left-pane</code> - This renders the widget on the left side of the
|
||||
screen where the note tree lives.</li>
|
||||
<li data-list-item-id="ec6ca6cd1ed1b9157edc99a61e4b9f336"><code>center-pane</code> - This renders the widget in the center of the
|
||||
<li><code>center-pane</code> - This renders the widget in the center of the
|
||||
layout in the same location that notes and splits appear.</li>
|
||||
<li data-list-item-id="e8575696a825b1dce0a62a3ffb6b59ae8"><code>note-detail-pane</code> - This renders the widget <em>with</em> the
|
||||
<li><code>note-detail-pane</code> - This renders the widget <em>with</em> the
|
||||
note in the center pane. This means it can appear multiple times with splits.</li>
|
||||
<li
|
||||
data-list-item-id="e064dfaa93f31a16d42c10c4c45d903be"><code>right-pane</code> - This renders the widget to the right of any opened
|
||||
<li><code>right-pane</code> - This renders the widget to the right of any opened
|
||||
notes.</li>
|
||||
</ul>
|
||||
<p><a href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/_help_s8alTXmpFR61">Reload</a> the application
|
||||
one last time. When you click the button, a "Hello World!" message should
|
||||
appear, confirming that your widget is fully functional.</p>
|
||||
<p><a href="#root/_help_s8alTXmpFR61">Reload</a> the application one last time.
|
||||
When you click the button, a "Hello World!" message should appear, confirming
|
||||
that your widget is fully functional.</p>
|
||||
5
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Troubleshooting.html
generated
vendored
5
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Troubleshooting.html
generated
vendored
@ -1,4 +1,5 @@
|
||||
<p>As Trilium is currently in beta, encountering bugs is to be expected.</p>
|
||||
<p>While Trilium is actively maintained and stable, encountering bugs is
|
||||
possible.</p>
|
||||
<h2>General Quick Fix</h2>
|
||||
<p>The first step in troubleshooting is often a restart.</p>
|
||||
<p>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., <code>root</code>).
|
||||
In Linux, you can set it as follows:</p><pre><code class="language-text-x-trilium-auto">TRILIUM_START_NOTE_ID=root ./trilium</code></pre>
|
||||
<h2>Broken Script Prevents Application Startup</h2>
|
||||
<p>If a custom script causes Triliumto crash, and it is set as a startup
|
||||
<p>If a custom script causes Trilium to crash, and it is set as a startup
|
||||
script or in an active <a href="#root/_help_MgibgPcfeuGz">custom widget</a>, start
|
||||
Triliumin "safe mode" to prevent any custom scripts from executing:</p><pre><code class="language-text-x-trilium-auto">TRILIUM_SAFE_MODE=true ./trilium</code></pre>
|
||||
<p>Depending on your Trilium distribution, you may have pre-made scripts
|
||||
|
||||
1429
docs/Developer Guide/!!!meta.json
vendored
1429
docs/Developer Guide/!!!meta.json
vendored
File diff suppressed because it is too large
Load Diff
13
docs/Developer Guide/Developer Guide.md
vendored
13
docs/Developer Guide/Developer Guide.md
vendored
@ -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.
|
||||
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
|
||||
|
||||
* <a class="reference-link" href="Developer%20Guide/Environment%20Setup.md">Environment Setup</a>
|
||||
* <a class="reference-link" href="Developer%20Guide/Project%20Structure.md">Project Structure</a>
|
||||
|
||||
### 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/)
|
||||
202
docs/Developer Guide/Developer Guide/Architecture.md
vendored
Normal file
202
docs/Developer Guide/Developer Guide/Architecture.md
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
# 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**:
|
||||
* 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
|
||||
* **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.
|
||||
|
||||
```
|
||||
graph TB
|
||||
subgraph Frontend
|
||||
Widgets[Widgets<br/>System]
|
||||
Froca[Froca<br/>Cache]
|
||||
UIServices[UI<br/>Services]
|
||||
end
|
||||
|
||||
subgraph Backend["Backend Server"]
|
||||
Express[Express<br/>Routes]
|
||||
Becca[Becca<br/>Cache]
|
||||
ScriptEngine[Script<br/>Engine]
|
||||
Database[(SQLite<br/>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
|
||||
```
|
||||
|
||||
## 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
|
||||
72
docs/Developer Guide/Developer Guide/Architecture/APIs.md
vendored
Normal file
72
docs/Developer Guide/Developer Guide/Architecture/APIs.md
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
# APIs
|
||||
### 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:**
|
||||
|
||||
```
|
||||
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)
|
||||
})
|
||||
```
|
||||
62
docs/Developer Guide/Developer Guide/Architecture/Arhitecture Decision Records.md
vendored
Normal file
62
docs/Developer Guide/Developer Guide/Architecture/Arhitecture Decision Records.md
vendored
Normal file
@ -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 <a class="reference-link" href="../Dependencies/CKEditor.md">CKEditor</a> 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)
|
||||
88
docs/Developer Guide/Developer Guide/Architecture/Backend.md
vendored
Normal file
88
docs/Developer Guide/Developer Guide/Architecture/Backend.md
vendored
Normal file
@ -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
|
||||
40
docs/Developer Guide/Developer Guide/Architecture/Database.md
vendored
Normal file
40
docs/Developer Guide/Developer Guide/Architecture/Database.md
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
# Database
|
||||
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`
|
||||
|
||||
### 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.
|
||||
@ -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 <a class="reference-link" href="blobs.md">blobs</a> 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` | |
|
||||
@ -1,2 +1,2 @@
|
||||
# attributes
|
||||
<table><thead><tr><th>Column Name</th><th>Data Type</th><th>Nullity</th><th>Default value</th><th>Description</th></tr></thead><tbody><tr><th><code>attributeId</code></th><td>Text</td><td>Non-null</td><td> </td><td>Unique Id of the attribute (e.g. <code>qhC1vzU4nwSE</code>), can also have a special unique ID for <a class="reference-link" href="#root/r11Bh3uxFGRj">Special notes</a> (e.g. <code>_lbToday_liconClass</code>).</td></tr><tr><th><code>noteId</code></th><td>Text</td><td>Non-null</td><td> </td><td>The ID of the <a href="notes.md">note</a> this atttribute belongs to</td></tr><tr><th><code>type</code></th><td>Text</td><td>Non-null</td><td> </td><td>The type of attribute (<code>label</code> or <code>relation</code>).</td></tr><tr><th><code>name</code></th><td>Text</td><td>Non-null</td><td> </td><td>The name/key of the attribute.</td></tr><tr><th><code>value</code></th><td>Text</td><td>Non-null</td><td><code>""</code></td><td><ul><li>For <code>label</code> attributes, a free-form value of the attribute.</li><li>For <code>relation</code> attributes, the ID of the <a href="notes.md">note</a> the relation is pointing to.</li></ul></td></tr><tr><th><code>position</code></th><td>Integer</td><td>Non-null</td><td>0</td><td>The position of the attribute compared to the other attributes. Some predefined attributes such as <code>originalFileName</code> have a value of 1000.</td></tr><tr><th><code>utcDateModified</code></th><td>Text</td><td>Non-null</td><td> </td><td>Modification date in UTC format (e.g. <code>2023-11-08 16:43:44.204Z</code>)</td></tr><tr><th><code>isDeleted</code></th><td>Integer</td><td>Non-null</td><td> </td><td><code>1</code> if the entity is <a href="../Deleted%20notes.md">deleted</a>, <code>0</code> otherwise.</td></tr><tr><th><code>deleteId</code></th><td>Text</td><td>Nullable</td><td><code>null</code></td><td> </td></tr><tr><th><code>isInheritable</code></th><td>Integer</td><td>Nullable</td><td>0</td><td> </td></tr></tbody></table>
|
||||
<table><thead><tr><th>Column Name</th><th>Data Type</th><th>Nullity</th><th>Default value</th><th>Description</th></tr></thead><tbody><tr><th><code>attributeId</code></th><td>Text</td><td>Non-null</td><td> </td><td>Unique Id of the attribute (e.g. <code>qhC1vzU4nwSE</code>), can also have a special unique ID for <a class="reference-link" href="#root/r11Bh3uxFGRj">Special notes</a> (e.g. <code>_lbToday_liconClass</code>).</td></tr><tr><th><code>noteId</code></th><td>Text</td><td>Non-null</td><td> </td><td>The ID of the <a href="notes.md">note</a> this atttribute belongs to</td></tr><tr><th><code>type</code></th><td>Text</td><td>Non-null</td><td> </td><td>The type of attribute (<code>label</code> or <code>relation</code>).</td></tr><tr><th><code>name</code></th><td>Text</td><td>Non-null</td><td> </td><td>The name/key of the attribute.</td></tr><tr><th><code>value</code></th><td>Text</td><td>Non-null</td><td><code>""</code></td><td><ul><li>For <code>label</code> attributes, a free-form value of the attribute.</li><li>For <code>relation</code> attributes, the ID of the <a href="notes.md">note</a> the relation is pointing to.</li></ul></td></tr><tr><th><code>position</code></th><td>Integer</td><td>Non-null</td><td>0</td><td>The position of the attribute compared to the other attributes. Some predefined attributes such as <code>originalFileName</code> have a value of 1000.</td></tr><tr><th><code>utcDateModified</code></th><td>Text</td><td>Non-null</td><td> </td><td>Modification date in UTC format (e.g. <code>2023-11-08 16:43:44.204Z</code>)</td></tr><tr><th><code>isDeleted</code></th><td>Integer</td><td>Non-null</td><td> </td><td><code>1</code> if the entity is <a href="../../../Concepts/Deleted%20notes.md">deleted</a>, <code>0</code> otherwise.</td></tr><tr><th><code>deleteId</code></th><td>Text</td><td>Nullable</td><td><code>null</code></td><td> </td></tr><tr><th><code>isInheritable</code></th><td>Integer</td><td>Nullable</td><td>0</td><td> </td></tr></tbody></table>
|
||||
@ -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`) |
|
||||
@ -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. |
|
||||
| `isDeleted` | Integer | Non-null | 0 | `1` if the entity is [deleted](../../../Concepts/Deleted%20notes.md), `0` otherwise. |
|
||||
@ -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`) |
|
||||
@ -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 <a class="reference-link" href="blobs.md">blobs</a>. 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`) |
|
||||
61
docs/Developer Guide/Developer Guide/Architecture/Frontend.md
vendored
Normal file
61
docs/Developer Guide/Developer Guide/Architecture/Frontend.md
vendored
Normal file
@ -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
|
||||
@ -1,6 +0,0 @@
|
||||
# Protected entities
|
||||
The following entities can be made protected, via their `isProtected` flag:
|
||||
|
||||
* <a class="reference-link" href="Database%20structure/attachments.md">attachments</a>
|
||||
* <a class="reference-link" href="Database%20structure/notes.md">notes</a>
|
||||
* <a class="reference-link" href="Database%20structure/revisions.md">revisions</a>
|
||||
464
docs/Developer Guide/Developer Guide/Architecture/Security.md
vendored
Normal file
464
docs/Developer Guide/Developer Guide/Architecture/Security.md
vendored
Normal file
@ -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
|
||||
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
111
docs/Developer Guide/Developer Guide/Concepts/Cache.md
vendored
Normal file
111
docs/Developer Guide/Developer Guide/Concepts/Cache.md
vendored
Normal file
@ -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<string, BNote>
|
||||
branches: Record<string, BBranch>
|
||||
attributes: Record<string, BAttribute>
|
||||
attachments: Record<string, BAttachment>
|
||||
// ... 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<string, FNote>
|
||||
branches: Record<string, FBranch>
|
||||
attributes: Record<string, FAttribute>
|
||||
// ... 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
|
||||
109
docs/Developer Guide/Developer Guide/Concepts/Entities.md
vendored
Normal file
109
docs/Developer Guide/Developer Guide/Concepts/Entities.md
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
# Entities
|
||||
### Entity System
|
||||
|
||||
Trilium's data model is based on five core entities:
|
||||
|
||||
```
|
||||
graph TD
|
||||
Note[Note<br/>BNote]
|
||||
Branch[Branch<br/>BBranch]
|
||||
Attribute[Attribute<br/>BAttribute]
|
||||
Revision[Revision<br/>BRevision]
|
||||
Attachment[Attachment<br/>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`.
|
||||
6
docs/Developer Guide/Developer Guide/Concepts/Protected entities.md
vendored
Normal file
6
docs/Developer Guide/Developer Guide/Concepts/Protected entities.md
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Protected entities
|
||||
The following entities can be made protected, via their `isProtected` flag:
|
||||
|
||||
* <a class="reference-link" href="../Architecture/Database/Database%20structure/attachments.md">attachments</a>
|
||||
* <a class="reference-link" href="../Architecture/Database/Database%20structure/notes.md">notes</a>
|
||||
* <a class="reference-link" href="../Architecture/Database/Database%20structure/revisions.md">revisions</a>
|
||||
484
docs/Developer Guide/Developer Guide/Concepts/Synchronisation.md
vendored
Normal file
484
docs/Developer Guide/Developer Guide/Concepts/Synchronisation.md
vendored
Normal file
@ -0,0 +1,484 @@
|
||||
# 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:
|
||||
|
||||
* Concurrent modifications across devices
|
||||
* Simple conflict resolution (without “merge conflict” indication).
|
||||
* Partial sync (only changed entities)
|
||||
* Protected note synchronization
|
||||
* Efficient bandwidth usage
|
||||
|
||||
## Sync Architecture
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Desktop1[Desktop 1<br/>Client]
|
||||
Desktop2[Desktop 2<br/>Client]
|
||||
|
||||
subgraph SyncServer["Sync Server"]
|
||||
SyncService[Sync Service<br/>- Entity Change Management<br/>- Conflict Resolution<br/>- Version Tracking]
|
||||
SyncDB[(Database<br/>entity_changes)]
|
||||
end
|
||||
|
||||
Desktop1 <-->|WebSocket/HTTP| SyncService
|
||||
Desktop2 <-->|WebSocket/HTTP| SyncService
|
||||
SyncService --> SyncDB
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Entity Changes
|
||||
|
||||
Every modification to any entity (note, branch, attribute, etc.) creates an **entity change** record:
|
||||
|
||||
```
|
||||
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, -- Unique component/widget 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 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
|
||||
|
||||
### 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. **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
|
||||
|
||||
### 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 {
|
||||
showIcon('not-synced')
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
### Incremental Sync
|
||||
|
||||
Only entities changed since last sync are transferred:
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
## Sync API Endpoints
|
||||
|
||||
Located at: `apps/server/src/routes/api/sync.ts`
|
||||
|
||||
## WebSocket Sync Updates
|
||||
|
||||
Real-time sync via WebSocket:
|
||||
|
||||
```typescript
|
||||
// Server broadcasts change to all connected clients
|
||||
ws.broadcast('frontend-update', {
|
||||
lastSyncedPush,
|
||||
entityChanges
|
||||
})
|
||||
|
||||
// Client receives and processed the information.
|
||||
```
|
||||
|
||||
## Sync Scheduling
|
||||
|
||||
### Automatic Sync
|
||||
|
||||
**Desktop:**
|
||||
|
||||
* Sync on startup
|
||||
* Periodic sync (configurable interval, default: 60s)
|
||||
|
||||
**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:**
|
||||
|
||||
```
|
||||
-- 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
|
||||
|
||||
## 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
|
||||
@ -1,5 +1,5 @@
|
||||
# Documentation
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/attachments/2bUrJyt2yfsd/image/Documentation_image.png" width="205" height="162">
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/45okJ9xpVO32/Documentation_image.png" width="205" height="162">
|
||||
|
||||
* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing <kbd>F1</kbd>.
|
||||
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.
|
||||
|
||||
@ -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 <a class="reference-link" href="Architecture/Internationalisation%20%20Translat.md">Internationalisation / Translations</a>.
|
||||
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 <a class="reference-link" href="Concepts/Internationalisation%20%20Translat.md">Internationalisation / Translations</a>.
|
||||
46
docs/Developer Guide/Developer Guide/Testing.md
vendored
46
docs/Developer Guide/Developer Guide/Testing.md
vendored
@ -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 <a class="reference-link" href="Testing/End-to-end%20tests.md">e2e tests</a>.
|
||||
See <a class="reference-link" href="Testing/End-to-end%20tests.md">End-to-end tests</a>.
|
||||
@ -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
|
||||
|
||||
|
||||
7
docs/Developer Guide/Developer Guide/Testing/Unit tests.md
vendored
Normal file
7
docs/Developer Guide/Developer Guide/Testing/Unit tests.md
vendored
Normal file
@ -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.
|
||||
22
docs/README.md
vendored
22
docs/README.md
vendored
@ -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).
|
||||
68
docs/User Guide/!!!meta.json
vendored
68
docs/User Guide/!!!meta.json
vendored
@ -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",
|
||||
@ -14616,21 +14658,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 +14829,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 +14991,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
|
||||
},
|
||||
|
||||
20
docs/User Guide/User Guide.md
vendored
20
docs/User Guide/User Guide.md
vendored
@ -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 <kbd>F1</kbd>.
|
||||
|
||||
## Getting started
|
||||
|
||||
1. See <a class="reference-link" href="User%20Guide/Quick%20Start.md">Quick Start</a>.
|
||||
2. Understand <a class="reference-link" href="User%20Guide/Basic%20Concepts%20and%20Features/Notes.md">Notes</a>.
|
||||
3. Browse through <a class="reference-link" href="User%20Guide/Collections.md">Collections</a>.
|
||||
2. Go through <a class="reference-link" href="User%20Guide/Basic%20Concepts%20and%20Features">Basic Concepts and Features</a>.
|
||||
3. Understand <a class="reference-link" href="User%20Guide/Basic%20Concepts%20and%20Features/Notes.md">Notes</a>.
|
||||
4. Browse through <a class="reference-link" href="User%20Guide/Collections.md">Collections</a>.
|
||||
|
||||
## Quick links
|
||||
|
||||
* <a class="reference-link" href="User%20Guide/Installation%20%26%20Setup/Desktop%20Installation.md">Desktop Installation</a>
|
||||
* <a class="reference-link" href="User%20Guide/Installation%20%26%20Setup/Server%20Installation.md">Server Installation</a>
|
||||
* <a class="reference-link" href="User%20Guide/Scripting/Script%20API/Frontend%20API">Frontend API</a> or <a class="reference-link" href="User%20Guide/Scripting/Script%20API/Backend%20API.dat">Backend API</a>
|
||||
* [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/)
|
||||
184
pnpm-lock.yaml
generated
184
pnpm-lock.yaml
generated
@ -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: {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user