nostr_database/
memory.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//! Memory (RAM) Storage backend for Nostr apps
6
7use std::num::NonZeroUsize;
8use std::sync::Arc;
9
10use lru::LruCache;
11use nostr::prelude::*;
12use tokio::sync::RwLock;
13
14use crate::{
15    Backend, DatabaseError, DatabaseEventResult, DatabaseEventStatus, DatabaseHelper, Events,
16    NostrDatabase, SaveEventStatus,
17};
18
19const MAX_EVENTS: usize = 35_000;
20
21/// Database options
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct MemoryDatabaseOptions {
24    /// Store events (default: false)
25    pub events: bool,
26    /// Max events and IDs to store in memory (default: 35_000)
27    ///
28    /// `None` means no limits.
29    ///
30    /// If `Some(0)` is passed, the default value will be used.
31    pub max_events: Option<usize>,
32}
33
34impl Default for MemoryDatabaseOptions {
35    fn default() -> Self {
36        Self {
37            events: false,
38            max_events: Some(MAX_EVENTS),
39        }
40    }
41}
42
43impl MemoryDatabaseOptions {
44    /// New default database options
45    pub fn new() -> Self {
46        Self::default()
47    }
48}
49
50#[derive(Debug, Clone)]
51enum InnerMemoryDatabase {
52    /// Just an event ID tracker
53    Tracker(Arc<RwLock<LruCache<EventId, ()>>>),
54    /// A full in-memory events store
55    Full(DatabaseHelper),
56}
57
58/// Memory Database (RAM)
59#[derive(Debug, Clone)]
60pub struct MemoryDatabase {
61    inner: InnerMemoryDatabase,
62}
63
64impl Default for MemoryDatabase {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl MemoryDatabase {
71    /// New Memory database with default options
72    pub fn new() -> Self {
73        Self::with_opts(MemoryDatabaseOptions::default())
74    }
75
76    /// New Memory database
77    pub fn with_opts(mut opts: MemoryDatabaseOptions) -> Self {
78        // Check if `Some(0)`
79        if let Some(0) = opts.max_events {
80            opts.max_events = Some(MAX_EVENTS);
81        }
82
83        // Check if event storing is allowed
84        let inner: InnerMemoryDatabase = if opts.events {
85            let helper: DatabaseHelper = match opts.max_events {
86                Some(max) => DatabaseHelper::bounded(max),
87                None => DatabaseHelper::unbounded(),
88            };
89            InnerMemoryDatabase::Full(helper)
90        } else {
91            let cache: LruCache<EventId, ()> = match opts.max_events {
92                Some(max) if max > 0 => {
93                    // SAFETY: checked above if > 0
94                    let max: NonZeroUsize = NonZeroUsize::new(max).unwrap();
95                    LruCache::new(max)
96                }
97                _ => LruCache::unbounded(),
98            };
99            InnerMemoryDatabase::Tracker(Arc::new(RwLock::new(cache)))
100        };
101
102        Self { inner }
103    }
104}
105
106impl NostrDatabase for MemoryDatabase {
107    fn backend(&self) -> Backend {
108        Backend::Memory
109    }
110
111    fn save_event<'a>(
112        &'a self,
113        event: &'a Event,
114    ) -> BoxedFuture<'a, Result<SaveEventStatus, DatabaseError>> {
115        Box::pin(async move {
116            match &self.inner {
117                InnerMemoryDatabase::Tracker(tracker) => {
118                    // Mark it as seen
119                    let mut seen_event_ids = tracker.write().await;
120                    seen_event_ids.put(event.id, ());
121
122                    Ok(SaveEventStatus::Success)
123                }
124                InnerMemoryDatabase::Full(helper) => {
125                    let DatabaseEventResult { status, .. } = helper.index_event(event).await;
126                    Ok(status)
127                }
128            }
129        })
130    }
131
132    fn check_id<'a>(
133        &'a self,
134        event_id: &'a EventId,
135    ) -> BoxedFuture<'a, Result<DatabaseEventStatus, DatabaseError>> {
136        Box::pin(async move {
137            match &self.inner {
138                InnerMemoryDatabase::Tracker(tracker) => {
139                    let seen_event_ids = tracker.read().await;
140
141                    Ok(if seen_event_ids.contains(event_id) {
142                        DatabaseEventStatus::Saved
143                    } else {
144                        DatabaseEventStatus::NotExistent
145                    })
146                }
147                InnerMemoryDatabase::Full(helper) => {
148                    if helper.has_event_id_been_deleted(event_id).await {
149                        Ok(DatabaseEventStatus::Deleted)
150                    } else if helper.has_event(event_id).await {
151                        Ok(DatabaseEventStatus::Saved)
152                    } else {
153                        Ok(DatabaseEventStatus::NotExistent)
154                    }
155                }
156            }
157        })
158    }
159
160    fn event_by_id<'a>(
161        &'a self,
162        event_id: &'a EventId,
163    ) -> BoxedFuture<'a, Result<Option<Event>, DatabaseError>> {
164        Box::pin(async move {
165            match &self.inner {
166                InnerMemoryDatabase::Tracker(..) => Ok(None),
167                InnerMemoryDatabase::Full(helper) => Ok(helper.event_by_id(event_id).await),
168            }
169        })
170    }
171
172    fn count(&self, filter: Filter) -> BoxedFuture<Result<usize, DatabaseError>> {
173        Box::pin(async move {
174            match &self.inner {
175                InnerMemoryDatabase::Tracker(..) => Ok(0),
176                InnerMemoryDatabase::Full(helper) => Ok(helper.count(filter).await),
177            }
178        })
179    }
180
181    fn query(&self, filter: Filter) -> BoxedFuture<Result<Events, DatabaseError>> {
182        Box::pin(async move {
183            match &self.inner {
184                InnerMemoryDatabase::Tracker(..) => Ok(Events::new(&filter)),
185                InnerMemoryDatabase::Full(helper) => Ok(helper.query(filter).await),
186            }
187        })
188    }
189
190    fn negentropy_items(
191        &self,
192        filter: Filter,
193    ) -> BoxedFuture<Result<Vec<(EventId, Timestamp)>, DatabaseError>> {
194        Box::pin(async move {
195            match &self.inner {
196                InnerMemoryDatabase::Tracker(..) => Ok(Vec::new()),
197                InnerMemoryDatabase::Full(helper) => Ok(helper.negentropy_items(filter).await),
198            }
199        })
200    }
201
202    fn delete(&self, filter: Filter) -> BoxedFuture<Result<(), DatabaseError>> {
203        Box::pin(async move {
204            match &self.inner {
205                InnerMemoryDatabase::Tracker(..) => Ok(()),
206                InnerMemoryDatabase::Full(helper) => {
207                    helper.delete(filter).await;
208                    Ok(())
209                }
210            }
211        })
212    }
213
214    fn wipe(&self) -> BoxedFuture<Result<(), DatabaseError>> {
215        Box::pin(async move {
216            match &self.inner {
217                InnerMemoryDatabase::Tracker(tracker) => {
218                    let mut seen_event_ids = tracker.write().await;
219                    seen_event_ids.clear();
220                }
221                InnerMemoryDatabase::Full(helper) => {
222                    helper.clear().await;
223                }
224            }
225
226            Ok(())
227        })
228    }
229}