unistore 0.1.0

simple storage abstraction that works on wasm and native Targets
Documentation
#[cfg(not(target_arch = "wasm32"))]
mod native;
#[cfg(target_arch = "wasm32")]
mod wasm;

use std::marker::PhantomData;

pub use async_std::sync::Mutex;
use serde::{Serialize, de::DeserializeOwned};

pub trait Key: Serialize + DeserializeOwned {}
impl<T: Serialize + DeserializeOwned> Key for T {}

pub trait Value: Serialize + DeserializeOwned {}
impl<T: Serialize + DeserializeOwned> Value for T {}

pub struct UniStore {
    #[cfg(target_arch = "wasm32")]
    db: wasm::Database,
    #[cfg(not(target_arch = "wasm32"))]
    db: native::Database,
    name: String,
}
impl std::fmt::Debug for UniStore {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("UniStore")
            .field("name", &self.name)
            .finish_non_exhaustive()
    }
}

pub struct UniTable<'a, K: Key, V: Value> {
    store: &'a UniStore,
    name: String,
    #[cfg(not(target_arch = "wasm32"))]
    table: native::Table,
    phantom: PhantomData<(K, V)>,
}
impl<K: Key, V: Value> std::fmt::Debug for UniTable<'_, K, V> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("UniTable")
            .field("name", &self.name)
            .finish_non_exhaustive()
    }
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[cfg(target_arch = "wasm32")]
    Wasm(#[from] wasm::Error),
    #[cfg(not(target_arch = "wasm32"))]
    Native(#[from] native::Error),
}
impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "UniStore Error: {self:?}")
    }
}

impl UniStore {
    pub async fn new(name: &str) -> Result<Self, Error> {
        #[cfg(target_arch = "wasm32")]
        let db = wasm::create_database(name).await?;
        #[cfg(not(target_arch = "wasm32"))]
        let db = native::create_database(name).await?;
        Ok(UniStore {
            db,
            name: name.to_string(),
        })
    }

    pub async fn create_table<K: Key, V: Value>(
        &self,
        name: &str,
        replace_if_incompatible: bool,
    ) -> Result<UniTable<K, V>, Error> {
        #[cfg(target_arch = "wasm32")]
        let table = wasm::create_table(self, name, replace_if_incompatible).await?;
        #[cfg(not(target_arch = "wasm32"))]
        let table = native::create_table(self, name, replace_if_incompatible).await?;
        Ok(table)
    }
}

impl<K: Key, V: Value> UniTable<'_, K, V> {
    pub async fn insert(&self, key: K, value: V) -> Result<(), Error> {
        #[cfg(target_arch = "wasm32")]
        wasm::insert(self, key, value).await?;
        #[cfg(not(target_arch = "wasm32"))]
        native::insert(self, key, value).await?;
        Ok(())
    }

    pub async fn contains(&self, key: K) -> Result<bool, Error> {
        #[cfg(target_arch = "wasm32")]
        let exists = wasm::contains(self, key).await?;
        #[cfg(not(target_arch = "wasm32"))]
        let exists = native::contains(self, key).await?;
        Ok(exists)
    }

    pub async fn get(&self, key: K) -> Result<Option<V>, Error> {
        #[cfg(target_arch = "wasm32")]
        let value = wasm::get(self, key).await?;
        #[cfg(not(target_arch = "wasm32"))]
        let value = native::get(self, key).await?;
        Ok(value)
    }
}

#[macro_export]
macro_rules! static_table {
    ($fn_name:ident, $name:literal, $key:ty, $val:ty, $get_store: path) => {
        async fn $fn_name() -> &'static $crate::UniTable<'static, $key, $val> {
            use $crate::Mutex;
            static TABLE: std::sync::OnceLock<$crate::UniTable<'static, $key, $val>> =
                std::sync::OnceLock::new();
            static INITIALIZING: Mutex<()> = Mutex::new(());

            if let Some(table) = TABLE.get() {
                return table;
            }
            let _lock = INITIALIZING.lock().await;
            if let Some(table) = TABLE.get() {
                return table;
            }
            let store = get_store().await;
            let table = store
                .create_table::<$key, $val>($name, true)
                .await
                .expect("Failed to create table");
            TABLE.set(table).expect("Failed to set table");
            TABLE.get().unwrap()
        }
    };
}

#[macro_export]
macro_rules! static_store {
    ($fn_name:ident, $name:literal) => {
        async fn $fn_name() -> &'static $crate::UniStore {
            use $crate::Mutex;
            static STORE: std::sync::OnceLock<$crate::UniStore> = std::sync::OnceLock::new();
            static INITIALIZING: Mutex<()> = Mutex::new(());

            if let Some(store) = STORE.get() {
                return store;
            }
            let _lock = INITIALIZING.lock().await;
            if let Some(store) = STORE.get() {
                return store;
            }
            let store = UniStore::new($name).await.expect("Failed to create store");
            STORE.set(store).expect("Failed to set store");
            STORE.get().unwrap()
        }
    };
}