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}