lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit cb167a7004167bf131efcb0a5e9815e35ad756c2
parent 3de23850c76e522ee039353470f17ec31ea4359a
Author: triesap <tyson@radroots.org>
Date:   Thu, 19 Feb 2026 18:15:07 +0000

nostr-ndb: add ingest api with source metadata


- add client and relay ingest source modeling for metadata
- add json and typed event ingest entry points on the ndb handle
- map typed event serialization failures into explicit crate errors
- add unit coverage for source builders and signed note ingestion

Diffstat:
MCargo.lock | 1+
Mnostr-ndb/Cargo.toml | 1+
Mnostr-ndb/src/error.rs | 3+++
Anostr-ndb/src/ingest.rs | 41+++++++++++++++++++++++++++++++++++++++++
Mnostr-ndb/src/lib.rs | 4++++
Mnostr-ndb/src/ndb.rs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
6 files changed, 110 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -2240,6 +2240,7 @@ dependencies = [ "nostrdb", "radroots-nostr", "radroots-nostr-runtime", + "serde_json", "tempfile", "thiserror 1.0.69", "tokio", diff --git a/nostr-ndb/Cargo.toml b/nostr-ndb/Cargo.toml @@ -19,6 +19,7 @@ radroots-nostr = { workspace = true, default-features = false, features = ["std" radroots-nostr-runtime = { workspace = true, optional = true, default-features = false, features = ["std", "rt", "nostr-client"] } futures = { workspace = true, optional = true } nostrdb = { version = "0.9.0", optional = true } +serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, optional = true, features = ["rt", "sync", "time"] } diff --git a/nostr-ndb/src/error.rs b/nostr-ndb/src/error.rs @@ -5,6 +5,9 @@ pub enum RadrootsNostrNdbError { #[error("database path must be utf-8")] NonUtf8Path, + #[error("event json encode failed: {0}")] + EventJsonEncode(String), + #[error("nostrdb error: {0}")] Ndb(String), } diff --git a/nostr-ndb/src/ingest.rs b/nostr-ndb/src/ingest.rs @@ -0,0 +1,41 @@ +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum RadrootsNostrNdbIngestSource { + Client, + Relay { relay_url: Option<String> }, +} + +impl RadrootsNostrNdbIngestSource { + pub fn client() -> Self { + Self::Client + } + + pub fn relay(relay_url: impl Into<String>) -> Self { + Self::Relay { + relay_url: Some(relay_url.into()), + } + } + + pub fn relay_unknown() -> Self { + Self::Relay { relay_url: None } + } + + pub(crate) fn to_ndb_metadata(&self) -> nostrdb::IngestMetadata { + match self { + Self::Client => nostrdb::IngestMetadata::new().client(true), + Self::Relay { relay_url } => { + let meta = nostrdb::IngestMetadata::new().client(false); + if let Some(relay_url) = relay_url { + meta.relay(relay_url.as_str()) + } else { + meta + } + } + } + } +} + +impl Default for RadrootsNostrNdbIngestSource { + fn default() -> Self { + Self::Client + } +} diff --git a/nostr-ndb/src/lib.rs b/nostr-ndb/src/lib.rs @@ -10,6 +10,8 @@ extern crate alloc; pub mod config; pub mod error; #[cfg(feature = "ndb")] +pub mod ingest; +#[cfg(feature = "ndb")] pub mod ndb; pub mod prelude { @@ -17,5 +19,7 @@ pub mod prelude { pub use crate::config::RadrootsNostrNdbConfig; pub use crate::error::RadrootsNostrNdbError; #[cfg(feature = "ndb")] + pub use crate::ingest::RadrootsNostrNdbIngestSource; + #[cfg(feature = "ndb")] pub use crate::ndb::RadrootsNostrNdb; } diff --git a/nostr-ndb/src/ndb.rs b/nostr-ndb/src/ndb.rs @@ -1,5 +1,7 @@ use crate::config::RadrootsNostrNdbConfig; use crate::error::RadrootsNostrNdbError; +use crate::ingest::RadrootsNostrNdbIngestSource; +use radroots_nostr::prelude::RadrootsNostrEvent; use std::path::Path; #[derive(Debug, Clone)] @@ -29,14 +31,36 @@ impl RadrootsNostrNdb { &self.db_dir } - pub(crate) fn inner(&self) -> &nostrdb::Ndb { - &self.inner + pub fn ingest_event_json_with_source( + &self, + json: &str, + source: RadrootsNostrNdbIngestSource, + ) -> Result<(), RadrootsNostrNdbError> { + let metadata = source.to_ndb_metadata(); + self.inner.process_event_with(json, metadata)?; + Ok(()) + } + + pub fn ingest_event_json(&self, json: &str) -> Result<(), RadrootsNostrNdbError> { + self.ingest_event_json_with_source(json, RadrootsNostrNdbIngestSource::default()) + } + + pub fn ingest_event( + &self, + event: &RadrootsNostrEvent, + source: RadrootsNostrNdbIngestSource, + ) -> Result<(), RadrootsNostrNdbError> { + let json = serde_json::to_string(event) + .map_err(|source| RadrootsNostrNdbError::EventJsonEncode(source.to_string()))?; + self.ingest_event_json_with_source(json.as_str(), source) } } #[cfg(test)] mod tests { use super::*; + use crate::ingest::RadrootsNostrNdbIngestSource; + use radroots_nostr::prelude::{RadrootsNostrEventBuilder, RadrootsNostrKeys}; use tempfile::TempDir; #[test] @@ -63,4 +87,38 @@ mod tests { assert_eq!(ndb.db_dir(), db_dir.as_path()); assert!(db_dir.exists()); } + + #[test] + fn ingest_source_builders_track_origin() { + assert_eq!( + RadrootsNostrNdbIngestSource::default(), + RadrootsNostrNdbIngestSource::client() + ); + assert_eq!( + RadrootsNostrNdbIngestSource::relay("wss://relay.radroots.org"), + RadrootsNostrNdbIngestSource::Relay { + relay_url: Some("wss://relay.radroots.org".into()) + } + ); + assert_eq!( + RadrootsNostrNdbIngestSource::relay_unknown(), + RadrootsNostrNdbIngestSource::Relay { relay_url: None } + ); + } + + #[test] + fn ingest_event_accepts_signed_note() { + let tmp_dir = TempDir::new().expect("tempdir should open"); + let db_dir = tmp_dir.path().join("ndb"); + let config = RadrootsNostrNdbConfig::new(&db_dir); + let ndb = RadrootsNostrNdb::open(config).expect("database should open"); + + let keys = RadrootsNostrKeys::generate(); + let event = RadrootsNostrEventBuilder::text_note("hello from ndb") + .sign_with_keys(&keys) + .expect("event should sign"); + + ndb.ingest_event(&event, RadrootsNostrNdbIngestSource::client()) + .expect("ingest should succeed"); + } }