#![warn(missing_docs)]
#![warn(rustdoc::bare_urls)]
use core::fmt;
use std::collections::{BTreeSet, HashSet};
use std::sync::Arc;
pub use async_trait::async_trait;
pub use nostr;
use nostr::nips::nip01::Coordinate;
use nostr::secp256k1::XOnlyPublicKey;
use nostr::{Event, EventId, Filter, JsonUtil, Kind, Metadata, Timestamp, Url};
mod error;
#[cfg(feature = "flatbuf")]
pub mod flatbuffers;
pub mod index;
pub mod memory;
mod options;
pub mod profile;
mod raw;
mod tag_indexes;
pub use self::error::DatabaseError;
#[cfg(feature = "flatbuf")]
pub use self::flatbuffers::{FlatBufferBuilder, FlatBufferDecode, FlatBufferEncode};
pub use self::index::{DatabaseIndexes, EventIndexResult};
pub use self::memory::MemoryDatabase;
pub use self::options::DatabaseOptions;
pub use self::profile::Profile;
pub use self::raw::RawEvent;
pub enum Backend {
Memory,
LMDB,
SQLite,
IndexedDB,
Custom(String),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Order {
Asc,
#[default]
Desc,
}
pub type DynNostrDatabase = dyn NostrDatabase<Err = DatabaseError>;
pub trait IntoNostrDatabase {
#[doc(hidden)]
fn into_nostr_database(self) -> Arc<DynNostrDatabase>;
}
impl IntoNostrDatabase for Arc<DynNostrDatabase> {
fn into_nostr_database(self) -> Arc<DynNostrDatabase> {
self
}
}
impl<T> IntoNostrDatabase for T
where
T: NostrDatabase + Sized + 'static,
{
fn into_nostr_database(self) -> Arc<DynNostrDatabase> {
Arc::new(EraseNostrDatabaseError(self))
}
}
impl<T> IntoNostrDatabase for Arc<T>
where
T: NostrDatabase + 'static,
{
fn into_nostr_database(self) -> Arc<DynNostrDatabase> {
let ptr: *const T = Arc::into_raw(self);
let ptr_erased = ptr as *const EraseNostrDatabaseError<T>;
unsafe { Arc::from_raw(ptr_erased) }
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait NostrDatabase: AsyncTraitDeps {
type Err: From<DatabaseError> + Into<DatabaseError>;
fn backend(&self) -> Backend;
fn opts(&self) -> DatabaseOptions;
async fn save_event(&self, event: &Event) -> Result<bool, Self::Err>;
async fn has_event_already_been_saved(&self, event_id: &EventId) -> Result<bool, Self::Err>;
async fn has_event_already_been_seen(&self, event_id: &EventId) -> Result<bool, Self::Err>;
async fn has_event_id_been_deleted(&self, event_id: &EventId) -> Result<bool, Self::Err>;
async fn has_coordinate_been_deleted(
&self,
coordinate: &Coordinate,
timestamp: Timestamp,
) -> Result<bool, Self::Err>;
async fn event_id_seen(&self, event_id: EventId, relay_url: Url) -> Result<(), Self::Err>;
async fn event_seen_on_relays(
&self,
event_id: EventId,
) -> Result<Option<HashSet<Url>>, Self::Err>;
async fn event_by_id(&self, event_id: EventId) -> Result<Event, Self::Err>;
async fn count(&self, filters: Vec<Filter>) -> Result<usize, Self::Err>;
async fn query(&self, filters: Vec<Filter>, order: Order) -> Result<Vec<Event>, Self::Err>;
async fn event_ids_by_filters(
&self,
filters: Vec<Filter>,
order: Order,
) -> Result<Vec<EventId>, Self::Err>;
async fn negentropy_items(
&self,
filter: Filter,
) -> Result<Vec<(EventId, Timestamp)>, Self::Err>;
async fn wipe(&self) -> Result<(), Self::Err>;
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait NostrDatabaseExt: NostrDatabase {
#[tracing::instrument(skip_all, level = "trace")]
async fn profile(&self, public_key: XOnlyPublicKey) -> Result<Profile, Self::Err> {
let filter = Filter::new()
.author(public_key)
.kind(Kind::Metadata)
.limit(1);
let events: Vec<Event> = self.query(vec![filter], Order::Desc).await?;
match events.first() {
Some(event) => match Metadata::from_json(event.content()) {
Ok(metadata) => Ok(Profile::new(public_key, metadata)),
Err(e) => {
tracing::error!("Impossible to deserialize profile metadata: {e}");
Ok(Profile::from(public_key))
}
},
None => Ok(Profile::from(public_key)),
}
}
#[tracing::instrument(skip_all, level = "trace")]
async fn contacts_public_keys(
&self,
public_key: XOnlyPublicKey,
) -> Result<Vec<XOnlyPublicKey>, Self::Err> {
let filter = Filter::new()
.author(public_key)
.kind(Kind::ContactList)
.limit(1);
let events: Vec<Event> = self.query(vec![filter], Order::Desc).await?;
match events.first() {
Some(event) => Ok(event.public_keys().copied().collect()),
None => Ok(Vec::new()),
}
}
#[tracing::instrument(skip_all, level = "trace")]
async fn contacts(&self, public_key: XOnlyPublicKey) -> Result<BTreeSet<Profile>, Self::Err> {
let filter = Filter::new()
.author(public_key)
.kind(Kind::ContactList)
.limit(1);
let events: Vec<Event> = self.query(vec![filter], Order::Desc).await?;
match events.first() {
Some(event) => {
let filter = Filter::new()
.authors(event.public_keys().copied())
.kind(Kind::Metadata);
let mut contacts: HashSet<Profile> = self
.query(vec![filter], Order::Desc)
.await?
.into_iter()
.map(|e| {
let metadata: Metadata =
Metadata::from_json(e.content()).unwrap_or_default();
Profile::new(e.author(), metadata)
})
.collect();
contacts.extend(event.public_keys().copied().map(Profile::from));
Ok(contacts.into_iter().collect())
}
None => Ok(BTreeSet::new()),
}
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<T: NostrDatabase + ?Sized> NostrDatabaseExt for T {}
#[repr(transparent)]
struct EraseNostrDatabaseError<T>(T);
impl<T: fmt::Debug> fmt::Debug for EraseNostrDatabaseError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<T: NostrDatabase> NostrDatabase for EraseNostrDatabaseError<T> {
type Err = DatabaseError;
fn backend(&self) -> Backend {
self.0.backend()
}
fn opts(&self) -> DatabaseOptions {
self.0.opts()
}
async fn save_event(&self, event: &Event) -> Result<bool, Self::Err> {
self.0.save_event(event).await.map_err(Into::into)
}
async fn has_event_already_been_saved(&self, event_id: &EventId) -> Result<bool, Self::Err> {
self.0
.has_event_already_been_saved(event_id)
.await
.map_err(Into::into)
}
async fn has_event_already_been_seen(&self, event_id: &EventId) -> Result<bool, Self::Err> {
self.0
.has_event_already_been_seen(event_id)
.await
.map_err(Into::into)
}
async fn has_event_id_been_deleted(&self, event_id: &EventId) -> Result<bool, Self::Err> {
self.0
.has_event_id_been_deleted(event_id)
.await
.map_err(Into::into)
}
async fn has_coordinate_been_deleted(
&self,
coordinate: &Coordinate,
timestamp: Timestamp,
) -> Result<bool, Self::Err> {
self.0
.has_coordinate_been_deleted(coordinate, timestamp)
.await
.map_err(Into::into)
}
async fn event_id_seen(&self, event_id: EventId, relay_url: Url) -> Result<(), Self::Err> {
self.0
.event_id_seen(event_id, relay_url)
.await
.map_err(Into::into)
}
async fn event_seen_on_relays(
&self,
event_id: EventId,
) -> Result<Option<HashSet<Url>>, Self::Err> {
self.0
.event_seen_on_relays(event_id)
.await
.map_err(Into::into)
}
async fn event_by_id(&self, event_id: EventId) -> Result<Event, Self::Err> {
self.0.event_by_id(event_id).await.map_err(Into::into)
}
async fn count(&self, filters: Vec<Filter>) -> Result<usize, Self::Err> {
self.0.count(filters).await.map_err(Into::into)
}
async fn query(&self, filters: Vec<Filter>, order: Order) -> Result<Vec<Event>, Self::Err> {
self.0.query(filters, order).await.map_err(Into::into)
}
async fn event_ids_by_filters(
&self,
filters: Vec<Filter>,
order: Order,
) -> Result<Vec<EventId>, Self::Err> {
self.0
.event_ids_by_filters(filters, order)
.await
.map_err(Into::into)
}
async fn negentropy_items(
&self,
filter: Filter,
) -> Result<Vec<(EventId, Timestamp)>, Self::Err> {
self.0.negentropy_items(filter).await.map_err(Into::into)
}
async fn wipe(&self) -> Result<(), Self::Err> {
self.0.wipe().await.map_err(Into::into)
}
}
#[cfg(not(target_arch = "wasm32"))]
pub trait SendOutsideWasm: Send {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Send> SendOutsideWasm for T {}
#[cfg(target_arch = "wasm32")]
pub trait SendOutsideWasm {}
#[cfg(target_arch = "wasm32")]
impl<T> SendOutsideWasm for T {}
#[cfg(not(target_arch = "wasm32"))]
pub trait SyncOutsideWasm: Sync {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Sync> SyncOutsideWasm for T {}
#[cfg(target_arch = "wasm32")]
pub trait SyncOutsideWasm {}
#[cfg(target_arch = "wasm32")]
impl<T> SyncOutsideWasm for T {}
pub trait AsyncTraitDeps: std::fmt::Debug + SendOutsideWasm + SyncOutsideWasm {}
impl<T: std::fmt::Debug + SendOutsideWasm + SyncOutsideWasm> AsyncTraitDeps for T {}