Skip to main content

elph_agent/datastore/
mod.rs

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