nitrite 0.4.0

An embedded NoSQL document database for Rust with collections, repositories, indexing, and ACID transactions
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
use crate::collection::CollectionEventCallback;
use crate::errors::NitriteResult;
use crate::nitrite_config::NitriteConfig;
use anyhow::Error;
use basu::error::BasuError;
use basu::event::Event;
use basu::Handle;
use std::fmt::Debug;
use std::sync::Arc;

/// Enumeration of lifecycle events that occur at the store level.
///
/// # Purpose
///
/// `StoreEvents` represents important lifecycle state transitions in a Nitrite database store.
/// These events allow applications to react to store-level operations like opening, committing data,
/// or closing the store.
///
/// # Variants
///
/// - **Open**: Fired when the store is successfully opened or created
/// - **Commit**: Fired when a transaction is committed to persistent storage
/// - **Closing**: Fired when the store is about to be closed (before close completes)
/// - **Closed**: Fired after the store has been fully closed
///
/// # Characteristics
///
/// - **Debug**: Can be formatted for logging
/// - **PartialEq**: Can be compared for equality
/// - **Clone**: Can be cloned cheaply (enum values are small)
/// - **Send + Sync**: Safe for concurrent access and thread-safe event handling
///
/// # Usage
///
/// Events are passed to registered `StoreEventListener` instances to enable applications
/// to respond to database lifecycle changes:
/// ```ignore
/// let listener = StoreEventListener::new(|info| {
///     match info.event() {
///         StoreEvents::Open => println!("Database opened"),
///         StoreEvents::Commit => println!("Data committed"),
///         StoreEvents::Closing => println!("Database closing"),
///         StoreEvents::Closed => println!("Database closed"),
///     }
///     Ok(())
/// });
/// store.subscribe(listener)?;
/// ```
#[derive(Debug, PartialEq, Clone)]
pub enum StoreEvents {
    Open,
    Commit,
    Closing,
    Closed,
}

/// Context information provided with each store event.
///
/// # Purpose
///
/// `StoreEventInfo` bundles the event type with the database configuration at the time the event
/// occurred. This allows event handlers to access both what event happened and the current state
/// of the database.
///
/// # Characteristics
///
/// - **Cloneable**: Can be shared and cloned efficiently
/// - **Debug**: Can be formatted for logging and debugging
/// - **Thread-Safe**: Safe to pass to event handlers in parallel processing
///
/// # Fields
///
/// - **event**: The store event that occurred (`StoreEvents` enum value)
/// - **nitrite_config**: The database configuration at the time of the event (internal only via `nitrite_config()`)
///
/// # Examples
///
/// From nitrite source:
/// ```ignore
/// let listener = StoreEventListener::new(|info| {
///     let event = info.event();
///     match event {
///         StoreEvents::Open => {
///             println!("Store opened!");
///             // Access configuration if needed (internal API)
///         },
///         _ => {}
///     }
///     Ok(())
/// });
/// ```
#[derive(Clone)]
pub struct StoreEventInfo {
    event: StoreEvents,
    nitrite_config: NitriteConfig,
}

impl StoreEventInfo {
    /// Creates a new store event context with the given event and configuration.
    ///
    /// # Arguments
    ///
    /// * `event` - The store event that occurred
    /// * `nitrite_config` - The database configuration at the time of the event
    ///
    /// # Returns
    ///
    /// A new `StoreEventInfo` instance bundling the event and configuration.
    ///
    /// # Behavior
    ///
    /// - Stores both the event type and configuration for access by event handlers
    /// - The configuration can be retrieved via `nitrite_config()` (internal API)
    /// - Cheap to clone due to Arc-based configuration
    ///
    /// # Examples
    ///
    /// From nitrite source:
    /// ```ignore
    /// let config = NitriteConfig::default();
    /// let info = StoreEventInfo::new(StoreEvents::Open, config);
    /// ```
    pub fn new(event: StoreEvents, nitrite_config: NitriteConfig) -> Self {
        StoreEventInfo {
            event,
            nitrite_config,
        }
    }

    /// Returns a clone of the event that occurred.
    ///
    /// # Returns
    ///
    /// A `StoreEvents` enum value indicating which event occurred.
    ///
    /// # Examples
    ///
    /// From nitrite tests:
    /// ```ignore
    /// let info = StoreEventInfo::new(StoreEvents::Commit, config);
    /// assert_eq!(info.event(), StoreEvents::Commit);
    /// ```
    pub fn event(&self) -> StoreEvents {
        self.event.clone()
    }
    
    /// Returns a reference to the database configuration (internal API).
    ///
    /// # Returns
    ///
    /// A reference to the `NitriteConfig` at the time of the event.
    ///
    /// # Behavior
    ///
    /// - This is an internal API; not typically used by application code
    /// - Provides access to database configuration if needed by event handlers
    pub(crate) fn nitrite_config(&self) -> &NitriteConfig {
        &self.nitrite_config
    }
}

impl Debug for StoreEventInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("StoreEventInfo")
            .field("event", &self.event)
            .finish()
    }
}

/// A trait for closures that handle store events.
///
/// # Purpose
///
/// `StoreEventCallback` defines the interface for any callable that processes store events.
/// It requires the closure to be `Send + Sync` for safe usage in a parallel event processing
/// context, and to return `NitriteResult<()>` to indicate success or error.
///
/// # Characteristics
///
/// - **Callable**: Implements `Fn(StoreEventInfo) -> NitriteResult<()>`
/// - **Thread-Safe**: Requires `Send + Sync` bounds
/// - **Zero-Cost Abstraction**: Automatically implemented for functions and closures
/// - **Async-Friendly**: Processed in Rayon's parallel thread pool
///
/// # Implementations
///
/// Automatically implemented for any function or closure that:
/// - Takes a `StoreEventInfo` parameter
/// - Returns `NitriteResult<()>`
/// - Is `Send + Sync` (safe to share across threads)
///
/// # Examples
///
/// From nitrite source:
/// ```ignore
/// // Simple closure handler
/// StoreEventListener::new(|info| {
///     println!("Event: {:?}", info.event());
///     Ok(())
/// });
///
/// // Closure capturing variables
/// let counter = Arc::new(AtomicUsize::new(0));
/// let counter_clone = counter.clone();
/// StoreEventListener::new(move |_| {
///     counter_clone.fetch_add(1, Ordering::Relaxed);
///     Ok(())
/// });
/// ```
pub trait StoreEventCallback: Send + Sync + Fn(StoreEventInfo) -> NitriteResult<()> {}

impl<F> StoreEventCallback for F
where
    F: Send + Sync + Fn(StoreEventInfo) -> NitriteResult<()>,
{
}

/// A listener for store-level events that wraps a callback function.
///
/// # Purpose
///
/// `StoreEventListener` packages a closure/function into a reusable listener that can be
/// registered with a store to receive notifications when database lifecycle events occur.
/// The listener is thread-safe and can be cloned for concurrent sharing.
///
/// # Characteristics
///
/// - **Callback-Based**: Wraps any `StoreEventCallback` implementation (closure or function)
/// - **Thread-Safe**: Uses `Arc<dyn StoreEventCallback>` for safe concurrent access
/// - **Cloneable**: Cloning is cheap (only increments Arc reference count)
/// - **Async-Processing**: Events are handled in Rayon's parallel thread pool
/// - **Handle Trait**: Implements the `Handle<StoreEventInfo>` interface for event processing
///
/// # Relationship to Related Types
///
/// - `StoreEventCallback`: The trait that event handlers implement
/// - `StoreEventInfo`: The event context passed to handlers
/// - `StoreEvents`: The enum of actual event types
///
/// # Examples
///
/// From nitrite source and tests:
/// ```ignore
/// // Create a listener with a simple closure
/// let listener = StoreEventListener::new(|info| {
///     match info.event() {
///         StoreEvents::Open => println!("Database opened"),
///         StoreEvents::Commit => println!("Transaction committed"),
///         StoreEvents::Closing => println!("Database closing"),
///         StoreEvents::Closed => println!("Database closed"),
///     }
///     Ok(())
/// });
///
/// // Register with store
/// store.subscribe(listener)?;
///
/// // Listener can be cloned cheaply for sharing
/// let listener2 = listener.clone();
/// ```
#[derive(Clone)]
pub struct StoreEventListener {
    on_event: Arc<dyn StoreEventCallback>,
}

impl StoreEventListener {
    /// Creates a new store event listener with the given callback.
    ///
    /// # Arguments
    ///
    /// * `on_event` - A closure or function implementing `StoreEventCallback` that handles events
    ///
    /// # Returns
    ///
    /// A new `StoreEventListener` ready to be registered with a store.
    ///
    /// # Behavior
    ///
    /// - Wraps the callback in an `Arc` for thread-safe sharing
    /// - The callback will be invoked for each event when the listener is registered
    /// - Callbacks are processed in Rayon's parallel thread pool
    /// - Multiple listeners can be registered with the same store
    /// - The listener can be cloned cheaply and registered with multiple stores
    ///
    /// # Type Constraints
    ///
    /// The `on_event` parameter must:
    /// - Take a single `StoreEventInfo` parameter
    /// - Return `NitriteResult<()>`
    /// - Be `Send + Sync` for safe concurrent execution
    /// - Be `'static` (own all captured data)
    ///
    /// # Examples
    ///
    /// From nitrite source and tests:
    /// ```ignore
    /// // Simple listener
    /// let listener = StoreEventListener::new(|_| Ok(()));
    /// store.subscribe(listener)?;
    ///
    /// // Listener with pattern matching
    /// let listener = StoreEventListener::new(|info| {
    ///     match info.event() {
    ///         StoreEvents::Open => println!("Database opened"),
    ///         _ => {}
    ///     }
    ///     Ok(())
    /// });
    ///
    /// // Listener capturing variables
    /// let counter = Arc::new(AtomicUsize::new(0));
    /// let counter_clone = counter.clone();
    /// let listener = StoreEventListener::new(move |_| {
    ///     counter_clone.fetch_add(1, Ordering::Relaxed);
    ///     Ok(())
    /// });
    /// ```
    pub fn new(on_event: impl StoreEventCallback + 'static) -> Self {
        StoreEventListener {
            on_event: Arc::new(on_event),
        }
    }
}

impl Handle<StoreEventInfo> for StoreEventListener {
    fn handle(&self, event: &Event<StoreEventInfo>) -> Result<(), BasuError> {
        // below code will run in rayon's thread pool using parallel iterator
        match (self.on_event)(event.data.clone()) {
            Ok(_) => Ok(()),
            Err(e) => Err(BasuError::HandlerError(Error::from(e))),
        }
    }
}

impl Debug for StoreEventListener {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("StoreEventListener")
            .finish()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::errors::{ErrorKind, NitriteError};
    use crate::nitrite_config::NitriteConfig;
    use basu::event::Event;
    use std::sync::Arc;

    #[test]
    fn test_store_event_listener_new() {
        let listener = StoreEventListener::new(|_| Ok(()));

        assert!(Arc::strong_count(&listener.on_event) > 0);
    }

    #[test]
    fn test_store_event_listener_handle_success() {
        let listener = StoreEventListener::new(|_| Ok(()));

        let nitrite_config = NitriteConfig::default();
        let store_event_info = StoreEventInfo::new(StoreEvents::Open, nitrite_config);
        let event = Event::new(store_event_info);

        assert!(listener.handle(&event).is_ok());
    }

    #[test]
    fn test_store_event_listener_handle_failure() {
        let listener = StoreEventListener::new(|_| {
            Err(NitriteError::new("Test error", ErrorKind::InvalidOperation))
        });

        let nitrite_config = NitriteConfig::default();
        let store_event_info = StoreEventInfo::new(StoreEvents::Open, nitrite_config);
        let event = Event::new(store_event_info);

        assert!(listener.handle(&event).is_err());
    }

    #[test]
    fn test_store_event_info_new() {
        let nitrite_config = NitriteConfig::default();
        let store_event_info = StoreEventInfo::new(StoreEvents::Commit, nitrite_config.clone());

        assert_eq!(store_event_info.event, StoreEvents::Commit);
    }

    #[test]
    fn test_store_event_info_debug() {
        let nitrite_config = NitriteConfig::default();
        let store_event_info = StoreEventInfo::new(StoreEvents::Closing, nitrite_config);

        let debug_str = format!("{:?}", store_event_info);
        assert!(debug_str.contains("StoreEventInfo"));
    }

    #[test]
    fn test_store_event_listener_debug() {
        let listener = StoreEventListener::new(|_| Ok(()));

        let debug_str = format!("{:?}", listener);
        assert!(debug_str.contains("StoreEventListener"));
    }

    #[test]
    fn test_store_event_listener_clone_efficiency() {
        // Test that listener cloning is efficient with Arc
        let listener = StoreEventListener::new(|_| Ok(()));
        let initial_count = Arc::strong_count(&listener.on_event);
        
        let listener2 = listener.clone();
        let new_count = Arc::strong_count(&listener2.on_event);
        
        // Clone should increment Arc count, not copy the callback
        assert_eq!(new_count, initial_count + 1);
    }

    #[test]
    fn test_store_event_info_clone_efficiency() {
        // Test that event info cloning with config is efficient
        let config1 = NitriteConfig::default();
        let info1 = StoreEventInfo::new(StoreEvents::Commit, config1);
        
        let info2 = info1.clone();
        assert_eq!(info2.event, StoreEvents::Commit);
    }

    #[test]
    fn test_multiple_listeners_efficiency() {
        // Test that multiple listeners can be created efficiently
        let listeners: Vec<_> = (0..10)
            .map(|_| StoreEventListener::new(|_| Ok(())))
            .collect();
        
        assert_eq!(listeners.len(), 10);
    }

    #[test]
    fn test_store_event_info_events_immutable() {
        // Test that StoreEventInfo events are immutable and efficiently accessed
        let config = NitriteConfig::default();
        let events = vec![
            StoreEvents::Open,
            StoreEvents::Commit,
            StoreEvents::Closing,
            StoreEvents::Closed,
        ];
        
        for event in events {
            let info = StoreEventInfo::new(event.clone(), config.clone());
            assert_eq!(info.event(), event);
        }
    }

    #[test]
    fn test_handle_with_multiple_events() {
        // Test handling multiple events efficiently
        let listener = StoreEventListener::new(|_| Ok(()));
        
        let events = vec![
            StoreEvents::Open,
            StoreEvents::Commit,
            StoreEvents::Closing,
        ];
        
        let config = NitriteConfig::default();
        for event in events {
            let info = StoreEventInfo::new(event, config.clone());
            let event_wrapper = Event::new(info);
            assert!(listener.handle(&event_wrapper).is_ok());
        }
    }

    #[test]
    fn test_listener_callback_capture_efficiency() {
        // Test that listener callback is efficiently captured in Arc
        let counter = Arc::new(std::sync::atomic::AtomicUsize::new(0));
        let counter_clone = counter.clone();
        
        let listener = StoreEventListener::new(move |_| {
            counter_clone.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
            Ok(())
        });
        
        let config = NitriteConfig::default();
        let info = StoreEventInfo::new(StoreEvents::Open, config);
        let event = Event::new(info);
        
        listener.handle(&event).unwrap();
        assert_eq!(counter.load(std::sync::atomic::Ordering::Relaxed), 1);
    }
}