syntaxis/docs/persistence-quick-start.md
Jesús Pérez 9cef9b8d57 refactor: consolidate configuration directories
Merge _configs/ into config/ for single configuration directory.
Update all path references.

Changes:
- Move _configs/* to config/
- Update .gitignore for new patterns
- No code references to _configs/ found

Impact: -1 root directory (layout_conventions.md compliance)
2025-12-26 18:36:23 +00:00

9.0 KiB

Persistence Layer Quick Start Guide

This guide shows how to use the new trait-based database abstraction layer.


Using SQLite Backend

1. Create Database Instance

use workspace_core::SqliteDatabase;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create SQLite database
    let db = SqliteDatabase::new("workspace.db").await?;

    // Or use in-memory database (for testing)
    let db = SqliteDatabase::new_memory().await?;

    Ok(())
}

2. Create a Project

use workspace_core::{SqliteDatabase, DbProject};
use chrono::Utc;
use uuid::Uuid;

let db = SqliteDatabase::new("workspace.db").await?;

let project = DbProject {
    id: Uuid::new_v4().to_string(),
    name: "My Project".to_string(),
    version: "0.1.0".to_string(),
    description: "A new project".to_string(),
    project_type: "MultiLang".to_string(),
    current_phase: "Creation".to_string(),
    created_at: Utc::now().to_rfc3339(),
    updated_at: Utc::now().to_rfc3339(),
};

db.create_project(&project).await?;

3. Retrieve Projects

// Get all projects
let projects = db.list_projects().await?;
println!("Found {} projects", projects.len());

// Get specific project
if let Some(project) = db.get_project("project-id").await? {
    println!("Project: {}", project.name);
}

4. Add Checklist Items

use workspace_core::DbChecklistItem;

let item = DbChecklistItem {
    id: Uuid::new_v4().to_string(),
    project_id: project.id.clone(),
    phase: "Creation".to_string(),
    task_id: "task-1".to_string(),
    description: "Complete project setup".to_string(),
    completed: false,
    completed_at: None,
    created_at: Utc::now().to_rfc3339(),
    task_type: "Setup".to_string(),
    task_priority: "High".to_string(),
    task_due: Some("2025-12-31".to_string()),
    task_estimation: Some("2d".to_string()),
    task_deps: "[]".to_string(),
    task_note: Some("Remember to test").to_string(),
    task_name: Some("Setup".to_string()),
};

db.create_checklist_item(&item).await?;

5. Track Phase Transitions

use workspace_core::DbPhaseTransition;

let transition = DbPhaseTransition {
    id: Uuid::new_v4().to_string(),
    project_id: project.id.clone(),
    from_phase: "Creation".to_string(),
    to_phase: "Development".to_string(),
    timestamp: Utc::now().to_rfc3339(),
    reason: Some("Project setup complete".to_string()),
};

db.record_phase_transition(&transition).await?;

Using Configuration Files

1. Create Configuration File

configs/database.toml

engine = "sqlite"

[sqlite]
path = "~/.local/share/core/workspace.db"
max_connections = 5
timeout_secs = 30
wal_mode = true
pragma_synchronous = "NORMAL"
pragma_cache_size = 2000

2. Load Configuration

use workspace_core::DatabaseConfig;
use std::path::Path;

let config = DatabaseConfig::load_from_file("configs/database.toml")?;

// Validate configuration
config.validate()?;

// Check which backend is configured
if config.is_sqlite() {
    println!("Using SQLite backend");
}

3. Runtime Selection (Future)

use workspace_core::{DatabaseConfig, Database, SqliteDatabase};

let config = DatabaseConfig::load_from_file("configs/database.toml")?;

// Create appropriate backend based on configuration
let db: Box<dyn Database> = match config.engine.as_str() {
    "sqlite" => {
        let sqlite_config = config.sqlite.ok_or("No SQLite config")?;
        let db = SqliteDatabase::new(&sqlite_config.path).await?;
        Box::new(db)
    }
    "surrealdb" => {
        // Once SurrealDB is implemented:
        // let db = SurrealDatabase::new(...).await?;
        // Box::new(db)
        return Err("SurrealDB not yet implemented".into());
    }
    _ => return Err("Unknown database engine".into()),
};

// Use database transparently
db.list_projects().await?;

Error Handling

Pattern 1: Propagate with ?

async fn create_project(db: &dyn Database, name: &str) -> Result<()> {
    let project = DbProject {
        id: Uuid::new_v4().to_string(),
        name: name.to_string(),
        // ... other fields ...
    };

    db.create_project(&project).await?; // Propagates error
    Ok(())
}

Pattern 2: Match Errors

match db.get_project("nonexistent").await {
    Ok(Some(project)) => println!("Found: {}", project.name),
    Ok(None) => println!("Project not found"),
    Err(e) => eprintln!("Error: {}", e),
}

Pattern 3: Check Error Type

use workspace_core::error::LifecycleError;

match db.create_project(&project).await {
    Ok(p) => println!("Created: {}", p.id),
    Err(LifecycleError::Database(msg)) => eprintln!("DB error: {}", msg),
    Err(e) => eprintln!("Other error: {}", e),
}

Testing

In-Memory Database

#[tokio::test]
async fn test_project_creation() {
    let db = SqliteDatabase::new_memory().await.unwrap();

    let project = DbProject {
        id: "test-1".to_string(),
        name: "Test".to_string(),
        // ... other fields ...
    };

    let created = db.create_project(&project).await.unwrap();
    assert_eq!(created.name, "Test");
}

Temporary File Database

use tempfile::tempdir;

#[tokio::test]
async fn test_persistence() {
    let dir = tempdir().unwrap();
    let db_path = dir.path().join("test.db").to_string_lossy().to_string();

    let db = SqliteDatabase::new(&db_path).await.unwrap();

    // Your test code here
}

Best Practices

1. Use Type Alias for Result

use workspace_core::error::Result;

async fn my_function() -> Result<String> {
    // Function body
    Ok("success".to_string())
}

2. Always Validate IDs

if let Some(project) = db.get_project(&project_id).await? {
    // Process project
} else {
    eprintln!("Project {} not found", project_id);
}

3. Batch Operations When Possible

// Good: create multiple items in a loop
for item in items {
    db.create_checklist_item(&item).await?;
}

4. Use Configuration for Paths

// Good
let path = config.sqlite.unwrap().path;
let db = SqliteDatabase::new(&path).await?;

// Bad
let db = SqliteDatabase::new("workspace.db").await?; // hardcoded

5. Always Handle Connection Errors

match db.ping().await {
    Ok(()) => println!("Database is healthy"),
    Err(e) => eprintln!("Database is unavailable: {}", e),
}

Common Operations

List All Projects for a Phase

let projects = db.list_projects().await?;
for project in projects {
    if project.current_phase == "Development" {
        println!("{}: {}", project.id, project.name);
    }
}

Get Project Completion Percentage

let completion = db.get_completion_percentage(&project_id, "Development").await?;
println!("Phase completion: {:.1}%", completion);

Record Security Assessment

use workspace_core::DbSecurityAssessment;

let assessment = DbSecurityAssessment {
    id: Uuid::new_v4().to_string(),
    project_id: project.id.clone(),
    profile: "OWASP".to_string(),
    risk_level: "Medium".to_string(),
    passed_rules: 45,
    failed_rules: 5,
    critical_issues: 0,
    assessment_date: Utc::now().to_rfc3339(),
};

db.create_security_assessment(&assessment).await?;

Get Phase History

let history = db.get_phase_history(&project_id).await?;
for transition in history {
    println!(
        "{}: {}{} (reason: {:?})",
        transition.timestamp,
        transition.from_phase,
        transition.to_phase,
        transition.reason
    );
}

Troubleshooting

Issue: "Database is locked"

Solution: SQLite doesn't handle high concurrency well. For production:

  1. Reduce connection pool size
  2. Use timeout configurations
  3. Switch to SurrealDB (when available)

Issue: "File not found"

Solution: Ensure directory exists:

let path = Path::new("~/.local/share/core/workspace.db");
if let Some(parent) = path.parent() {
    std::fs::create_dir_all(parent)?;
}

Issue: "Connection timeout"

Solution: Increase timeout in configuration:

[sqlite]
timeout_secs = 60  # Increase from default 30

Next Steps

  1. Review SURREALDB_IMPLEMENTATION_PROGRESS.md for architecture details
  2. Check src/persistence/mod.rs for full Database trait documentation
  3. Explore src/persistence/config.rs for configuration options
  4. Study src/persistence/sqlite_impl.rs for implementation patterns
  5. Run cargo test -p syntaxis-core --lib persistence to verify setup

References


Last Updated: November 15, 2025 Status: Ready for Use Support: See SURREALDB_IMPLEMENTATION_PROGRESS.md for more details