Skip to content

Commit 5ac8fbb

Browse files
feat(store): load override defaults (#2857)
* feat(store): load override defaults * Update docs * Update example * Allow setting defaults from js * Tweak resolve * Merge remote-tracking branch 'upstream/v2' into store-load-override-defaults * Merge branch 'v2' of https://github.com/tauri-apps/plugins-workspace into store-load-override-defaults * Merge branch 'v2' into store-load-override-defaults * Rename to ignore defaults * Merge remote-tracking branch 'upstream/v2' into store-load-override-defaults
1 parent e0323ec commit 5ac8fbb

File tree

7 files changed

+171
-64
lines changed

7 files changed

+171
-64
lines changed

.changes/store-defaults-js.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
store: minor
3+
store-js: minor
4+
---
5+
6+
Allow setting defaults from the JavaScript API
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
store: minor
3+
store-js: minor
4+
---
5+
6+
Add an new option `overrideDefaults` for creating/loading and reloading the store that overrides the store with the on-disk state, ignoring defaults

examples/api/src/views/Store.svelte

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,85 @@
11
<script>
2-
import { LazyStore } from "@tauri-apps/plugin-store";
3-
import { onMount } from "svelte";
2+
import { appDataDir, resolve } from '@tauri-apps/api/path'
3+
import { LazyStore } from '@tauri-apps/plugin-store'
4+
import { onMount } from 'svelte'
45
5-
export let onMessage;
6+
let { onMessage } = $props()
67
7-
let key;
8-
let value;
8+
let key = $state()
9+
let value = $state()
910
10-
let store = new LazyStore("cache.json");
11-
let cache = {};
11+
const storeName = 'cache.json'
12+
13+
let store = new LazyStore(storeName)
14+
let path = $state('')
15+
let cache = $state({})
1216
1317
async function refreshEntries() {
1418
try {
15-
const values = await store.entries();
16-
cache = {};
19+
const values = await store.entries()
20+
cache = {}
1721
for (const [key, value] of values) {
18-
cache[key] = value;
22+
cache[key] = value
1923
}
2024
} catch (error) {
21-
onMessage(error);
25+
onMessage(error)
2226
}
2327
}
2428
2529
onMount(async () => {
26-
await refreshEntries();
27-
});
30+
path = await resolve(await appDataDir(), storeName)
31+
await refreshEntries()
32+
})
2833
2934
async function write(key, value) {
3035
try {
3136
if (value) {
32-
await store.set(key, value);
37+
await store.set(key, value)
3338
} else {
34-
await store.delete(key);
39+
await store.delete(key)
3540
}
36-
const v = await store.get(key);
41+
const v = await store.get(key)
3742
if (v === undefined) {
38-
delete cache[key];
39-
cache = cache;
43+
delete cache[key]
44+
cache = cache
4045
} else {
41-
cache[key] = v;
46+
cache[key] = v
4247
}
4348
} catch (error) {
44-
onMessage(error);
49+
onMessage(error)
4550
}
4651
}
4752
4853
async function reset() {
4954
try {
50-
await store.reset();
55+
await store.reset()
56+
} catch (error) {
57+
onMessage(error)
58+
}
59+
await refreshEntries()
60+
}
61+
62+
async function reload() {
63+
try {
64+
await store.reload({ overrideDefaults: true })
5165
} catch (error) {
52-
onMessage(error);
66+
onMessage(error)
5367
}
54-
await refreshEntries();
68+
await refreshEntries()
5569
}
5670
5771
async function close() {
5872
try {
59-
await store.close();
60-
onMessage("Store is now closed, any new operations will error out");
73+
await store.close()
74+
onMessage('Store is now closed, any new operations will error out')
6175
} catch (error) {
62-
onMessage(error);
76+
onMessage(error)
6377
}
6478
}
6579
6680
function reopen() {
67-
store = new LazyStore("cache.json");
68-
onMessage("We made a new `LazyStore` instance, operations will now work");
81+
store = new LazyStore(storeName)
82+
onMessage('We made a new `LazyStore` instance, operations will now work')
6983
}
7084
</script>
7185

@@ -82,14 +96,17 @@
8296
</div>
8397

8498
<div>
85-
<button class="btn" on:click={() => write(key, value)}>Write</button>
86-
<button class="btn" on:click={() => reset()}>Reset</button>
87-
<button class="btn" on:click={() => close()}>Close</button>
88-
<button class="btn" on:click={() => reopen()}>Re-open</button>
99+
<button class="btn" onclick={() => write(key, value)}>Write</button>
100+
<button class="btn" onclick={() => reset()}>Reset</button>
101+
<button class="btn" onclick={() => reload()}>Reload</button>
102+
<button class="btn" onclick={() => close()}>Close</button>
103+
<button class="btn" onclick={() => reopen()}>Re-open</button>
89104
</div>
105+
<div>Store at <code>{path}</code> on disk</div>
90106
</div>
91107

92108
<div>
109+
<h2>Store Values</h2>
93110
{#each Object.entries(cache) as [k, v]}
94111
<div>{k} = {v}</div>
95112
{/each}

plugins/store/api-iife.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/store/guest-js/index.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ interface ChangePayload<T> {
1818
* Options to create a store
1919
*/
2020
export type StoreOptions = {
21+
/**
22+
* Default value of the store
23+
*/
24+
defaults: { [key: string]: unknown }
2125
/**
2226
* Auto save on modification with debounce duration in milliseconds, it's 100ms by default, pass in `false` to disable it
2327
*/
@@ -34,6 +38,10 @@ export type StoreOptions = {
3438
* Force create a new store with default values even if it already exists.
3539
*/
3640
createNew?: boolean
41+
/**
42+
* When creating the store, override the store with the on-disk state if it exists, ignoring defaults
43+
*/
44+
overrideDefaults?: boolean
3745
}
3846

3947
/**
@@ -145,8 +153,8 @@ export class LazyStore implements IStore {
145153
return (await this.store).length()
146154
}
147155

148-
async reload(): Promise<void> {
149-
await (await this.store).reload()
156+
async reload(options?: ReloadOptions): Promise<void> {
157+
await (await this.store).reload(options)
150158
}
151159

152160
async save(): Promise<void> {
@@ -196,7 +204,7 @@ export class Store extends Resource implements IStore {
196204
static async load(path: string, options?: StoreOptions): Promise<Store> {
197205
const rid = await invoke<number>('plugin:store|load', {
198206
path,
199-
...options
207+
options
200208
})
201209
return new Store(rid)
202210
}
@@ -280,8 +288,8 @@ export class Store extends Resource implements IStore {
280288
return await invoke('plugin:store|length', { rid: this.rid })
281289
}
282290

283-
async reload(): Promise<void> {
284-
await invoke('plugin:store|reload', { rid: this.rid })
291+
async reload(options?: ReloadOptions): Promise<void> {
292+
await invoke('plugin:store|reload', { rid: this.rid, ...options })
285293
}
286294

287295
async save(): Promise<void> {
@@ -396,10 +404,15 @@ interface IStore {
396404
*
397405
* This method is useful if the on-disk state was edited by the user and you want to synchronize the changes.
398406
*
399-
* Note: This method does not emit change events.
407+
* Note:
408+
* - This method loads the data and merges it with the current store,
409+
* this behavior will be changed to resetting to default first and then merging with the on-disk state in v3,
410+
* to fully match the store with the on-disk state, set {@linkcode ReloadOptions.ignoreDefaults} to `true`
411+
* - This method does not emit change events.
412+
*
400413
* @returns
401414
*/
402-
reload(): Promise<void>
415+
reload(options?: ReloadOptions): Promise<void>
403416

404417
/**
405418
* Saves the store to disk at the store's `path`.
@@ -437,3 +450,13 @@ interface IStore {
437450
*/
438451
close(): Promise<void>
439452
}
453+
454+
/**
455+
* Options to {@linkcode IStore.reload} a {@linkcode IStore}
456+
*/
457+
export type ReloadOptions = {
458+
/**
459+
* To fully match the store with the on-disk state, ignoring defaults
460+
*/
461+
ignoreDefaults?: boolean
462+
}

plugins/store/src/lib.rs

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,36 @@ enum AutoSave {
5353
Bool(bool),
5454
}
5555

56-
fn builder<R: Runtime>(
57-
app: AppHandle<R>,
58-
store_state: State<'_, StoreState>,
59-
path: PathBuf,
56+
#[derive(Serialize, Deserialize)]
57+
#[serde(rename_all = "camelCase")]
58+
struct LoadStoreOptions {
59+
defaults: Option<HashMap<String, JsonValue>>,
6060
auto_save: Option<AutoSave>,
6161
serialize_fn_name: Option<String>,
6262
deserialize_fn_name: Option<String>,
63+
#[serde(default)]
6364
create_new: bool,
65+
#[serde(default)]
66+
override_defaults: bool,
67+
}
68+
69+
fn builder<R: Runtime>(
70+
app: AppHandle<R>,
71+
store_state: State<'_, StoreState>,
72+
path: PathBuf,
73+
options: Option<LoadStoreOptions>,
6474
) -> Result<StoreBuilder<R>> {
6575
let mut builder = app.store_builder(path);
66-
if let Some(auto_save) = auto_save {
76+
77+
let Some(options) = options else {
78+
return Ok(builder);
79+
};
80+
81+
if let Some(defaults) = options.defaults {
82+
builder = builder.defaults(defaults);
83+
}
84+
85+
if let Some(auto_save) = options.auto_save {
6786
match auto_save {
6887
AutoSave::DebounceDuration(duration) => {
6988
builder = builder.auto_save(Duration::from_millis(duration));
@@ -75,26 +94,30 @@ fn builder<R: Runtime>(
7594
}
7695
}
7796

78-
if let Some(serialize_fn_name) = serialize_fn_name {
97+
if let Some(serialize_fn_name) = options.serialize_fn_name {
7998
let serialize_fn = store_state
8099
.serialize_fns
81100
.get(&serialize_fn_name)
82101
.ok_or_else(|| crate::Error::SerializeFunctionNotFound(serialize_fn_name))?;
83102
builder = builder.serialize(*serialize_fn);
84103
}
85104

86-
if let Some(deserialize_fn_name) = deserialize_fn_name {
105+
if let Some(deserialize_fn_name) = options.deserialize_fn_name {
87106
let deserialize_fn = store_state
88107
.deserialize_fns
89108
.get(&deserialize_fn_name)
90109
.ok_or_else(|| crate::Error::DeserializeFunctionNotFound(deserialize_fn_name))?;
91110
builder = builder.deserialize(*deserialize_fn);
92111
}
93112

94-
if create_new {
113+
if options.create_new {
95114
builder = builder.create_new();
96115
}
97116

117+
if options.override_defaults {
118+
builder = builder.override_defaults();
119+
}
120+
98121
Ok(builder)
99122
}
100123

@@ -103,20 +126,9 @@ async fn load<R: Runtime>(
103126
app: AppHandle<R>,
104127
store_state: State<'_, StoreState>,
105128
path: PathBuf,
106-
auto_save: Option<AutoSave>,
107-
serialize_fn_name: Option<String>,
108-
deserialize_fn_name: Option<String>,
109-
create_new: Option<bool>,
129+
options: Option<LoadStoreOptions>,
110130
) -> Result<ResourceId> {
111-
let builder = builder(
112-
app,
113-
store_state,
114-
path,
115-
auto_save,
116-
serialize_fn_name,
117-
deserialize_fn_name,
118-
create_new.unwrap_or_default(),
119-
)?;
131+
let builder = builder(app, store_state, path, options)?;
120132
let (_, rid) = builder.build_inner()?;
121133
Ok(rid)
122134
}
@@ -209,9 +221,17 @@ async fn length<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<usize>
209221
}
210222

211223
#[tauri::command]
212-
async fn reload<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<()> {
224+
async fn reload<R: Runtime>(
225+
app: AppHandle<R>,
226+
rid: ResourceId,
227+
ignore_defaults: Option<bool>,
228+
) -> Result<()> {
213229
let store = app.resources_table().get::<Store<R>>(rid)?;
214-
store.reload()
230+
if ignore_defaults.unwrap_or_default() {
231+
store.reload_ignore_defaults()
232+
} else {
233+
store.reload()
234+
}
215235
}
216236

217237
#[tauri::command]

0 commit comments

Comments
 (0)