#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod command;
mod error;
mod manager;
mod pinia;
mod store;
pub use error::{Error, Result};
pub use manager::ManagerExt;
pub use pinia::Pinia;
pub use serde_json::Value as Json;
use std::path::{Path, PathBuf};
pub use store::{Store, StoreState};
use tauri::plugin::TauriPlugin;
use tauri::{Manager, RunEvent, Runtime};
#[cfg(feature = "async-pinia")]
use {std::future::Future, std::pin::Pin, std::time::Duration, tauri::async_runtime};
#[cfg(feature = "ahash")]
use ahash::{HashMap, HashMapExt, HashSet};
#[cfg(not(feature = "ahash"))]
use std::collections::{HashMap, HashSet};
#[cfg(feature = "async-pinia")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-pinia")))]
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[derive(Default)]
pub struct Builder {
path: Option<PathBuf>,
sync_denylist: HashSet<String>,
#[cfg(feature = "async-pinia")]
autosave: Option<Duration>,
}
impl Builder {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn path(mut self, path: impl AsRef<Path>) -> Self {
let path = path.as_ref().to_path_buf();
self.path = Some(path);
self
}
#[must_use]
pub fn sync_denylist(mut self, denylist: &[&str]) -> Self {
self
.sync_denylist
.extend(denylist.iter().map(ToString::to_string));
self
}
#[cfg(feature = "async-pinia")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-pinia")))]
#[must_use]
pub fn autosave(mut self, interval: Duration) -> Self {
self.autosave = Some(interval);
self
}
pub fn build<R: Runtime>(mut self) -> TauriPlugin<R> {
tauri::plugin::Builder::new("pinia")
.invoke_handler(tauri::generate_handler![
command::load,
command::patch,
command::save,
command::save_all,
command::unload
])
.setup(move |app, _| {
let path = self.path.take().unwrap_or_else(|| {
app
.path()
.app_data_dir()
.expect("failed to resolve app data dir")
.join("pinia")
});
#[cfg(feature = "tracing")]
tracing::trace!("pinia path: {}", path.display());
app.manage(Pinia::<R> {
path,
sync_denylist: self.sync_denylist,
#[cfg(not(feature = "async-pinia"))]
stores: std::sync::Mutex::new(HashMap::new()),
#[cfg(feature = "async-pinia")]
stores: tokio::sync::Mutex::new(HashMap::new()),
#[cfg(feature = "async-pinia")]
autosave: std::sync::Mutex::new(None),
});
#[cfg(feature = "async-pinia")]
if let Some(duration) = self.autosave {
app.pinia().set_autosave(app, duration);
};
Ok(())
})
.on_event(|app, event| {
if let RunEvent::Exit = event {
#[cfg(not(feature = "async-pinia"))]
app.pinia().save_all();
#[cfg(feature = "async-pinia")]
async_runtime::block_on(app.pinia().save_all());
}
})
.build()
}
}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::default().build()
}
#[cfg(not(feature = "async-pinia"))]
pub fn with_store<R, M, F, T>(manager: &M, id: impl AsRef<str>, f: F) -> Result<T>
where
R: Runtime,
M: Manager<R> + ManagerExt<R>,
F: FnOnce(&mut Store<R>) -> Result<T>,
{
manager.with_store(id, f)
}
#[cfg(feature = "async-pinia")]
pub async fn with_store<R, M, F, T>(manager: &M, id: impl AsRef<str>, f: F) -> Result<T>
where
R: Runtime,
M: Manager<R> + ManagerExt<R>,
F: FnOnce(&mut Store<R>) -> BoxFuture<Result<T>> + Send + 'static,
T: Send + 'static,
{
manager.with_store(id, f).await
}
#[cfg(feature = "async-pinia")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-pinia")))]
pub trait FutureExt: Future {
fn boxed<'a>(self) -> BoxFuture<'a, Self::Output>
where
Self: Sized + Send + 'a,
{
Box::pin(self)
}
}
#[cfg(feature = "async-pinia")]
impl<T> FutureExt for T where T: ?Sized + Future {}