//! # Zettel Core Library
//!
//! A robust, composable library for managing Luhmann-style Zettelkasten systems.
//!
//! ## Design Principles
//!
//! - **Composability**: Small, focused modules that work together
//! - **Performance**: Efficient operations for large vaults (10k+ notes)
//! - **Reliability**: Atomic operations with rollback capabilities
//! - **Extensibility**: Plugin system and event hooks
//!
//! ## Quick Start
//!
//! ```rust
//! use zettel_core::{Vault, Config};
//!
//! let config = Config::default();
//! let vault = Vault::open("/path/to/notes", config)?;
//!
//! // Create a new note
//! let id = vault.id_manager().next_sibling("1a")?;
//! let note = vault.create_note(&id, "My New Note")?;
//!
//! // Search notes
//! let results = vault.search("philosophy")?;
//! ```
// pub mod config;
// pub mod error;
// pub mod hooks;
pub mod id;
// pub mod note;
// pub mod search;
// pub mod template;
// pub mod vault;
// pub use config::{Config, ConfigBuilder};
// pub use error::{Result, ZettelError};
// pub use hooks::{Hook, HookEvent, HookManager};
pub use id::{Id, IdError, IdManager};
// pub use note::{LinkType, Note, NoteManager};
// pub use search::{SearchEngine, SearchQuery, SearchResult};
// pub use template::{Template, TemplateEngine};
// pub use vault::{Vault, VaultBuilder};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::Path;
/// Core result type for all zettel operations
pub type Result<T> = std::result::Result<T, ZettelError>;
/// Metadata about a note in the zettelkasten
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NoteMetadata {
pub id: String,
pub title: Option<String>,
pub path: std::path::PathBuf,
pub created: DateTime<Utc>,
pub modified: DateTime<Utc>,
pub parent: Option<String>,
pub children: Vec<String>,
pub links: Vec<String>,
pub backlinks: Vec<String>,
pub tags: Vec<String>,
pub word_count: usize,
}
/// Core vault statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VaultStats {
pub total_notes: usize,
pub orphaned_notes: usize,
pub total_links: usize,
pub broken_links: usize,
pub deepest_level: usize,
pub last_indexed: DateTime<Utc>,
}
/// Represents the structure and operations of a Zettelkasten vault
pub trait VaultOperations {
/// Create a new note with the given ID and title
fn create_note(&self, id: &str, title: Option<&str>) -> Result<Note>;
/// Get an existing note by ID
fn get_note(&self, id: &str) -> Result<Note>;
/// Update an existing note
fn update_note(&self, note: &Note) -> Result<()>;
/// Delete a note by ID
fn delete_note(&self, id: &str) -> Result<()>;
/// List all notes matching optional criteria
fn list_notes(&self, filter: Option<&NoteFilter>) -> Result<Vec<NoteMetadata>>;
/// Search notes by content or metadata
fn search(&self, query: &SearchQuery) -> Result<Vec<SearchResult>>;
/// Get vault statistics
fn stats(&self) -> Result<VaultStats>;
/// Validate vault integrity
fn validate(&self) -> Result<Vec<ValidationIssue>>;
/// Rebuild search index
fn reindex(&self) -> Result<()>;
}
/// Filter criteria for listing notes
#[derive(Debug, Clone, Default)]
pub struct NoteFilter {
pub parent: Option<String>,
pub has_children: Option<bool>,
pub orphaned: Option<bool>,
pub tags: Option<Vec<String>>,
pub created_after: Option<DateTime<Utc>>,
pub created_before: Option<DateTime<Utc>>,
pub modified_after: Option<DateTime<Utc>>,
pub modified_before: Option<DateTime<Utc>>,
pub word_count_min: Option<usize>,
pub word_count_max: Option<usize>,
}
/// Issues found during vault validation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationIssue {
pub severity: IssueSeverity,
pub category: IssueCategory,
pub description: String,
pub file: Option<std::path::PathBuf>,
pub suggestion: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IssueSeverity {
Error, // Breaks functionality
Warning, // Potential problem
Info, // Suggestion for improvement
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IssueCategory {
InvalidId,
MissingFile,
BrokenLink,
DuplicateId,
OrphanedNote,
MalformedContent,
ConfigurationIssue,
}
/// Builder pattern for creating vaults with custom configuration
pub struct VaultBuilder {
path: std::path::PathBuf,
config: Config,
initialize_if_missing: bool,
backup_on_change: bool,
}
impl VaultBuilder {
pub fn new<P: AsRef<Path>>(path: P) -> Self {
Self {
path: path.as_ref().to_path_buf(),
config: Config::default(),
initialize_if_missing: false,
backup_on_change: false,
}
}
pub fn with_config(mut self, config: Config) -> Self {
self.config = config;
self
}
pub fn initialize_if_missing(mut self, initialize: bool) -> Self {
self.initialize_if_missing = initialize;
self
}
pub fn backup_on_change(mut self, backup: bool) -> Self {
self.backup_on_change = backup;
self
}
pub fn build(self) -> Result<Vault> {
Vault::new(
self.path,
self.config,
self.initialize_if_missing,
self.backup_on_change,
)
}
}
/// Event types for the hook system
#[derive(Debug, Clone)]
pub enum EventType {
PreNoteCreate { id: String, title: Option<String> },
PostNoteCreate { note: NoteMetadata },
PreNoteUpdate { old: NoteMetadata, new: Note },
PostNoteUpdate { note: NoteMetadata },
PreNoteDelete { note: NoteMetadata },
PostNoteDelete { id: String },
PreLinkCreate { from: String, to: String },
PostLinkCreate { from: String, to: String },
VaultIndexed { stats: VaultStats },
}
/// Trait for plugins that extend vault functionality
pub trait Plugin: Send + Sync {
fn name(&self) -> &str;
fn version(&self) -> &str;
fn description(&self) -> &str;
/// Initialize plugin with vault reference
fn initialize(&mut self, vault: &Vault) -> Result<()>;
/// Handle vault events
fn handle_event(&self, event: &EventType) -> Result<()>;
/// Provide additional CLI commands (optional)
fn commands(&self) -> Vec<PluginCommand> {
Vec::new()
}
/// Provide additional configuration schema (optional)
fn config_schema(&self) -> Option<serde_json::Value> {
None
}
}
/// Command provided by a plugin
#[derive(Debug, Clone)]
pub struct PluginCommand {
pub name: String,
pub description: String,
pub handler: fn(&[String]) -> Result<String>,
}
/// Atomic operation support for reliable modifications
pub struct Transaction<'a> {
vault: &'a Vault,
operations: Vec<Operation>,
checkpoint: Option<String>,
}
#[derive(Debug, Clone)]
pub enum Operation {
CreateNote {
id: String,
title: Option<String>,
content: String,
},
UpdateNote {
id: String,
content: String,
},
DeleteNote {
id: String,
},
CreateLink {
from: String,
to: String,
},
DeleteLink {
from: String,
to: String,
},
}
impl<'a> Transaction<'a> {
pub fn new(vault: &'a Vault) -> Self {
Self {
vault,
operations: Vec::new(),
checkpoint: None,
}
}
pub fn create_note(&mut self, id: &str, title: Option<&str>, content: &str) -> &mut Self {
self.operations.push(Operation::CreateNote {
id: id.to_string(),
title: title.map(|s| s.to_string()),
content: content.to_string(),
});
self
}
pub fn update_note(&mut self, id: &str, content: &str) -> &mut Self {
self.operations.push(Operation::UpdateNote {
id: id.to_string(),
content: content.to_string(),
});
self
}
pub fn create_link(&mut self, from: &str, to: &str) -> &mut Self {
self.operations.push(Operation::CreateLink {
from: from.to_string(),
to: to.to_string(),
});
self
}
/// Execute all operations atomically
pub fn commit(self) -> Result<Vec<String>> {
self.vault.execute_transaction(self.operations)
}
/// Rollback any changes made during this transaction
pub fn rollback(self) -> Result<()> {
if let Some(checkpoint) = self.checkpoint {
self.vault.restore_checkpoint(&checkpoint)
} else {
Ok(())
}
}
}
/// Performance monitoring and metrics
#[derive(Debug, Clone, Default)]
pub struct Metrics {
pub operations_total: u64,
pub search_queries_total: u64,
pub index_rebuilds_total: u64,
pub average_search_time_ms: f64,
pub cache_hits: u64,
pub cache_misses: u64,
pub last_backup: Option<DateTime<Utc>>,
}
/// Main entry point: convenience function for opening a vault
pub fn open<P: AsRef<Path>>(path: P) -> Result<Vault> {
VaultBuilder::new(path).build()
}
/// Initialize a new vault at the given path
pub fn init<P: AsRef<Path>>(path: P, config: Option<Config>) -> Result<Vault> {
VaultBuilder::new(path)
.with_config(config.unwrap_or_default())
.initialize_if_missing(true)
.build()
}
/// Validate an existing vault without opening it
pub fn validate<P: AsRef<Path>>(path: P) -> Result<Vec<ValidationIssue>> {
let vault = open(path)?;
vault.validate()
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_vault_creation() {
let temp_dir = TempDir::new().unwrap();
let vault = VaultBuilder::new(temp_dir.path())
.initialize_if_missing(true)
.build()
.unwrap();
assert!(vault.path().exists());
}
#[test]
fn test_note_creation() {
let temp_dir = TempDir::new().unwrap();
let vault = VaultBuilder::new(temp_dir.path())
.initialize_if_missing(true)
.build()
.unwrap();
let note = vault.create_note("1", Some("Test Note")).unwrap();
assert_eq!(note.id(), "1");
assert_eq!(note.title(), Some("Test Note"));
}
#[test]
fn test_id_generation() {
let temp_dir = TempDir::new().unwrap();
let vault = VaultBuilder::new(temp_dir.path())
.initialize_if_missing(true)
.build()
.unwrap();
let id_manager = vault.id_manager();
assert_eq!(id_manager.next_sibling("1").unwrap(), "2");
assert_eq!(id_manager.next_child("1").unwrap(), "1a");
assert_eq!(id_manager.next_child("1a").unwrap(), "1a1");
}
#[test]
fn test_transaction_rollback() {
let temp_dir = TempDir::new().unwrap();
let vault = VaultBuilder::new(temp_dir.path())
.initialize_if_missing(true)
.build()
.unwrap();
let mut tx = Transaction::new(&vault);
tx.create_note("1", Some("Test"), "# Test\n\nContent");
// Simulate failure and rollback
tx.rollback().unwrap();
// Note should not exist
assert!(vault.get_note("1").is_err());
}
}