Skip to main content

unistore/
lib.rs

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}