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}