aimdb_core/database.rs
1//! Core database implementation
2//!
3//! `AimDb` is the central coordination point for AimDB, managing type-safe
4//! in-memory storage with type-safe records and data 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 a specific record by key
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("sensor.temp", SensorData { temp: 23.5 }).await?;
78 /// # Ok(())
79 /// # }
80 /// ```
81 pub async fn produce<T>(&self, key: impl AsRef<str>, data: T) -> DbResult<()>
82 where
83 T: Send + 'static + Clone + core::fmt::Debug,
84 {
85 self.aimdb.produce(key, data).await
86 }
87
88 /// Subscribes to a record by key
89 ///
90 /// Creates a subscription to the configured buffer for the given record key.
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>("sensor.temp")?;
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>(
111 &self,
112 key: impl AsRef<str>,
113 ) -> DbResult<Box<dyn crate::buffer::BufferReader<T> + Send>>
114 where
115 T: Send + Sync + 'static + Debug + Clone,
116 {
117 self.aimdb.subscribe(key)
118 }
119
120 /// Creates a RuntimeContext for this database
121 ///
122 /// Provides services with access to runtime capabilities (timing, logging) plus the emitter.
123 ///
124 /// # Example
125 /// ```rust,ignore
126 /// # use aimdb_core::Database;
127 /// # #[cfg(feature = "tokio-runtime")]
128 /// # {
129 /// # async fn example<A: aimdb_executor::Runtime + Clone>(db: Database<A>) {
130 /// let ctx = db.context();
131 /// // Pass ctx to services
132 /// # }
133 /// # }
134 /// ```
135 pub fn context(&self) -> RuntimeContext<A>
136 where
137 A: aimdb_executor::Runtime + Clone,
138 {
139 #[cfg(feature = "std")]
140 {
141 RuntimeContext::from_arc(std::sync::Arc::new(self.adapter.clone()))
142 }
143 #[cfg(not(feature = "std"))]
144 {
145 // For no_std, we need a static reference - this would typically be handled
146 // by the caller storing the adapter in a static cell first
147 // For now, we'll document this limitation
148 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.")
149 }
150 }
151}
152
153// Spawn implementation for databases with spawn-capable adapters
154impl<A> Database<A>
155where
156 A: RuntimeAdapter + Spawn,
157{
158 /// Spawns a service on the database's runtime
159 ///
160 /// # Example
161 /// ```rust,ignore
162 /// # use aimdb_core::Database;
163 /// # use aimdb_executor::{Runtime, Spawn};
164 /// # #[cfg(feature = "tokio-runtime")]
165 /// # {
166 /// async fn my_service<R: Runtime>(ctx: aimdb_core::RuntimeContext<R>) -> aimdb_core::DbResult<()> {
167 /// // Service implementation
168 /// Ok(())
169 /// }
170 ///
171 /// # async fn example<A: Runtime + Spawn>(db: Database<A>) -> aimdb_core::DbResult<()> {
172 /// let ctx = db.context();
173 /// db.spawn(async move {
174 /// if let Err(e) = my_service(ctx).await {
175 /// eprintln!("Service error: {:?}", e);
176 /// }
177 /// })?;
178 /// # Ok(())
179 /// # }
180 /// # }
181 /// ```
182 pub fn spawn<F>(&self, future: F) -> DbResult<()>
183 where
184 F: core::future::Future<Output = ()> + Send + 'static,
185 {
186 #[cfg(feature = "tracing")]
187 tracing::debug!("Spawning service on database runtime");
188
189 self.adapter.spawn(future).map_err(DbError::from)?;
190 Ok(())
191 }
192}