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