Skip to main content

blvm_sdk/module/
module_db.rs

1//! Standard module storage API.
2//!
3//! All modules use this single system: `ModuleDb` for persistent storage.
4//! No ad-hoc storage—open your DB, run migrations, use named trees.
5//!
6//! ## Usage
7//!
8//! ```ignore
9//! let db = ModuleDb::open(&bootstrap.data_dir)?;
10//! db.run_migrations(migrations!(1 => up_initial, 2 => up_add_proposals))?;
11//! let tree = db.tree("proposals")?;
12//! tree.insert(b"key", b"value")?;
13//! ```
14//!
15//! ## Tree naming
16//!
17//! Define tree names as constants in your module (e.g. `const PROPOSALS_TREE: &str = "proposals"`).
18//! Avoid generic names like `"items"`—use descriptive names for each logical store.
19
20use anyhow::Result;
21use blvm_node::storage::database::{Database, Tree};
22use std::path::Path;
23use std::sync::Arc;
24
25use crate::module::database::{open_module_db, run_migrations, MigrationUp};
26
27/// Standard module database.
28///
29/// Single entry point for all module storage. Opens DB at `{data_dir}/db/`,
30/// runs migrations, provides named trees.
31#[derive(Clone)]
32pub struct ModuleDb {
33    db: Arc<dyn Database>,
34}
35
36impl ModuleDb {
37    /// Open the module database at `{data_dir}/db/`.
38    ///
39    /// Uses the same backend as the node when `MODULE_CONFIG_DATABASE_BACKEND` is set.
40    pub fn open<P: AsRef<Path>>(data_dir: P) -> Result<Self> {
41        let db = open_module_db(data_dir)?;
42        Ok(Self { db })
43    }
44
45    /// Open and run migrations. Convention for modules that use migrations.
46    pub fn open_with_migrations<P: AsRef<Path>>(
47        data_dir: P,
48        migrations: &[(u32, MigrationUp)],
49    ) -> Result<Self> {
50        let db = Self::open(data_dir)?;
51        db.run_migrations(migrations)?;
52        Ok(db)
53    }
54
55    /// Open at data_dir, or fallback to temp dir when primary open fails.
56    ///
57    /// Both attempts use [`open_module_db`](super::database::open_module_db) (Sled/TidesDB).
58    /// Avoids a hard-coded Redb fallback, which fails when `blvm-node` is built without `redb`.
59    pub fn open_or_temp<P: AsRef<Path>>(data_dir: P, module_name: &str) -> Result<Self> {
60        let data_dir = data_dir.as_ref().to_path_buf();
61        Self::open(&data_dir).or_else(|_| {
62            let temp = std::env::temp_dir().join(module_name);
63            let _ = std::fs::create_dir_all(&temp);
64            Self::open(&temp)
65        })
66    }
67
68    /// Like `open_or_temp` but runs migrations when the primary data_dir succeeds.
69    pub fn open_or_temp_with_migrations<P: AsRef<Path>>(
70        data_dir: P,
71        module_name: &str,
72        migrations: &[(u32, MigrationUp)],
73    ) -> Result<Self> {
74        let db = Self::open_or_temp(data_dir, module_name)?;
75        let _ = db.run_migrations(migrations);
76        Ok(db)
77    }
78
79    /// Run pending migrations.
80    pub fn run_migrations(&self, migrations: &[(u32, MigrationUp)]) -> Result<()> {
81        run_migrations(&self.db, migrations)
82    }
83
84    /// Open a named tree. Use descriptive constants defined in your module.
85    pub fn tree(&self, name: &str) -> Result<Arc<dyn Tree>> {
86        let t = self.db.open_tree(name)?;
87        Ok(Arc::from(t))
88    }
89
90    /// Access the underlying database (for compatibility with code expecting `Arc<dyn Database>`).
91    pub fn as_db(&self) -> Arc<dyn Database> {
92        Arc::clone(&self.db)
93    }
94}