Skip to main content

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#![forbid(unsafe_code)]
8#![warn(missing_docs)]
9#![warn(rustdoc::bare_urls)]
10#![warn(clippy::large_futures)]
11#![cfg_attr(bench, feature(test))]
12
13#[cfg(bench)]
14extern crate test;
15
16use std::any::Any;
17use std::collections::BTreeSet;
18use std::fmt::Debug;
19use std::future::Future;
20use std::pin::Pin;
21use std::sync::Arc;
22
23use nostr::event::{Event, EventId};
24use nostr::filter::Filter;
25use nostr::types::Timestamp;
26
27pub mod error;
28pub mod prelude;
29
30use self::error::Error;
31
32/// Backend features
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub struct Features {
35    /// Whether the database supports persistent storage.
36    pub persistent: bool,
37    /// Whether the database supports event expiration (NIP-40)
38    ///
39    /// When supported, the database will automatically exclude expired events
40    /// from query results and/or delete them.
41    ///
42    /// <https://github.com/nostr-protocol/nips/blob/master/40.md>
43    pub event_expiration: bool,
44    /// Whether the database supports full-text search (NIP-50)
45    ///
46    /// <https://github.com/nostr-protocol/nips/blob/master/50.md>
47    pub full_text_search: bool,
48    /// Whether the database supports the request to vanish (NIP-62)
49    ///
50    /// <https://github.com/nostr-protocol/nips/blob/master/62.md>
51    pub request_to_vanish: bool,
52}
53
54/// Database event status
55#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
56pub enum DatabaseEventStatus {
57    /// The event is saved into the database
58    Saved,
59    /// The event is marked as deleted
60    Deleted,
61    /// The event doesn't exist
62    NotExistent,
63}
64
65/// Reason why event wasn't stored into the database
66#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
67pub enum RejectedReason {
68    /// Ephemeral events aren't expected to be stored
69    Ephemeral,
70    /// The event already exists
71    Duplicate,
72    /// The event was deleted
73    Deleted,
74    /// The event is expired
75    Expired,
76    /// The event was replaced
77    Replaced,
78    /// Attempt to delete a non-owned event
79    InvalidDelete,
80    /// The event author vanished before
81    Vanished,
82    /// Other reason
83    Other,
84}
85
86/// Save event status
87#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
88pub enum SaveEventStatus {
89    /// The event has been successfully saved
90    Success,
91    /// The event has been rejected
92    Rejected(RejectedReason),
93}
94
95impl SaveEventStatus {
96    /// Check if event is successfully saved
97    #[inline]
98    pub fn is_success(&self) -> bool {
99        matches!(self, Self::Success)
100    }
101}
102
103#[doc(hidden)]
104pub trait IntoNostrDatabase {
105    fn into_nostr_database(self) -> Arc<dyn NostrDatabase>;
106}
107
108impl IntoNostrDatabase for Arc<dyn NostrDatabase> {
109    fn into_nostr_database(self) -> Arc<dyn NostrDatabase> {
110        self
111    }
112}
113
114impl<T> IntoNostrDatabase for T
115where
116    T: NostrDatabase + Sized + 'static,
117{
118    fn into_nostr_database(self) -> Arc<dyn NostrDatabase> {
119        Arc::new(self)
120    }
121}
122
123impl<T> IntoNostrDatabase for Arc<T>
124where
125    T: NostrDatabase + 'static,
126{
127    fn into_nostr_database(self) -> Arc<dyn NostrDatabase> {
128        self
129    }
130}
131
132/// Nostr (Events) Database
133pub trait NostrDatabase: Any + Debug + Send + Sync {
134    /// Name of the backend database used
135    fn backend(&self) -> &'static str;
136
137    /// Get backend features
138    fn features(&self) -> Features;
139
140    /// Save [`Event`] into store
141    ///
142    /// **This method assumes that [`Event`] was already verified**
143    fn save_event<'a>(
144        &'a self,
145        event: &'a Event,
146    ) -> Pin<Box<dyn Future<Output = Result<SaveEventStatus, Error>> + Send + 'a>>;
147
148    /// Check event status by ID
149    ///
150    /// Check if the event is saved, deleted or not existent.
151    fn check_id<'a>(
152        &'a self,
153        event_id: &'a EventId,
154    ) -> Pin<Box<dyn Future<Output = Result<DatabaseEventStatus, Error>> + Send + 'a>>;
155
156    /// Get [`Event`] by [`EventId`]
157    fn event_by_id<'a>(
158        &'a self,
159        event_id: &'a EventId,
160    ) -> Pin<Box<dyn Future<Output = Result<Option<Event>, Error>> + Send + 'a>>;
161
162    /// Count the number of events found with [`Filter`].
163    ///
164    /// Use `Filter::new()` or `Filter::default()` to count all events.
165    fn count(
166        &self,
167        filter: Filter,
168    ) -> Pin<Box<dyn Future<Output = Result<usize, Error>> + Send + '_>>;
169
170    /// Query stored events.
171    fn query(
172        &self,
173        filter: Filter,
174    ) -> Pin<Box<dyn Future<Output = Result<BTreeSet<Event>, Error>> + Send + '_>>;
175
176    /// Get `negentropy` items
177    #[allow(clippy::type_complexity)]
178    fn negentropy_items(
179        &self,
180        filter: Filter,
181    ) -> Pin<Box<dyn Future<Output = Result<Vec<(EventId, Timestamp)>, Error>> + Send + '_>> {
182        Box::pin(async move {
183            let events: BTreeSet<Event> = self.query(filter).await?;
184            Ok(events.into_iter().map(|e| (e.id, e.created_at)).collect())
185        })
186    }
187
188    /// Delete all events that match the [`Filter`]
189    fn delete(
190        &self,
191        filter: Filter,
192    ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + '_>>;
193
194    /// Wipe all data
195    fn wipe(&self) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + '_>>;
196}