title: diaryx_core description: Core library shared by Diaryx clients author: adammharris audience:
- public part_of: 'README' contents:
- 'README' attachments:
- 'Cargo.toml'
- 'build.rs' exclude:
- '*.lock'
Diaryx Core Library
This is the diaryx_core library! It contains shared code for the Diaryx clients.
Async-first Architecture
This library uses an async-first design. All core modules (Workspace, Validator, Exporter, Searcher, Publisher) use the AsyncFileSystem trait for filesystem operations.
For CLI/native code: Wrap a sync filesystem with SyncToAsyncFs and use futures_lite::future::block_on():
use ;
use Workspace;
let fs = new;
let workspace = new;
// Use block_on for sync contexts
let tree = block_on;
For WASM: Implement AsyncFileSystem directly using JS promises/IndexedDB.
Quick overview
diaryx_core
└── src
Module Documentation
| Module | README | Description |
|---|---|---|
crdt |
src/crdt/README.md | Real-time collaboration via Y.js CRDTs |
cloud |
src/cloud/README.md | Bidirectional file sync with cloud storage |
Provided functionality
Managing frontmatter
Full key-value operations for managing frontmatter properties:
set_frontmatter_propertyget_frontmatter_propertyrename_frontmatter_propertyremove_frontmatter_propertyget_all_frontmatter
Also, sorting frontmatter properties:
sort_frontmattersort_alphabeticallysort_by_pattern
Managing file content
Operations for managing content of markdown files separate from frontmatter:
set_contentget_contentappend_contentclear_content
Search
Search frontmatter or content separately:
SearchQuery::contentSearchQuery::frontmatter
Export
use ;
use ;
use Path;
let workspace_root = new;
let audience = "public";
let destination = new;
let fs = new;
let exporter = new;
// Use futures_lite::future::block_on for sync contexts
let plan = block_on.unwrap;
let force = false;
let keep_audience = false;
let options = ExportOptions ;
let result = block_on;
match result
Validation
The validate module provides functionality to check workspace link integrity and automatically fix issues.
Validator
The Validator struct checks part_of and contents references within a workspace:
use Validator;
use ;
use Path;
let fs = new;
let validator = new;
// Validate entire workspace starting from root index
// The second parameter controls orphan detection depth:
// - Some(2) matches tree view depth (recommended for UI)
// - None for unlimited depth (full workspace scan)
let root_path = new;
let result = block_on.unwrap;
// Or validate a single file
let file_path = new;
let result = block_on.unwrap;
if result.is_ok else
Validation Errors
BrokenPartOf- A file'spart_ofpoints to a non-existent fileBrokenContentsRef- An index'scontentsreferences a non-existent fileBrokenAttachment- A file'sattachmentsreferences a non-existent file
Validation Warnings
OrphanFile- A markdown file not referenced by any index'scontentsUnlinkedEntry- A file/directory not in the contents hierarchyCircularReference- Circular reference detected in workspace hierarchyNonPortablePath- A path contains absolute paths or./..componentsMultipleIndexes- Multiple index files in the same directoryOrphanBinaryFile- A binary file not referenced by any index'sattachmentsMissingPartOf- A non-index file has nopart_ofproperty
Exclude Patterns
Index files can define exclude patterns to suppress OrphanFile and OrphanBinaryFile warnings for specific files:
---
title: Docs
contents:
- guide.md
exclude:
- "LICENSE.md" # Exact filename
- "*.lock" # Glob pattern
- "build/**" # Recursive glob
---
Exclude patterns are inherited up the part_of hierarchy. If a parent index excludes *.lock files, that pattern also applies to all child directories.
ValidationFixer
The ValidationFixer struct provides methods to automatically fix validation issues:
use ;
use ;
use Path;
let fs = new;
let validator = new;
let fixer = new;
// Validate workspace (use None for full depth when fixing)
let root_path = new;
let result = block_on.unwrap;
// Fix all issues at once
let = block_on;
for fix in error_fixes.iter.chain
// Or fix individual issues (all methods are async)
block_on;
CRDT (Real-time Collaboration)
The crdt module provides conflict-free replicated data types for real-time collaboration, built on yrs (Rust port of Yjs). This module is feature-gated and must be enabled explicitly.
Feature Flags
[]
= { = "0.1", = ["crdt"] }
# For SQLite-based persistent storage
= { = "0.1", = ["crdt", "crdt-sqlite"] }
Architecture
The CRDT system uses two document types:
- WorkspaceCrdt - A single Y.Doc that stores file hierarchy metadata (file paths, titles, audiences, etc.)
- BodyDoc - Per-file Y.Docs that store document content (body text and frontmatter)
Both document types support:
- Real-time synchronization via Y-sync protocol (compatible with Hocuspocus server)
- Version history with time travel capabilities
- Pluggable storage backends (in-memory or SQLite)
WorkspaceCrdt
Manages the workspace file hierarchy as a CRDT.
Doc-ID Based Architecture
Files are keyed by stable document IDs (UUIDs) rather than file paths. This makes renames and moves trivial property updates rather than delete+create operations:
use ;
use Arc;
let storage = new;
let workspace = new;
// Create a file with auto-generated UUID
let metadata = with_filename;
let doc_id = workspace.create_file.unwrap;
// Derive filesystem path from doc_id (walks parent chain)
let path = workspace.get_path; // Some("my-note.md")
// Find doc_id by path
let found_id = workspace.find_by_path;
// Renames are trivial - just update filename (doc_id is stable!)
workspace.rename_file.unwrap;
// Moves are trivial - just update part_of (doc_id is stable!)
workspace.move_file.unwrap;
Legacy Path-Based API
For backward compatibility, the path-based API still works:
use ;
use Arc;
let storage = new;
let workspace = new;
// Set file metadata by path
let metadata = FileMetadata ;
workspace.set_file;
// Get file metadata
if let Some = workspace.get_file
// List all files
let files = workspace.list_files;
// Remove a file
workspace.remove_file;
Migration
Workspaces using the legacy path-based format can be migrated to doc-IDs:
// Check if migration is needed
if workspace.needs_migration
BodyDoc
Manages individual document content:
use ;
use Arc;
let storage = new;
let doc = new;
// Set body content
doc.set_body;
// Get body content
let content = doc.get_body;
// Collaborative editing operations
doc.insert_at;
doc.delete_range;
// Frontmatter operations
doc.set_frontmatter;
doc.set_frontmatter;
let title = doc.get_frontmatter;
doc.remove_frontmatter;
BodyDocManager
Manages multiple BodyDocs with lazy loading:
use ;
use Arc;
let storage = new;
let manager = new;
// Get or create a BodyDoc for a file
let doc = manager.get_or_create;
doc.set_body;
// Check if a doc exists
if manager.has_doc
// Remove a doc from the manager
manager.remove_doc;
Sync Protocol
The sync module implements Y-sync protocol for real-time collaboration with Hocuspocus or other Y.js-compatible servers:
use ;
use Arc;
let storage = new;
let workspace = new;
// Get sync state for initial handshake
let state_vector = workspace.get_sync_state;
// Apply remote update from server
let remote_update: = /* from WebSocket */;
workspace.apply_update;
// Encode state for sending to server
let full_state = workspace.encode_state;
// Encode incremental update since a state vector
let diff = workspace.encode_state_as_update;
Version History
All local changes are automatically recorded in the storage backend, enabling version history and time travel:
use ;
use Arc;
let storage = new;
let workspace = new;
// Make some changes
workspace.set_file;
workspace.set_file;
// Get version history
let history: = storage.get_all_updates.unwrap;
for entry in &history
// Time travel to a specific version
workspace.restore_to_version;
Storage Backends
MemoryStorage
In-memory storage for WASM/web and testing:
use MemoryStorage;
use Arc;
let storage = new;
SqliteStorage (requires crdt-sqlite feature)
Persistent storage using SQLite:
use SqliteStorage;
use Arc;
let storage = new;
Command API
CRDT operations are also available through the unified command API (used by WASM and Tauri):
use ;
let diaryx = with_crdt;
// Execute CRDT commands
let result = diaryx.execute;
let result = diaryx.execute;
let result = diaryx.execute;
Publish
The publish module converts markdown files to HTML using comrak:
use ;
use ;
use Path;
let fs = new;
let publisher = new;
let options = PublishOptions ;
// Publish a single file
let html = block_on?;
// Publish to a specific path
block_on?;
Templates
Templates provide reusable content patterns for new entries.
Template Syntax
Templates support variable substitution:
{{title}}- Entry title{{filename}}- Filename without extension{{date}}- Current date (ISO format){{part_of}}- Parent index reference
Built-in Templates
note- General note with title placeholder
Using Templates
use ;
use InMemoryFileSystem;
let fs = new;
let manager = new
.with_workspace_dir;
// Get a template
let template = manager.get.unwrap;
// Render with context
let context = new
.with_title
.with_date
.with_part_of;
let content = template.render;
Workspace Config Templates
Templates can be regular workspace entries referenced by link in workspace config:
default_template: "[Default](/templates/default.md)"
The resolution order is: workspace config link -> _templates/ directory -> built-in templates.
Custom Templates (Legacy)
Create custom templates in _templates/ within your workspace:
title: {{title}}
part_of: {{part_of}}
tags: []
Created: {{date}}
Workspaces
Workspaces organize entries into a tree structure using part_of and contents relationships.
Tree Structure
use Workspace;
use ;
use Path;
let fs = new;
let workspace = new;
// Build tree from root index
let tree = block_on?;
// Traverse the tree
for child in &tree.children
Link Formats
Configure how part_of, contents, and attachments links are formatted:
LinkFormat::MarkdownRoot(default) -[../parent.md](../parent.md)(clickable in editors)LinkFormat::Relative-../parent.md(simple relative paths)LinkFormat::Absolute-/workspace/parent.md(absolute from workspace root)
Date parsing
The date module provides natural language date parsing:
use parse_date;
// Natural language parsing
let today = parse_date?;
let yesterday = parse_date?;
let last_friday = parse_date?;
let three_days_ago = parse_date?;
// ISO format
let specific = parse_date?;
Shared errors
The error module provides [DiaryxError] for all fallible operations:
use ;
// Error handling
match result
For IPC (Tauri), convert to SerializableError:
let serializable = error.to_serializable;
// { kind: "FileRead", message: "...", path: Some(...) }
Configuration
Diaryx uses a two-layer configuration model plus a separate native auth store:
- User config (
~/.config/diaryx/config.toml) - Device/user-level settings such as default workspace and editor. Managed by theconfigmodule. - Native auth store (
~/.config/diaryx/auth.toml) - Device/user-level Diaryx account session and remembered sync server/workspace metadata. Managed by theauthmodule on native hosts, with legacy fallback fromconfig.toml. - Workspace config (root index frontmatter) - Workspace-level settings (link format, filename style, templates, audience). Managed by
WorkspaceConfigin theworkspacemodule.
User Config
use Config;
use PathBuf;
let config = load?;
let workspace = &config.default_workspace; // Main workspace path
let editor = &config.editor; // Preferred editor
= "/home/user/diary"
= "nvim"
# Sync settings (optional)
= "https://sync.example.com"
= "user@example.com"
Workspace Config
Workspace-level settings live in the root index file's YAML frontmatter. See workspace/README.md for the full field reference.
---
title: My Workspace
link_format: markdown_root
filename_style: kebab_case
auto_update_timestamp: true
auto_rename_to_title: true
sync_title_to_heading: false
default_template: "[Default](/templates/default.md)"
public_audience: "public"
---
Filesystem abstraction
The fs module provides filesystem abstraction through two traits: FileSystem (synchronous) and AsyncFileSystem (asynchronous).
Note: As of the async-first refactor, all core modules (Workspace, Validator, Exporter, Searcher, Publisher) use AsyncFileSystem. For synchronous contexts (CLI, tests), wrap a sync filesystem with SyncToAsyncFs and use futures_lite::future::block_on().
FileSystem trait
The synchronous FileSystem trait provides basic implementations:
RealFileSystem- Native filesystem usingstd::fs(not available on WASM)InMemoryFileSystem- In-memory implementation, useful for WASM and testing
use ;
use Path;
// Create an in-memory filesystem
let fs = new;
// Write a file (sync)
fs.write_file.unwrap;
// Read it back
let content = fs.read_to_string.unwrap;
assert_eq!;
AsyncFileSystem trait (Primary API)
The AsyncFileSystem trait is the primary API for all core modules:
- WASM environments where JavaScript APIs (like IndexedDB) are async
- Native code using async runtimes like tokio
- All workspace operations (Workspace, Validator, Exporter, etc.)
use ;
use Workspace;
use Path;
// Wrap a sync filesystem for use with async APIs
let sync_fs = new;
let async_fs = new;
// Use with Workspace (async)
let workspace = new;
// For sync contexts, use block_on
let tree = block_on;
SyncToAsyncFs adapter
The SyncToAsyncFs struct wraps any synchronous FileSystem implementation to provide an AsyncFileSystem interface. This is the recommended way to use the async-first API in synchronous contexts:
use ;
use Workspace;
// For native code
let fs = new;
let workspace = new;
// For tests/WASM
let fs = new;
let workspace = new;
// Access the inner sync filesystem if needed
// let inner = async_fs.inner();