commit 3de23850c76e522ee039353470f17ec31ea4359a
parent 7b7d6014a5ff2d77a179ab1637df9fc6a85972f5
Author: triesap <tyson@radroots.org>
Date: Thu, 19 Feb 2026 18:06:45 +0000
nostr-ndb: add config and open lifecycle api
- add a dedicated config type for ndb open settings
- add the radroots ndb handle with deterministic open flow
- map core open-path failures into crate error variants
- add unit tests for config builders and database creation
Diffstat:
6 files changed, 139 insertions(+), 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -2240,6 +2240,7 @@ dependencies = [
"nostrdb",
"radroots-nostr",
"radroots-nostr-runtime",
+ "tempfile",
"thiserror 1.0.69",
"tokio",
]
diff --git a/nostr-ndb/Cargo.toml b/nostr-ndb/Cargo.toml
@@ -24,3 +24,4 @@ tokio = { workspace = true, optional = true, features = ["rt", "sync", "time"] }
[dev-dependencies]
tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] }
+tempfile = { workspace = true }
diff --git a/nostr-ndb/src/config.rs b/nostr-ndb/src/config.rs
@@ -0,0 +1,51 @@
+use std::path::{Path, PathBuf};
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct RadrootsNostrNdbConfig {
+ db_dir: PathBuf,
+ mapsize_bytes: Option<usize>,
+ ingester_threads: Option<i32>,
+ skip_validation: bool,
+}
+
+impl RadrootsNostrNdbConfig {
+ pub fn new(db_dir: impl Into<PathBuf>) -> Self {
+ Self {
+ db_dir: db_dir.into(),
+ mapsize_bytes: None,
+ ingester_threads: None,
+ skip_validation: false,
+ }
+ }
+
+ pub fn db_dir(&self) -> &Path {
+ &self.db_dir
+ }
+
+ pub fn mapsize_bytes(&self) -> Option<usize> {
+ self.mapsize_bytes
+ }
+
+ pub fn ingester_threads(&self) -> Option<i32> {
+ self.ingester_threads
+ }
+
+ pub fn skip_validation(&self) -> bool {
+ self.skip_validation
+ }
+
+ pub fn with_mapsize_bytes(mut self, bytes: usize) -> Self {
+ self.mapsize_bytes = Some(bytes);
+ self
+ }
+
+ pub fn with_ingester_threads(mut self, threads: i32) -> Self {
+ self.ingester_threads = Some(threads);
+ self
+ }
+
+ pub fn with_skip_validation(mut self, skip_validation: bool) -> Self {
+ self.skip_validation = skip_validation;
+ self
+ }
+}
diff --git a/nostr-ndb/src/error.rs b/nostr-ndb/src/error.rs
@@ -2,6 +2,16 @@ use thiserror::Error;
#[derive(Debug, Error)]
pub enum RadrootsNostrNdbError {
- #[error("not implemented")]
- NotImplemented,
+ #[error("database path must be utf-8")]
+ NonUtf8Path,
+
+ #[error("nostrdb error: {0}")]
+ Ndb(String),
+}
+
+#[cfg(feature = "ndb")]
+impl From<nostrdb::Error> for RadrootsNostrNdbError {
+ fn from(value: nostrdb::Error) -> Self {
+ Self::Ndb(value.to_string())
+ }
}
diff --git a/nostr-ndb/src/lib.rs b/nostr-ndb/src/lib.rs
@@ -6,8 +6,16 @@ compile_error!("radroots-nostr-ndb requires the std feature");
extern crate alloc;
+#[cfg(feature = "ndb")]
+pub mod config;
pub mod error;
+#[cfg(feature = "ndb")]
+pub mod ndb;
pub mod prelude {
+ #[cfg(feature = "ndb")]
+ pub use crate::config::RadrootsNostrNdbConfig;
pub use crate::error::RadrootsNostrNdbError;
+ #[cfg(feature = "ndb")]
+ pub use crate::ndb::RadrootsNostrNdb;
}
diff --git a/nostr-ndb/src/ndb.rs b/nostr-ndb/src/ndb.rs
@@ -0,0 +1,66 @@
+use crate::config::RadrootsNostrNdbConfig;
+use crate::error::RadrootsNostrNdbError;
+use std::path::Path;
+
+#[derive(Debug, Clone)]
+pub struct RadrootsNostrNdb {
+ db_dir: std::path::PathBuf,
+ pub(crate) inner: nostrdb::Ndb,
+}
+
+impl RadrootsNostrNdb {
+ pub fn open(config: RadrootsNostrNdbConfig) -> Result<Self, RadrootsNostrNdbError> {
+ let mut inner_config = nostrdb::Config::new().skip_validation(config.skip_validation());
+ if let Some(mapsize_bytes) = config.mapsize_bytes() {
+ inner_config = inner_config.set_mapsize(mapsize_bytes);
+ }
+ if let Some(ingester_threads) = config.ingester_threads() {
+ inner_config = inner_config.set_ingester_threads(ingester_threads);
+ }
+
+ let db_dir = config.db_dir().to_path_buf();
+ let db_dir_str = db_dir.to_str().ok_or(RadrootsNostrNdbError::NonUtf8Path)?;
+ let inner = nostrdb::Ndb::new(db_dir_str, &inner_config)?;
+
+ Ok(Self { db_dir, inner })
+ }
+
+ pub fn db_dir(&self) -> &Path {
+ &self.db_dir
+ }
+
+ pub(crate) fn inner(&self) -> &nostrdb::Ndb {
+ &self.inner
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use tempfile::TempDir;
+
+ #[test]
+ fn config_builder_tracks_values() {
+ let config = RadrootsNostrNdbConfig::new("target/testdbs/nostr_ndb_config")
+ .with_mapsize_bytes(1024 * 1024)
+ .with_ingester_threads(2)
+ .with_skip_validation(true);
+
+ assert_eq!(config.mapsize_bytes(), Some(1024 * 1024));
+ assert_eq!(config.ingester_threads(), Some(2));
+ assert!(config.skip_validation());
+ }
+
+ #[test]
+ fn open_creates_database() {
+ let tmp_dir = TempDir::new().expect("tempdir should open");
+ let db_dir = tmp_dir.path().join("ndb");
+ let config = RadrootsNostrNdbConfig::new(&db_dir)
+ .with_mapsize_bytes(64 * 1024 * 1024)
+ .with_ingester_threads(1);
+
+ let ndb = RadrootsNostrNdb::open(config).expect("database should open");
+ assert_eq!(ndb.db_dir(), db_dir.as_path());
+ assert!(db_dir.exists());
+ }
+}