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 data_dir fails (e.g. standalone without data dir).
56    pub fn open_or_temp<P: AsRef<Path>>(data_dir: P, module_name: &str) -> Result<Self> {
57        Self::open(&data_dir).or_else(|_| {
58            let temp = std::env::temp_dir().join(module_name);
59            Self::open(&temp).or_else(|_| {
60                let dir = temp.join("db");
61                std::fs::create_dir_all(&dir).ok();
62                let db = blvm_node::storage::database::create_database(
63                    &dir,
64                    blvm_node::storage::database::DatabaseBackend::Redb,
65                    None,
66                )?;
67                Ok(Self { db: Arc::from(db) })
68            })
69        })
70    }
71
72    /// Like `open_or_temp` but runs migrations when the primary data_dir succeeds.
73    pub fn open_or_temp_with_migrations<P: AsRef<Path>>(
74        data_dir: P,
75        module_name: &str,
76        migrations: &[(u32, MigrationUp)],
77    ) -> Result<Self> {
78        let db = Self::open_or_temp(data_dir, module_name)?;
79        let _ = db.run_migrations(migrations);
80        Ok(db)
81    }
82
83    /// Run pending migrations.
84    pub fn run_migrations(&self, migrations: &[(u32, MigrationUp)]) -> Result<()> {
85        run_migrations(&self.db, migrations)
86    }
87
88    /// Open a named tree. Use descriptive constants defined in your module.
89    pub fn tree(&self, name: &str) -> Result<Arc<dyn Tree>> {
90        let t = self.db.open_tree(name)?;
91        Ok(Arc::from(t))
92    }
93
94    /// Access the underlying database (for compatibility with code expecting `Arc<dyn Database>`).
95    pub fn as_db(&self) -> Arc<dyn Database> {
96        Arc::clone(&self.db)
97    }
98}