aimdb_core/
database.rs

1//! AimDB Database Implementation
2//!
3//! This module provides the unified database implementation for AimDB, supporting async
4//! in-memory storage with type-safe records and real-time synchronization across
5//! MCU → edge → cloud environments.
6
7use crate::{AimDb, DbError, DbResult, RuntimeAdapter, RuntimeContext};
8use aimdb_executor::Spawn;
9use core::fmt::Debug;
10
11#[cfg(not(feature = "std"))]
12extern crate alloc;
13
14#[cfg(not(feature = "std"))]
15use alloc::boxed::Box;
16
17#[cfg(feature = "std")]
18use std::boxed::Box;
19
20/// AimDB Database implementation
21///
22/// Unified database combining runtime adapter management with type-safe record
23/// registration and producer-consumer patterns. See `examples/` for usage patterns.
24///
25/// This is a thin wrapper around `AimDb<R>` that adds adapter-specific functionality.
26/// Most users should use `AimDbBuilder` directly to create databases.
27pub struct Database<A: RuntimeAdapter + aimdb_executor::Spawn + 'static> {
28    adapter: A,
29    aimdb: AimDb<A>,
30}
31
32impl<A: RuntimeAdapter + aimdb_executor::Spawn + 'static> Database<A> {
33    /// Internal accessor for the AimDb instance
34    ///
35    /// Used by adapter crates. Should not be used by application code.
36    #[doc(hidden)]
37    pub fn inner_aimdb(&self) -> &AimDb<A> {
38        &self.aimdb
39    }
40
41    /// Creates a new database from adapter and AimDb
42    ///
43    /// # Arguments
44    /// * `adapter` - The runtime adapter
45    /// * `aimdb` - The configured AimDb instance
46    ///
47    /// Most users should use `AimDbBuilder` directly instead of this constructor.
48    pub fn new(adapter: A, aimdb: AimDb<A>) -> DbResult<Self> {
49        #[cfg(feature = "tracing")]
50        tracing::info!("Initializing unified database with typed records");
51
52        Ok(Self { adapter, aimdb })
53    }
54
55    /// Gets a reference to the runtime adapter
56    ///
57    /// # Example
58    /// ```rust,ignore
59    /// # use aimdb_core::Database;
60    /// # #[cfg(feature = "tokio-runtime")]
61    /// # {
62    /// # async fn example<A: aimdb_core::RuntimeAdapter>(db: Database<A>) {
63    /// let adapter = db.adapter();
64    /// // Use adapter directly
65    /// # }
66    /// # }
67    /// ```
68    pub fn adapter(&self) -> &A {
69        &self.adapter
70    }
71
72    /// Produces typed data to the record's producer pipeline
73    ///
74    /// # Example
75    /// ```rust,ignore
76    /// # async fn example<A: aimdb_core::RuntimeAdapter>(db: aimdb_core::Database<A>) -> aimdb_core::DbResult<()> {
77    /// db.produce(SensorData { temp: 23.5 }).await?;
78    /// # Ok(())
79    /// # }
80    /// ```
81    pub async fn produce<T>(&self, data: T) -> DbResult<()>
82    where
83        T: Send + 'static + Clone + core::fmt::Debug,
84    {
85        self.aimdb.produce(data).await
86    }
87
88    /// Subscribes to a record type's buffer
89    ///
90    /// Creates a subscription to the configured buffer for the given record type.
91    /// Returns a boxed reader for receiving values asynchronously.
92    ///
93    /// # Example
94    /// ```rust,ignore
95    /// # async fn example<A: aimdb_core::RuntimeAdapter>(db: aimdb_core::Database<A>) -> aimdb_core::DbResult<()> {
96    /// let mut reader = db.subscribe::<SensorData>()?;
97    ///
98    /// loop {
99    ///     match reader.recv().await {
100    ///         Ok(data) => println!("Received: {:?}", data),
101    ///         Err(e) => {
102    ///             eprintln!("Error: {:?}", e);
103    ///             break;
104    ///         }
105    ///     }
106    /// }
107    /// # Ok(())
108    /// # }
109    /// ```
110    pub fn subscribe<T>(&self) -> DbResult<Box<dyn crate::buffer::BufferReader<T> + Send>>
111    where
112        T: Send + Sync + 'static + Debug + Clone,
113    {
114        // Get the typed record using the helper
115        let typed_record = self.aimdb.inner().get_typed_record::<T, A>()?;
116
117        // Get the buffer
118        #[cfg(feature = "std")]
119        let buffer = typed_record.buffer().ok_or(DbError::InvalidOperation {
120            operation: "subscribe".to_string(),
121            reason: format!(
122                "No buffer configured for record type {}. Use RecordRegistrar::buffer() to configure a buffer.",
123                core::any::type_name::<T>()
124            ),
125        })?;
126
127        #[cfg(not(feature = "std"))]
128        let buffer = typed_record.buffer().ok_or(DbError::InvalidOperation {
129            _operation: (),
130            _reason: (),
131        })?;
132
133        // DynBuffer provides subscribe_boxed() - universal across all runtimes!
134        Ok(buffer.subscribe_boxed())
135    }
136
137    /// Creates a RuntimeContext for this database
138    ///
139    /// Provides services with access to runtime capabilities (timing, logging) plus the emitter.
140    ///
141    /// # Example
142    /// ```rust,ignore
143    /// # use aimdb_core::Database;
144    /// # #[cfg(feature = "tokio-runtime")]
145    /// # {
146    /// # async fn example<A: aimdb_executor::Runtime + Clone>(db: Database<A>) {
147    /// let ctx = db.context();
148    /// // Pass ctx to services
149    /// # }
150    /// # }
151    /// ```
152    pub fn context(&self) -> RuntimeContext<A>
153    where
154        A: aimdb_executor::Runtime + Clone,
155    {
156        #[cfg(feature = "std")]
157        {
158            RuntimeContext::from_arc(std::sync::Arc::new(self.adapter.clone()))
159        }
160        #[cfg(not(feature = "std"))]
161        {
162            // For no_std, we need a static reference - this would typically be handled
163            // by the caller storing the adapter in a static cell first
164            // For now, we'll document this limitation
165            panic!("context() not supported in no_std without a static reference. To use context(), store your adapter in a static cell (e.g., StaticCell from portable-atomic or embassy-sync), or use adapter() directly.")
166        }
167    }
168}
169
170// Spawn implementation for databases with spawn-capable adapters
171impl<A> Database<A>
172where
173    A: RuntimeAdapter + Spawn,
174{
175    /// Spawns a service on the database's runtime
176    ///
177    /// # Example
178    /// ```rust,ignore
179    /// # use aimdb_core::Database;
180    /// # use aimdb_executor::{Runtime, Spawn};
181    /// # #[cfg(feature = "tokio-runtime")]
182    /// # {
183    /// async fn my_service<R: Runtime>(ctx: aimdb_core::RuntimeContext<R>) -> aimdb_core::DbResult<()> {
184    ///     // Service implementation
185    ///     Ok(())
186    /// }
187    ///
188    /// # async fn example<A: Runtime + Spawn>(db: Database<A>) -> aimdb_core::DbResult<()> {
189    /// let ctx = db.context();
190    /// db.spawn(async move {
191    ///     if let Err(e) = my_service(ctx).await {
192    ///         eprintln!("Service error: {:?}", e);
193    ///     }
194    /// })?;
195    /// # Ok(())
196    /// # }
197    /// # }
198    /// ```
199    pub fn spawn<F>(&self, future: F) -> DbResult<()>
200    where
201        F: core::future::Future<Output = ()> + Send + 'static,
202    {
203        #[cfg(feature = "tracing")]
204        tracing::debug!("Spawning service on database runtime");
205
206        self.adapter.spawn(future).map_err(DbError::from)?;
207        Ok(())
208    }
209}