nostr_database/
lib.rs

1// Copyright (c) 2022-2023 Yuki Kishimoto
2// Copyright (c) 2023-2025 Rust Nostr Developers
3// Distributed under the MIT software license
4
5//! Nostr Database
6
7#![warn(missing_docs)]
8#![warn(rustdoc::bare_urls)]
9#![warn(clippy::large_futures)]
10#![allow(clippy::mutable_key_type)] // TODO: remove when possible. Needed to suppress false positive for `BTreeSet<Event>`
11
12use std::any::Any;
13use std::collections::HashMap;
14use std::fmt::Debug;
15use std::sync::Arc;
16
17pub use nostr;
18use nostr::prelude::*;
19
20mod collections;
21mod error;
22pub mod ext;
23#[cfg(feature = "flatbuf")]
24pub mod flatbuffers;
25mod helper;
26pub mod memory;
27pub mod prelude;
28pub mod profile;
29
30pub use self::collections::events::Events;
31pub use self::error::DatabaseError;
32#[cfg(feature = "flatbuf")]
33pub use self::flatbuffers::{FlatBufferBuilder, FlatBufferDecode, FlatBufferEncode};
34pub use self::helper::{DatabaseEventResult, DatabaseHelper};
35pub use self::memory::{MemoryDatabase, MemoryDatabaseOptions};
36pub use self::profile::Profile;
37
38/// NIP65 relays map
39pub type RelaysMap = HashMap<RelayUrl, Option<RelayMetadata>>;
40
41/// Backend
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum Backend {
44    /// Memory
45    Memory,
46    /// RocksDB
47    RocksDB,
48    /// Lightning Memory-Mapped Database
49    LMDB,
50    /// SQLite
51    SQLite,
52    /// IndexedDB
53    IndexedDB,
54    /// Custom
55    Custom(String),
56}
57
58impl Backend {
59    /// Check if it's a persistent backend
60    ///
61    /// All values different from [`Backend::Memory`] are considered persistent
62    pub fn is_persistent(&self) -> bool {
63        !matches!(self, Self::Memory)
64    }
65}
66
67/// Database event status
68#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
69pub enum DatabaseEventStatus {
70    /// The event is saved into the database
71    Saved,
72    /// The event is marked as deleted
73    Deleted,
74    /// The event doesn't exist
75    NotExistent,
76}
77
78/// Reason why event wasn't stored into the database
79#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
80pub enum RejectedReason {
81    /// Ephemeral events aren't expected to be stored
82    Ephemeral,
83    /// The event already exists
84    Duplicate,
85    /// The event was deleted
86    Deleted,
87    /// The event is expired
88    Expired,
89    /// The event was replaced
90    Replaced,
91    /// Attempt to delete a non-owned event
92    InvalidDelete,
93    /// Other reason
94    Other,
95}
96
97/// Save event status
98#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
99pub enum SaveEventStatus {
100    /// The event has been successfully saved
101    Success,
102    /// The event has been rejected
103    Rejected(RejectedReason),
104}
105
106impl SaveEventStatus {
107    /// Check if event is successfully saved
108    #[inline]
109    pub fn is_success(&self) -> bool {
110        matches!(self, Self::Success)
111    }
112}
113
114#[doc(hidden)]
115pub trait IntoNostrDatabase {
116    fn into_nostr_database(self) -> Arc<dyn NostrDatabase>;
117}
118
119impl IntoNostrDatabase for Arc<dyn NostrDatabase> {
120    fn into_nostr_database(self) -> Arc<dyn NostrDatabase> {
121        self
122    }
123}
124
125impl<T> IntoNostrDatabase for T
126where
127    T: NostrDatabase + Sized + 'static,
128{
129    fn into_nostr_database(self) -> Arc<dyn NostrDatabase> {
130        Arc::new(self)
131    }
132}
133
134impl<T> IntoNostrDatabase for Arc<T>
135where
136    T: NostrDatabase + 'static,
137{
138    fn into_nostr_database(self) -> Arc<dyn NostrDatabase> {
139        self
140    }
141}
142
143/// Nostr (Events) Database
144pub trait NostrDatabase: Any + Debug + Send + Sync {
145    /// Name of the backend database used
146    fn backend(&self) -> Backend;
147
148    /// Save [`Event`] into store
149    ///
150    /// **This method assumes that [`Event`] was already verified**
151    fn save_event<'a>(
152        &'a self,
153        event: &'a Event,
154    ) -> BoxedFuture<'a, Result<SaveEventStatus, DatabaseError>>;
155
156    /// Check event status by ID
157    ///
158    /// Check if the event is saved, deleted or not existent.
159    fn check_id<'a>(
160        &'a self,
161        event_id: &'a EventId,
162    ) -> BoxedFuture<'a, Result<DatabaseEventStatus, DatabaseError>>;
163
164    /// Get [`Event`] by [`EventId`]
165    fn event_by_id<'a>(
166        &'a self,
167        event_id: &'a EventId,
168    ) -> BoxedFuture<'a, Result<Option<Event>, DatabaseError>>;
169
170    /// Count the number of events found with [`Filter`].
171    ///
172    /// Use `Filter::new()` or `Filter::default()` to count all events.
173    fn count(&self, filter: Filter) -> BoxedFuture<Result<usize, DatabaseError>>;
174
175    /// Query stored events.
176    fn query(&self, filter: Filter) -> BoxedFuture<Result<Events, DatabaseError>>;
177
178    /// Get `negentropy` items
179    fn negentropy_items(
180        &self,
181        filter: Filter,
182    ) -> BoxedFuture<Result<Vec<(EventId, Timestamp)>, DatabaseError>> {
183        Box::pin(async move {
184            let events: Events = self.query(filter).await?;
185            Ok(events.into_iter().map(|e| (e.id, e.created_at)).collect())
186        })
187    }
188
189    /// Delete all events that match the [Filter]
190    fn delete(&self, filter: Filter) -> BoxedFuture<Result<(), DatabaseError>>;
191
192    /// Wipe all data
193    fn wipe(&self) -> BoxedFuture<Result<(), DatabaseError>>;
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_backend_is_persistent() {
202        assert!(!Backend::Memory.is_persistent());
203        assert!(Backend::RocksDB.is_persistent());
204        assert!(Backend::LMDB.is_persistent());
205        assert!(Backend::SQLite.is_persistent());
206        assert!(Backend::IndexedDB.is_persistent());
207        assert!(Backend::Custom("custom".to_string()).is_persistent());
208    }
209}