tauri_store/collection/
builder.rs

1use super::handle::Handle;
2use super::marker::CollectionMarker;
3use super::{DefaultMarker, OnLoadFn, StoreCollection};
4use crate::collection::autosave::Autosave;
5use crate::collection::table::{MarshalerTable, PathTable};
6use crate::error::Result;
7use crate::manager::ManagerExt;
8use crate::migration::{Migration, MigrationContext, Migrator};
9use crate::store::{JsonMarshaler, Marshaler, SaveStrategy, Store, StoreId};
10use dashmap::{DashMap, DashSet};
11use std::collections::HashMap;
12use std::marker::PhantomData;
13use std::path::{Path, PathBuf};
14use std::sync::Mutex;
15use std::time::Duration;
16use tauri::{Manager, Runtime};
17
18#[cfg(feature = "plugin")]
19use tauri::plugin::TauriPlugin;
20
21/// Builder for the [`StoreCollection`](crate::collection::StoreCollection).
22pub struct StoreCollectionBuilder<R, C>
23where
24  R: Runtime,
25  C: CollectionMarker,
26{
27  default_path: Option<PathBuf>,
28  path_table: HashMap<StoreId, Box<Path>>,
29  default_marshaler: Option<Box<dyn Marshaler>>,
30  marshaler_table: HashMap<StoreId, Box<dyn Marshaler>>,
31  default_save_strategy: SaveStrategy,
32  autosave: Option<Duration>,
33  on_load: Option<Box<OnLoadFn<R, C>>>,
34  save_denylist: DashSet<StoreId>,
35  sync_denylist: DashSet<StoreId>,
36  migrator: Migrator,
37  debug_stores: bool,
38}
39
40impl<R, C> StoreCollectionBuilder<R, C>
41where
42  R: Runtime,
43  C: CollectionMarker,
44{
45  /// Creates a new builder instance with default values.
46  pub fn new() -> Self {
47    Self::default()
48  }
49
50  /// Sets the autosave interval for all stores.
51  #[must_use]
52  pub fn autosave(mut self, duration: Duration) -> Self {
53    self.autosave = Some(duration);
54    self
55  }
56
57  /// Sets the default save strategy to be used by the stores.
58  #[must_use]
59  pub fn default_save_strategy(mut self, strategy: SaveStrategy) -> Self {
60    self.default_save_strategy = strategy;
61    self
62  }
63
64  /// Registers a closure to be called when a store is loaded.
65  #[must_use]
66  pub fn on_load<F>(mut self, f: F) -> Self
67  where
68    F: Fn(&Store<R, C>) -> Result<()> + Send + Sync + 'static,
69  {
70    self.on_load = Some(Box::new(f));
71    self
72  }
73
74  /// Default directory where the stores are saved.
75  #[must_use]
76  pub fn path(mut self, path: impl AsRef<Path>) -> Self {
77    self.default_path = Some(path.as_ref().to_path_buf());
78    self
79  }
80
81  /// Sets where a store should be saved.
82  #[must_use]
83  pub fn path_of(mut self, id: impl AsRef<str>, path: impl AsRef<Path>) -> Self {
84    let id = StoreId::from(id.as_ref());
85    let path = Box::from(path.as_ref());
86    self.path_table.insert(id, path);
87    self
88  }
89
90  /// Sets a list of stores that should not be saved to disk.
91  #[must_use]
92  pub fn save_denylist<I, T>(mut self, denylist: I) -> Self
93  where
94    I: IntoIterator<Item = T>,
95    T: AsRef<str>,
96  {
97    let denylist = denylist
98      .into_iter()
99      .map(|it| StoreId::from(it.as_ref()));
100
101    self.save_denylist.extend(denylist);
102    self
103  }
104
105  /// Sets a list of stores that should not be synchronized across windows.
106  #[must_use]
107  pub fn sync_denylist<I, T>(mut self, denylist: I) -> Self
108  where
109    I: IntoIterator<Item = T>,
110    T: AsRef<str>,
111  {
112    let denylist = denylist
113      .into_iter()
114      .map(|it| StoreId::from(it.as_ref()));
115
116    self.sync_denylist.extend(denylist);
117    self
118  }
119
120  /// Defines how the stores should be serialized and deserialized.
121  #[must_use]
122  pub fn marshaler(mut self, marshaler: Box<dyn Marshaler>) -> Self {
123    self.default_marshaler = Some(marshaler);
124    self
125  }
126
127  /// Defines how a store should be serialized and deserialized.
128  #[must_use]
129  pub fn marshaler_of(mut self, id: impl AsRef<str>, marshaler: Box<dyn Marshaler>) -> Self {
130    let id = StoreId::from(id.as_ref());
131    self.marshaler_table.insert(id, marshaler);
132    self
133  }
134
135  /// Adds a `.dev` suffix to the store files when in development mode.
136  ///
137  /// This is enabled by default.
138  #[must_use]
139  pub fn enable_debug_stores(mut self, yes: bool) -> Self {
140    self.debug_stores = yes;
141    self
142  }
143
144  #[must_use]
145  #[doc(hidden)]
146  pub fn migrator(mut self, migrator: Migrator) -> Self {
147    self.migrator = migrator;
148    self
149  }
150
151  /// Defines a migration for a store.
152  #[must_use]
153  pub fn migration(mut self, id: impl Into<StoreId>, migration: Migration) -> Self {
154    self.migrator.add_migration(id.into(), migration);
155    self
156  }
157
158  /// Defines multiple migrations for a store.
159  #[must_use]
160  pub fn migrations<I>(mut self, id: impl Into<StoreId>, migrations: I) -> Self
161  where
162    I: IntoIterator<Item = Migration>,
163  {
164    self
165      .migrator
166      .add_migrations(id.into(), migrations);
167
168    self
169  }
170
171  /// Sets a closure to be called before each migration step.
172  #[must_use]
173  pub fn on_before_each_migration<F>(mut self, f: F) -> Self
174  where
175    F: Fn(MigrationContext) + Send + Sync + 'static,
176  {
177    self.migrator.on_before_each(f);
178    self
179  }
180
181  /// Builds the [`StoreCollection`](crate::collection::StoreCollection).
182  ///
183  /// # Panics
184  ///
185  /// Panics if a store collection is already initialized.
186  #[doc(hidden)]
187  pub fn build(self, handle: Handle<R>, plugin_name: &str) -> Result<()> {
188    let app = handle.app().clone();
189    debug_assert!(
190      app.try_state::<StoreCollection<R, C>>().is_none(),
191      "store collection is already initialized"
192    );
193
194    let default_path = match self.default_path {
195      Some(path) => path,
196      #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
197      None => app.path().app_data_dir()?.join(plugin_name),
198      #[cfg(any(target_os = "android", target_os = "ios"))]
199      None => handle.get_sandboxed_path()?.join(plugin_name),
200    };
201
202    let path_table = PathTable {
203      default: default_path.into_boxed_path(),
204      table: self.path_table,
205    };
206
207    let default_marshaler = self
208      .default_marshaler
209      .unwrap_or_else(|| Box::new(JsonMarshaler));
210
211    let marshaler_table = MarshalerTable {
212      default: default_marshaler,
213      table: self.marshaler_table,
214    };
215
216    app.manage(StoreCollection::<R, C> {
217      handle,
218      name: Box::from(plugin_name),
219      path_table,
220      marshaler_table,
221      stores: DashMap::new(),
222      on_load: self.on_load,
223      autosave: Mutex::new(Autosave::new(self.autosave)),
224      default_save_strategy: self.default_save_strategy,
225      save_denylist: self.save_denylist,
226      sync_denylist: self.sync_denylist,
227      migrator: Mutex::new(self.migrator),
228      debug_stores: self.debug_stores,
229      phantom: PhantomData,
230    });
231
232    let collection = app.store_collection_with_marker::<C>();
233    collection
234      .autosave
235      .lock()
236      .expect("autosave is poisoned")
237      .start::<R, C>(&app);
238
239    collection
240      .migrator
241      .lock()
242      .expect("migrator is poisoned")
243      .read::<R, C>(&app)?;
244
245    Ok(())
246  }
247}
248
249impl<R> StoreCollectionBuilder<R, DefaultMarker>
250where
251  R: Runtime,
252{
253  /// Initializes the plugin with a [`StoreCollection`](crate::collection::StoreCollection).
254  ///
255  /// # Panics
256  ///
257  /// Panics if a store collection is already initialized.
258  #[cfg(feature = "plugin")]
259  pub fn build_plugin(self) -> TauriPlugin<R> {
260    crate::plugin::build(self)
261  }
262}
263
264impl<R, C> Default for StoreCollectionBuilder<R, C>
265where
266  R: Runtime,
267  C: CollectionMarker,
268{
269  fn default() -> Self {
270    Self {
271      default_path: None,
272      path_table: HashMap::new(),
273      default_marshaler: None,
274      marshaler_table: HashMap::new(),
275      default_save_strategy: SaveStrategy::Immediate,
276      autosave: None,
277      on_load: None,
278      save_denylist: DashSet::new(),
279      sync_denylist: DashSet::new(),
280      migrator: Migrator::default(),
281      debug_stores: true,
282    }
283  }
284}