elph_agent/datastore/
mod.rs1mod 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
22pub struct DatabaseSpec<'a> {
24 pub path: &'a Path,
25 pub migrations: &'static [Migration],
26}
27
28pub async fn ensure_database(path: &Path, migrations: &'static [Migration]) -> Result<()> {
30 ensure_parent_dir(path)?;
31 open_and_migrate(path, migrations).await
32}
33
34pub 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}