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)
9.0 KiB
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:
- Reduce connection pool size
- Use timeout configurations
- 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
- Review
SURREALDB_IMPLEMENTATION_PROGRESS.mdfor architecture details - Check
src/persistence/mod.rsfor full Database trait documentation - Explore
src/persistence/config.rsfor configuration options - Study
src/persistence/sqlite_impl.rsfor implementation patterns - Run
cargo test -p syntaxis-core --lib persistenceto verify setup
References
- Database Trait Documentation
- Configuration Guide
- SQLite Implementation
- Error Types
- Migration Infrastructure
- Implementation Progress
- Original Plan
Last Updated: November 15, 2025 Status: Ready for Use Support: See SURREALDB_IMPLEMENTATION_PROGRESS.md for more details