1mod index;
2mod item;
3mod key;
4#[cfg(not(target_arch = "wasm32"))]
5mod native;
6#[cfg(target_arch = "wasm32")]
7mod wasm;
8pub use index::UniIndex;
9pub use item::UniStoreItem;
10pub use key::Key;
11#[cfg(test)]
12mod tests;
13
14use std::marker::PhantomData;
15
16pub use async_std::sync::Mutex;
17use serde::{Serialize, de::DeserializeOwned};
18pub use unistore_derive::UniStoreItem;
19
20pub trait Value: Serialize + DeserializeOwned {}
21impl<T: Serialize + DeserializeOwned> Value for T {}
22
23pub struct UniStore {
24 #[cfg(target_arch = "wasm32")]
25 db: wasm::Database,
26 #[cfg(not(target_arch = "wasm32"))]
27 db: native::Database,
28 name: String,
29}
30impl std::fmt::Debug for UniStore {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 f.debug_struct("UniStore")
33 .field("name", &self.name)
34 .finish_non_exhaustive()
35 }
36}
37
38pub struct UniTable<'a, K: Key, V: Value> {
39 store: &'a UniStore,
40 name: String,
41 #[cfg(not(target_arch = "wasm32"))]
42 table: native::Table,
43 phantom: PhantomData<(K, V)>,
44}
45impl<K: Key, V: Value> std::fmt::Debug for UniTable<'_, K, V> {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.debug_struct("UniTable")
48 .field("name", &self.name)
49 .finish_non_exhaustive()
50 }
51}
52
53pub trait AsKey<K: Key> {
54 fn as_key(self) -> K;
55}
56impl<K: Key> AsKey<K> for K {
57 fn as_key(self) -> K {
58 self
59 }
60}
61impl<K: Key + Copy> AsKey<K> for &K {
62 fn as_key(self) -> K {
63 *self
64 }
65}
66impl AsKey<String> for &str {
67 fn as_key(self) -> String {
68 self.to_string()
69 }
70}
71impl AsKey<String> for bool {
72 fn as_key(self) -> String {
73 if self {
74 "1".to_string()
75 } else {
76 "0".to_string()
77 }
78 }
79}
80
81pub trait AsValue<V: Value>: Serialize {}
82impl<V: Value> AsValue<V> for V {}
83impl<V: Value> AsValue<V> for &V {}
84impl AsValue<String> for &str {}
85
86#[derive(thiserror::Error, Debug)]
87pub enum Error {
88 #[cfg(target_arch = "wasm32")]
89 #[error("Error in WebAssembly implementation: {0}")]
90 Wasm(String),
91 #[cfg(not(target_arch = "wasm32"))]
92 #[error("Error in native implementation: {0}")]
93 Native(#[from] native::Error),
94 #[error("Missind index for {0}")]
95 MissingIndex(&'static str),
96 #[error("Key type mismatch: {0}")]
97 KeyTypeMismatch(String),
98 #[error("Table already exists with different Value type")]
99 ValueTypeMismatch(String),
100}
101
102#[cfg(target_arch = "wasm32")]
103impl From<wasm::Error> for Error {
104 fn from(value: wasm::Error) -> Self {
105 Error::Wasm(value.to_string())
106 }
107}
108
109impl UniStore {
110 pub async fn new(
111 qualifier: &str,
112 organization: &str,
113 application: &str,
114 ) -> Result<Self, Error> {
115 let name = format!("{qualifier}.{organization}.{application}");
116 #[cfg(target_arch = "wasm32")]
117 let db = wasm::create_database(&name).await?;
118 #[cfg(not(target_arch = "wasm32"))]
119 let db = native::create_database(qualifier, organization, application).await?;
120 Ok(UniStore { db, name })
121 }
122
123 pub async fn create_table<K: Key, V: Value>(
124 &self,
125 name: &str,
126 replace_if_incompatible: bool,
127 ) -> Result<UniTable<K, V>, Error> {
128 #[cfg(target_arch = "wasm32")]
129 let table = wasm::create_table(self, name, replace_if_incompatible).await?;
130 #[cfg(not(target_arch = "wasm32"))]
131 let table = native::create_table(self, name, replace_if_incompatible).await?;
132 Ok(table)
133 }
134}
135
136impl<K: Key, V: Value> UniTable<'_, K, V> {
137 pub async fn insert(&self, key: impl AsKey<K>, value: impl AsValue<V>) -> Result<(), Error> {
138 #[cfg(target_arch = "wasm32")]
139 wasm::insert(self, key, value).await?;
140 #[cfg(not(target_arch = "wasm32"))]
141 native::insert(self, key, value).await?;
142 Ok(())
143 }
144
145 pub async fn contains(&self, key: impl AsKey<K>) -> Result<bool, Error> {
146 #[cfg(target_arch = "wasm32")]
147 let exists = wasm::contains(self, key).await?;
148 #[cfg(not(target_arch = "wasm32"))]
149 let exists = native::contains(self, key).await?;
150 Ok(exists)
151 }
152
153 pub async fn get(&self, key: impl AsKey<K>) -> Result<Option<V>, Error> {
154 #[cfg(target_arch = "wasm32")]
155 let value = wasm::get(self, key).await?;
156 #[cfg(not(target_arch = "wasm32"))]
157 let value = native::get(self, key).await?;
158 Ok(value)
159 }
160
161 pub async fn remove(&self, key: impl AsKey<K>) -> Result<(), Error> {
162 #[cfg(target_arch = "wasm32")]
163 wasm::remove(self, key).await?;
164 #[cfg(not(target_arch = "wasm32"))]
165 native::remove(self, key).await?;
166 Ok(())
167 }
168
169 pub async fn len(&self) -> Result<usize, Error> {
170 #[cfg(target_arch = "wasm32")]
171 let count = wasm::len(self).await?;
172 #[cfg(not(target_arch = "wasm32"))]
173 let count = native::len(self).await?;
174 Ok(count)
175 }
176
177 pub async fn is_empty(&self) -> Result<bool, Error> {
178 #[cfg(target_arch = "wasm32")]
179 let empty = wasm::is_empty(self).await?;
180 #[cfg(not(target_arch = "wasm32"))]
181 let empty = native::is_empty(self).await?;
182 Ok(empty)
183 }
184
185 pub async fn get_prefix(&self, prefix: impl AsKey<K>) -> Result<Vec<(K, V)>, Error> {
186 #[cfg(target_arch = "wasm32")]
187 let values = wasm::get_prefix(self, prefix).await?;
188 #[cfg(not(target_arch = "wasm32"))]
189 let values = native::get_prefix(self, prefix).await?;
190 Ok(values)
191 }
192}
193
194#[macro_export]
195macro_rules! static_table {
196 ($fn_name:ident, $name:literal, $key:ty, $val:ty, $get_store: path) => {
197 async fn $fn_name() -> &'static $crate::UniTable<'static, $key, $val> {
198 use $crate::Mutex;
199 static TABLE: std::sync::OnceLock<$crate::UniTable<'static, $key, $val>> =
200 std::sync::OnceLock::new();
201 static INITIALIZING: Mutex<()> = Mutex::new(());
202
203 if let Some(table) = TABLE.get() {
204 return table;
205 }
206 let _lock = INITIALIZING.lock().await;
207 if let Some(table) = TABLE.get() {
208 return table;
209 }
210 let store = $get_store().await;
211 let table = store
212 .create_table::<$key, $val>($name, true)
213 .await
214 .expect("Failed to create table");
215 TABLE.set(table).expect("Failed to set table");
216 TABLE.get().unwrap()
217 }
218 };
219}
220
221#[macro_export]
222macro_rules! static_store {
223 ($fn_name:ident, $qualifier:literal, $organization:literal, $application:literal) => {
224 async fn $fn_name() -> &'static $crate::UniStore {
225 use $crate::Mutex;
226 static STORE: std::sync::OnceLock<$crate::UniStore> = std::sync::OnceLock::new();
227 static INITIALIZING: Mutex<()> = Mutex::new(());
228
229 if let Some(store) = STORE.get() {
230 return store;
231 }
232 let _lock = INITIALIZING.lock().await;
233 if let Some(store) = STORE.get() {
234 return store;
235 }
236 let store = $crate::UniStore::new($qualifier, $organization, $application)
237 .await
238 .expect("Failed to create store");
239 STORE.set(store).expect("Failed to set store");
240 STORE.get().unwrap()
241 }
242 };
243}