diff --git a/.changes/store-defaults-js.md b/.changes/store-defaults-js.md
new file mode 100644
index 0000000000..122ef81950
--- /dev/null
+++ b/.changes/store-defaults-js.md
@@ -0,0 +1,6 @@
+---
+store: minor
+store-js: minor
+---
+
+Allow setting defaults from the JavaScript API
diff --git a/.changes/store-load-override-defaults.md b/.changes/store-load-override-defaults.md
new file mode 100644
index 0000000000..1fb8376e07
--- /dev/null
+++ b/.changes/store-load-override-defaults.md
@@ -0,0 +1,6 @@
+---
+store: minor
+store-js: minor
+---
+
+Add an new option `overrideDefaults` for creating/loading and reloading the store that overrides the store with the on-disk state, ignoring defaults
diff --git a/examples/api/src/views/Store.svelte b/examples/api/src/views/Store.svelte
index 6248b0099b..b03f563db3 100644
--- a/examples/api/src/views/Store.svelte
+++ b/examples/api/src/views/Store.svelte
@@ -1,71 +1,85 @@
@@ -82,14 +96,17 @@
+
Store Values
{#each Object.entries(cache) as [k, v]}
{k} = {v}
{/each}
diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js
index 9aa983b6a1..f02ebd26db 100644
--- a/plugins/store/api-iife.js
+++ b/plugins/store/api-iife.js
@@ -1 +1 @@
-if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,a;function r(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},a){return window.__TAURI_INTERNALS__.invoke(t,e,a)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,a,r){if("function"==typeof e?t!==e||!r:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===a?r:"a"===a?r.call(t):r?r.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,a){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,a)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,a){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:r(e)}).then((e=>async()=>async function(t,e){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(t,e),await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.load(t,e)}e=new WeakMap,function(t){t.WINDOW_RESIZED="tauri://resize",t.WINDOW_MOVED="tauri://move",t.WINDOW_CLOSE_REQUESTED="tauri://close-requested",t.WINDOW_DESTROYED="tauri://destroyed",t.WINDOW_FOCUS="tauri://focus",t.WINDOW_BLUR="tauri://blur",t.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",t.WINDOW_THEME_CHANGED="tauri://theme-changed",t.WINDOW_CREATED="tauri://window-created",t.WEBVIEW_CREATED="tauri://webview-created",t.DRAG_ENTER="tauri://drag-enter",t.DRAG_OVER="tauri://drag-over",t.DRAG_DROP="tauri://drag-drop",t.DRAG_LEAVE="tauri://drag-leave"}(a||(a={}));class u extends i{constructor(t){super(t)}static async load(t,e){const a=await s("plugin:store|load",{path:t,...e});return new u(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new u(t):null))}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,a]=await s("plugin:store|get",{rid:this.rid,key:t});return a?e:void 0}async has(t){return await s("plugin:store|has",{rid:this.rid,key:t})}async delete(t){return await s("plugin:store|delete",{rid:this.rid,key:t})}async clear(){await s("plugin:store|clear",{rid:this.rid})}async reset(){await s("plugin:store|reset",{rid:this.rid})}async keys(){return await s("plugin:store|keys",{rid:this.rid})}async values(){return await s("plugin:store|values",{rid:this.rid})}async entries(){return await s("plugin:store|entries",{rid:this.rid})}async length(){return await s("plugin:store|length",{rid:this.rid})}async reload(){await s("plugin:store|reload",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(a=>{a.payload.resourceId===this.rid&&a.payload.key===t&&e(a.payload.exists?a.payload.value:void 0)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.exists?e.payload.value:void 0)}))}}return t.LazyStore=class{get store(){return this._store||(this._store=o(this.path,this.options)),this._store}constructor(t,e){this.path=t,this.options=e}async init(){await this.store}async set(t,e){return(await this.store).set(t,e)}async get(t){return(await this.store).get(t)}async has(t){return(await this.store).has(t)}async delete(t){return(await this.store).delete(t)}async clear(){await(await this.store).clear()}async reset(){await(await this.store).reset()}async keys(){return(await this.store).keys()}async values(){return(await this.store).values()}async entries(){return(await this.store).entries()}async length(){return(await this.store).length()}async reload(){await(await this.store).reload()}async save(){await(await this.store).save()}async onKeyChange(t,e){return(await this.store).onKeyChange(t,e)}async onChange(t){return(await this.store).onChange(t)}async close(){this._store&&await(await this._store).close()}},t.Store=u,t.getStore=async function(t){return await u.get(t)},t.load=o,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})}
+if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,a;function r(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},a){return window.__TAURI_INTERNALS__.invoke(t,e,a)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,a,r){if("function"==typeof e?t!==e||!r:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===a?r:"a"===a?r.call(t):r?r.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,a){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,a)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,a){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:r(e)}).then((e=>async()=>async function(t,e){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(t,e),await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.load(t,e)}e=new WeakMap,function(t){t.WINDOW_RESIZED="tauri://resize",t.WINDOW_MOVED="tauri://move",t.WINDOW_CLOSE_REQUESTED="tauri://close-requested",t.WINDOW_DESTROYED="tauri://destroyed",t.WINDOW_FOCUS="tauri://focus",t.WINDOW_BLUR="tauri://blur",t.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",t.WINDOW_THEME_CHANGED="tauri://theme-changed",t.WINDOW_CREATED="tauri://window-created",t.WEBVIEW_CREATED="tauri://webview-created",t.DRAG_ENTER="tauri://drag-enter",t.DRAG_OVER="tauri://drag-over",t.DRAG_DROP="tauri://drag-drop",t.DRAG_LEAVE="tauri://drag-leave"}(a||(a={}));class u extends i{constructor(t){super(t)}static async load(t,e){const a=await s("plugin:store|load",{path:t,options:e});return new u(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new u(t):null))}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,a]=await s("plugin:store|get",{rid:this.rid,key:t});return a?e:void 0}async has(t){return await s("plugin:store|has",{rid:this.rid,key:t})}async delete(t){return await s("plugin:store|delete",{rid:this.rid,key:t})}async clear(){await s("plugin:store|clear",{rid:this.rid})}async reset(){await s("plugin:store|reset",{rid:this.rid})}async keys(){return await s("plugin:store|keys",{rid:this.rid})}async values(){return await s("plugin:store|values",{rid:this.rid})}async entries(){return await s("plugin:store|entries",{rid:this.rid})}async length(){return await s("plugin:store|length",{rid:this.rid})}async reload(t){await s("plugin:store|reload",{rid:this.rid,...t})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(a=>{a.payload.resourceId===this.rid&&a.payload.key===t&&e(a.payload.exists?a.payload.value:void 0)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.exists?e.payload.value:void 0)}))}}return t.LazyStore=class{get store(){return this._store||(this._store=o(this.path,this.options)),this._store}constructor(t,e){this.path=t,this.options=e}async init(){await this.store}async set(t,e){return(await this.store).set(t,e)}async get(t){return(await this.store).get(t)}async has(t){return(await this.store).has(t)}async delete(t){return(await this.store).delete(t)}async clear(){await(await this.store).clear()}async reset(){await(await this.store).reset()}async keys(){return(await this.store).keys()}async values(){return(await this.store).values()}async entries(){return(await this.store).entries()}async length(){return(await this.store).length()}async reload(t){await(await this.store).reload(t)}async save(){await(await this.store).save()}async onKeyChange(t,e){return(await this.store).onKeyChange(t,e)}async onChange(t){return(await this.store).onChange(t)}async close(){this._store&&await(await this._store).close()}},t.Store=u,t.getStore=async function(t){return await u.get(t)},t.load=o,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})}
diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts
index 1df89fd529..49853f11e5 100644
--- a/plugins/store/guest-js/index.ts
+++ b/plugins/store/guest-js/index.ts
@@ -18,6 +18,10 @@ interface ChangePayload
{
* Options to create a store
*/
export type StoreOptions = {
+ /**
+ * Default value of the store
+ */
+ defaults: { [key: string]: unknown }
/**
* Auto save on modification with debounce duration in milliseconds, it's 100ms by default, pass in `false` to disable it
*/
@@ -34,6 +38,10 @@ export type StoreOptions = {
* Force create a new store with default values even if it already exists.
*/
createNew?: boolean
+ /**
+ * When creating the store, override the store with the on-disk state if it exists, ignoring defaults
+ */
+ overrideDefaults?: boolean
}
/**
@@ -145,8 +153,8 @@ export class LazyStore implements IStore {
return (await this.store).length()
}
- async reload(): Promise {
- await (await this.store).reload()
+ async reload(options?: ReloadOptions): Promise {
+ await (await this.store).reload(options)
}
async save(): Promise {
@@ -196,7 +204,7 @@ export class Store extends Resource implements IStore {
static async load(path: string, options?: StoreOptions): Promise {
const rid = await invoke('plugin:store|load', {
path,
- ...options
+ options
})
return new Store(rid)
}
@@ -280,8 +288,8 @@ export class Store extends Resource implements IStore {
return await invoke('plugin:store|length', { rid: this.rid })
}
- async reload(): Promise {
- await invoke('plugin:store|reload', { rid: this.rid })
+ async reload(options?: ReloadOptions): Promise {
+ await invoke('plugin:store|reload', { rid: this.rid, ...options })
}
async save(): Promise {
@@ -396,10 +404,15 @@ interface IStore {
*
* This method is useful if the on-disk state was edited by the user and you want to synchronize the changes.
*
- * Note: This method does not emit change events.
+ * Note:
+ * - This method loads the data and merges it with the current store,
+ * this behavior will be changed to resetting to default first and then merging with the on-disk state in v3,
+ * to fully match the store with the on-disk state, set {@linkcode ReloadOptions.ignoreDefaults} to `true`
+ * - This method does not emit change events.
+ *
* @returns
*/
- reload(): Promise
+ reload(options?: ReloadOptions): Promise
/**
* Saves the store to disk at the store's `path`.
@@ -437,3 +450,13 @@ interface IStore {
*/
close(): Promise
}
+
+/**
+ * Options to {@linkcode IStore.reload} a {@linkcode IStore}
+ */
+export type ReloadOptions = {
+ /**
+ * To fully match the store with the on-disk state, ignoring defaults
+ */
+ ignoreDefaults?: boolean
+}
diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs
index 0e59cd8192..0223f9c21e 100644
--- a/plugins/store/src/lib.rs
+++ b/plugins/store/src/lib.rs
@@ -53,17 +53,36 @@ enum AutoSave {
Bool(bool),
}
-fn builder(
- app: AppHandle,
- store_state: State<'_, StoreState>,
- path: PathBuf,
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct LoadStoreOptions {
+ defaults: Option>,
auto_save: Option,
serialize_fn_name: Option,
deserialize_fn_name: Option,
+ #[serde(default)]
create_new: bool,
+ #[serde(default)]
+ override_defaults: bool,
+}
+
+fn builder(
+ app: AppHandle,
+ store_state: State<'_, StoreState>,
+ path: PathBuf,
+ options: Option,
) -> Result> {
let mut builder = app.store_builder(path);
- if let Some(auto_save) = auto_save {
+
+ let Some(options) = options else {
+ return Ok(builder);
+ };
+
+ if let Some(defaults) = options.defaults {
+ builder = builder.defaults(defaults);
+ }
+
+ if let Some(auto_save) = options.auto_save {
match auto_save {
AutoSave::DebounceDuration(duration) => {
builder = builder.auto_save(Duration::from_millis(duration));
@@ -75,7 +94,7 @@ fn builder(
}
}
- if let Some(serialize_fn_name) = serialize_fn_name {
+ if let Some(serialize_fn_name) = options.serialize_fn_name {
let serialize_fn = store_state
.serialize_fns
.get(&serialize_fn_name)
@@ -83,7 +102,7 @@ fn builder(
builder = builder.serialize(*serialize_fn);
}
- if let Some(deserialize_fn_name) = deserialize_fn_name {
+ if let Some(deserialize_fn_name) = options.deserialize_fn_name {
let deserialize_fn = store_state
.deserialize_fns
.get(&deserialize_fn_name)
@@ -91,10 +110,14 @@ fn builder(
builder = builder.deserialize(*deserialize_fn);
}
- if create_new {
+ if options.create_new {
builder = builder.create_new();
}
+ if options.override_defaults {
+ builder = builder.override_defaults();
+ }
+
Ok(builder)
}
@@ -103,20 +126,9 @@ async fn load(
app: AppHandle,
store_state: State<'_, StoreState>,
path: PathBuf,
- auto_save: Option,
- serialize_fn_name: Option,
- deserialize_fn_name: Option,
- create_new: Option,
+ options: Option,
) -> Result {
- let builder = builder(
- app,
- store_state,
- path,
- auto_save,
- serialize_fn_name,
- deserialize_fn_name,
- create_new.unwrap_or_default(),
- )?;
+ let builder = builder(app, store_state, path, options)?;
let (_, rid) = builder.build_inner()?;
Ok(rid)
}
@@ -209,9 +221,17 @@ async fn length(app: AppHandle, rid: ResourceId) -> Result
}
#[tauri::command]
-async fn reload(app: AppHandle, rid: ResourceId) -> Result<()> {
+async fn reload(
+ app: AppHandle,
+ rid: ResourceId,
+ ignore_defaults: Option,
+) -> Result<()> {
let store = app.resources_table().get::>(rid)?;
- store.reload()
+ if ignore_defaults.unwrap_or_default() {
+ store.reload_ignore_defaults()
+ } else {
+ store.reload()
+ }
}
#[tauri::command]
diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs
index 1dc5e1d21d..f8b0c46045 100644
--- a/plugins/store/src/store.rs
+++ b/plugins/store/src/store.rs
@@ -39,6 +39,7 @@ pub struct StoreBuilder {
deserialize_fn: DeserializeFn,
auto_save: Option,
create_new: bool,
+ override_defaults: bool,
}
impl StoreBuilder {
@@ -66,6 +67,7 @@ impl StoreBuilder {
deserialize_fn,
auto_save: Some(Duration::from_millis(100)),
create_new: false,
+ override_defaults: false,
}
}
@@ -178,6 +180,12 @@ impl StoreBuilder {
self
}
+ /// Override the store values when creating the store, ignoring defaults.
+ pub fn override_defaults(mut self) -> Self {
+ self.override_defaults = true;
+ self
+ }
+
pub(crate) fn build_inner(mut self) -> crate::Result<(Arc>, ResourceId)> {
let stores = self.app.state::().stores.clone();
let mut stores = stores.lock().unwrap();
@@ -205,7 +213,11 @@ impl StoreBuilder {
);
if !self.create_new {
- let _ = store_inner.load();
+ if self.override_defaults {
+ let _ = store_inner.load_ignore_defaults();
+ } else {
+ let _ = store_inner.load();
+ }
}
let store = Store {
@@ -284,6 +296,8 @@ impl StoreInner {
}
/// Update the store from the on-disk state
+ ///
+ /// Note: This method loads the data and merges it with the current store
pub fn load(&mut self) -> crate::Result<()> {
let bytes = fs::read(&self.path)?;
@@ -293,6 +307,13 @@ impl StoreInner {
Ok(())
}
+ /// Load the store from the on-disk state, ignoring defaults
+ pub fn load_ignore_defaults(&mut self) -> crate::Result<()> {
+ let bytes = fs::read(&self.path)?;
+ self.cache = (self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?;
+ Ok(())
+ }
+
/// Inserts a key-value pair into the store.
pub fn set(&mut self, key: impl Into, value: impl Into) {
let key = key.into();
@@ -499,10 +520,24 @@ impl Store {
}
/// Update the store from the on-disk state
+ ///
+ /// Note:
+ /// - This method loads the data and merges it with the current store,
+ /// this behavior will be changed to resetting to default first and then merging with the on-disk state in v3,
+ /// to fully match the store with the on-disk state,
+ /// use [`reload_override_defaults`](Self::reload_override_defaults) instead
+ /// - This method does not emit change events
pub fn reload(&self) -> crate::Result<()> {
self.store.lock().unwrap().load()
}
+ /// Load the store from the on-disk state, ignoring defaults
+ ///
+ /// Note: This method does not emit change events
+ pub fn reload_ignore_defaults(&self) -> crate::Result<()> {
+ self.store.lock().unwrap().load_ignore_defaults()
+ }
+
/// Saves the store to disk at the store's `path`.
pub fn save(&self) -> crate::Result<()> {
if let Some(sender) = self.auto_save_debounce_sender.lock().unwrap().take() {