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:
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");
+ }
}