1#![forbid(unsafe_code)]
11#![cfg_attr(docsrs, feature(doc_auto_cfg))]
12#![doc = include_str!("../README.md")]
13#![doc(html_favicon_url = "https://tb.dev.br/tauri-store/favicon.ico")]
14
15mod command;
16mod manager;
17mod vue;
18
19use serde::de::DeserializeOwned;
20use std::collections::{HashMap, HashSet};
21use std::path::{Path, PathBuf};
22use std::time::Duration;
23use tauri::plugin::{PluginApi, TauriPlugin};
24use tauri::{AppHandle, RunEvent, Runtime};
25use tauri_store::Migrator;
26
27pub use manager::ManagerExt;
28pub use tauri_store::prelude::*;
29pub use tauri_store::{Migration, MigrationContext};
30pub use vue::{Vue, VueMarker};
31
32#[cfg(target_os = "ios")]
33tauri::ios_plugin_binding!(init_plugin_vue);
34
35pub struct Builder<R: Runtime> {
37 default_path: Option<PathBuf>,
38 path_table: HashMap<StoreId, Box<Path>>,
39 default_marshaler: Option<Box<dyn Marshaler>>,
40 marshaler_table: HashMap<StoreId, Box<dyn Marshaler>>,
41 default_save_strategy: SaveStrategy,
42 autosave: Option<Duration>,
43 on_load: Option<Box<OnLoadFn<R, VueMarker>>>,
44 save_denylist: HashSet<StoreId>,
45 sync_denylist: HashSet<StoreId>,
46 migrator: Migrator,
47 debug_stores: bool,
48}
49
50impl<R: Runtime> Builder<R> {
51 pub fn new() -> Self {
53 Self::default()
54 }
55
56 #[must_use]
58 pub fn autosave(mut self, interval: Duration) -> Self {
59 self.autosave = Some(interval);
60 self
61 }
62
63 #[must_use]
65 pub fn default_save_strategy(mut self, strategy: SaveStrategy) -> Self {
66 self.default_save_strategy = strategy;
67 self
68 }
69
70 #[must_use]
72 pub fn on_load<F>(mut self, f: F) -> Self
73 where
74 F: Fn(&Store<R, VueMarker>) -> Result<()> + Send + Sync + 'static,
75 {
76 self.on_load = Some(Box::new(f));
77 self
78 }
79
80 #[must_use]
82 pub fn path(mut self, path: impl AsRef<Path>) -> Self {
83 let path = path.as_ref().to_path_buf();
84 self.default_path = Some(path);
85 self
86 }
87
88 #[must_use]
90 pub fn path_of(mut self, id: impl AsRef<str>, path: impl AsRef<Path>) -> Self {
91 let id = StoreId::from(id.as_ref());
92 let path = Box::from(path.as_ref());
93 self.path_table.insert(id, path);
94 self
95 }
96
97 #[must_use]
99 pub fn save_denylist<I, T>(mut self, denylist: I) -> Self
100 where
101 I: IntoIterator<Item = T>,
102 T: AsRef<str>,
103 {
104 self.save_denylist.extend(
105 denylist
106 .into_iter()
107 .map(|it| StoreId::from(it.as_ref())),
108 );
109
110 self
111 }
112
113 #[must_use]
115 pub fn sync_denylist<I, T>(mut self, denylist: I) -> Self
116 where
117 I: IntoIterator<Item = T>,
118 T: AsRef<str>,
119 {
120 self.sync_denylist.extend(
121 denylist
122 .into_iter()
123 .map(|it| StoreId::from(it.as_ref())),
124 );
125
126 self
127 }
128
129 #[must_use]
131 pub fn marshaler(mut self, marshaler: Box<dyn Marshaler>) -> Self {
132 self.default_marshaler = Some(marshaler);
133 self
134 }
135
136 #[must_use]
138 pub fn marshaler_of(mut self, id: impl AsRef<str>, marshaler: Box<dyn Marshaler>) -> Self {
139 let id = StoreId::from(id.as_ref());
140 self.marshaler_table.insert(id, marshaler);
141 self
142 }
143
144 #[must_use]
148 pub fn enable_debug_stores(mut self, yes: bool) -> Self {
149 self.debug_stores = yes;
150 self
151 }
152
153 #[must_use]
155 pub fn migration(mut self, id: impl Into<StoreId>, migration: Migration) -> Self {
156 self.migrator.add_migration(id.into(), migration);
157 self
158 }
159
160 #[must_use]
162 pub fn migrations<I>(mut self, id: impl Into<StoreId>, migrations: I) -> Self
163 where
164 I: IntoIterator<Item = Migration>,
165 {
166 self
167 .migrator
168 .add_migrations(id.into(), migrations);
169
170 self
171 }
172
173 #[must_use]
175 pub fn on_before_each_migration<F>(mut self, f: F) -> Self
176 where
177 F: Fn(MigrationContext) + Send + Sync + 'static,
178 {
179 self.migrator.on_before_each(f);
180 self
181 }
182
183 fn build_collection(self, handle: Handle<R>) -> Result<()> {
184 let mut builder = StoreCollection::<R, VueMarker>::builder()
185 .default_save_strategy(self.default_save_strategy)
186 .save_denylist(&self.save_denylist)
187 .sync_denylist(&self.sync_denylist)
188 .migrator(self.migrator)
189 .enable_debug_stores(self.debug_stores);
190
191 if let Some(path) = self.default_path {
192 builder = builder.path(path);
193 }
194
195 if let Some(on_load) = self.on_load {
196 builder = builder.on_load(on_load);
197 }
198
199 if let Some(duration) = self.autosave {
200 builder = builder.autosave(duration);
201 }
202
203 if let Some(marshaler) = self.default_marshaler {
204 builder = builder.marshaler(marshaler);
205 }
206
207 for (id, path) in self.path_table {
208 builder = builder.path_of(id, path);
209 }
210
211 for (id, marshaler) in self.marshaler_table {
212 builder = builder.marshaler_of(id, marshaler);
213 }
214
215 builder.build(handle, env!("CARGO_PKG_NAME"))
216 }
217
218 pub fn build(self) -> TauriPlugin<R> {
220 tauri::plugin::Builder::new("vue")
221 .setup(|app, api| setup(app, api, self))
222 .on_event(on_event)
223 .invoke_handler(tauri::generate_handler![
224 command::allow_save,
225 command::allow_sync,
226 command::clear_autosave,
227 command::deny_save,
228 command::deny_sync,
229 command::destroy,
230 command::get_default_save_strategy,
231 command::get_store_collection_path,
232 command::get_save_strategy,
233 command::get_store_ids,
234 command::get_store_path,
235 command::get_store_state,
236 command::load,
237 command::patch,
238 command::save,
239 command::save_all,
240 command::save_all_now,
241 command::save_now,
242 command::save_some,
243 command::save_some_now,
244 command::set_autosave,
245 command::set_save_strategy,
246 command::set_store_options,
247 command::unload
248 ])
249 .build()
250 }
251}
252
253impl<R: Runtime> Default for Builder<R> {
254 fn default() -> Self {
255 Self {
256 default_path: None,
257 path_table: HashMap::new(),
258 default_marshaler: None,
259 marshaler_table: HashMap::new(),
260 default_save_strategy: SaveStrategy::default(),
261 autosave: None,
262 on_load: None,
263 save_denylist: HashSet::default(),
264 sync_denylist: HashSet::default(),
265 migrator: Migrator::default(),
266 debug_stores: true,
267 }
268 }
269}
270
271#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
272fn setup<R, D>(app: &AppHandle<R>, _api: PluginApi<R, D>, builder: Builder<R>) -> BoxResult<()>
273where
274 R: Runtime,
275 D: DeserializeOwned,
276{
277 let handle = Handle::new(app.clone());
278 builder.build_collection(handle)?;
279 Ok(())
280}
281
282#[cfg(any(target_os = "android", target_os = "ios"))]
283fn setup<R, D>(_app: &AppHandle<R>, api: PluginApi<R, D>, builder: Builder<R>) -> BoxResult<()>
284where
285 R: Runtime,
286 D: DeserializeOwned,
287{
288 #[cfg(target_os = "android")]
289 let handle = api.register_android_plugin("com.plugin.vue", "VuePlugin")?;
290
291 #[cfg(target_os = "ios")]
292 let handle = api.register_ios_plugin(init_plugin_vue)?;
293
294 builder.build_collection(Handle::new(handle))?;
295 Ok(())
296}
297
298fn on_event<R>(app: &AppHandle<R>, event: &RunEvent)
299where
300 R: Runtime,
301{
302 if let RunEvent::Exit = event {
303 let _ = app.vue().0.on_exit();
304 }
305}
306
307pub fn init<R: Runtime>() -> TauriPlugin<R> {
309 Builder::default().build()
310}