Skip to main content

elph_agent/datastore/
mod.rs

1//! Turso local database helpers and migration runner.
2
3mod migrations;
4
5use std::path::Path;
6
7use thiserror::Error;
8use turso::Builder;
9
10pub use migrations::{Migration, run as run_migrations};
11
12#[derive(Debug, Error)]
13pub enum DatastoreError {
14    #[error("turso database error: {0}")]
15    Turso(#[from] turso::Error),
16    #[error("io error: {0}")]
17    Io(#[from] std::io::Error),
18}
19
20pub type Result<T> = std::result::Result<T, DatastoreError>;
21
22/// A local database file and its pending migrations.
23pub struct DatabaseSpec<'a> {
24    pub path: &'a Path,
25    pub migrations: &'static [Migration],
26}
27
28/// Initialize one local Turso database and apply pending migrations.
29pub async fn ensure_database(path: &Path, migrations: &'static [Migration]) -> Result<()> {
30    ensure_parent_dir(path)?;
31    open_and_migrate(path, migrations).await
32}
33
34/// Initialize multiple local Turso databases.
35pub async fn ensure_databases(specs: &[DatabaseSpec<'_>]) -> Result<()> {
36    for spec in specs {
37        ensure_database(spec.path, spec.migrations).await?;
38    }
39    Ok(())
40}
41
42async fn open_and_migrate(path: &Path, migrations: &'static [Migration]) -> Result<()> {
43    let db = Builder::new_local(&path.to_string_lossy()).build().await?;
44    let conn = db.connect()?;
45    migrations::run(&conn, migrations).await?;
46    Ok(())
47}
48
49fn ensure_parent_dir(path: &Path) -> Result<()> {
50    if let Some(parent) = path.parent() {
51        std::fs::create_dir_all(parent)?;
52    }
53    Ok(())
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    static TEST_MIGRATIONS: [Migration; 1] = [Migration {
61        version: 1,
62        name: "create_notes",
63        up: "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL)",
64    }];
65
66    #[tokio::test]
67    async fn ensure_database_applies_migrations() {
68        let tmp = tempfile::tempdir().expect("tempdir");
69        let db_path = tmp.path().join("test.db");
70
71        ensure_database(&db_path, &TEST_MIGRATIONS)
72            .await
73            .expect("ensure database");
74
75        assert!(db_path.exists());
76    }
77}