Skip to content

Commit

Permalink
refactor(store)!: more reworks (#1860)
Browse files Browse the repository at this point in the history
* refactor(store): more reworks

* Enable auto save by default

* Store to resource table by default

* Remove share store

* Clean up

* Add close store

* Add store function

* Add lazy store

* Add init to lazy store

* refresh cache in example

* Add get-or-create-store

* Revert "Add get-or-create-store"

This reverts commit 7ffd769.

* try get first

* Docs

* Use absolute path for store

* more docs

* Allow js to use pre-stored (de)serialize functions

* Fix js get and close store

* Show case how to use pretty json

* Update readme

* Use store instead of `store_builder` in example

* Build

* Fix example

* More docs for StoreBuilder::build

* Add default (de)serialize fn

* Use pretty json by default

* Use `undefined` for empty value in get

* Change files

* Differentiate json null from no value for events

* Add create or existing

* Build

* Rename inner store's inset method to set

* Update readme

* Apply suggestions from code review

* Use close instead

* Update breaking changes

* Return result in resolve_store_path

* Change to close_resource and take &self

* Clean up

* Apply suggestions from code review

Co-authored-by: Amr Bashir <[email protected]>

* Remove unused pub(crate)

* Update change file

* Expose resolve_store_path

* Remove with_store

* Remove StoreInner from pub and expose is_empty

* Fix wrong jsdoc param

* Update readme

* rename createOrExistingStore to createOrLoad

* make api consistent with the JS implementation, add examples

* fmt

* reintroduce "get existing store" behavior for create_or_load

* rename createOrLoad to newOrExisting

* keep store lock throughout whole new_or_existing_inner

* Remove load

* Missed if load

* Don't make StoreState public

* Remove R: Runtime from Builder

* rename newOrExisting to load, load to reload

* update docs

* rename missing reload fn

* rename builder fn to build()

* fix default permission

* Fix description and create_new logic

* Clippy

* Update docs

* Update docs

* remove create_store command

* remove close_store command since we extend from Resource

* Revert "remove close_store command since we extend from Resource"

This reverts commit 4a29fc8.

* Reapply "remove close_store command since we extend from Resource"

This reverts commit 70a1830.

---------

Co-authored-by: Amr Bashir <[email protected]>
Co-authored-by: Lucas Nogueira <[email protected]>
  • Loading branch information
3 people authored Oct 17, 2024
1 parent cfd48b3 commit 8c67d44
Show file tree
Hide file tree
Showing 20 changed files with 1,058 additions and 507 deletions.
7 changes: 7 additions & 0 deletions .changes/store-plugin-rework-js-feat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"store-js": minor:feat
---

- Add `getStore`
- Add an option to use pre-stored (de)serialize functions (registered on rust)
- Add `LazyStore`
23 changes: 23 additions & 0 deletions .changes/store-plugin-rework.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"store": minor:breaking
---

### Breaking changes:

- Renamed `StoreCollection` to `StoreState`
- `StoreBuilder::build` now returns a `Result`
- `StoreExt::store` now returns `Result<Arc<Store>>`

### Enhancements:

- Save and cancel pending auto save on drop
- Use absolute path as store's key, fix #984
- Share store to resource table by default
- Enable auto save with 100ms debounce time by default
- Use pretty json by default, close #1690

### New features:

- Add `get_store` to get shared stores across js and rust side
- Add default (de)serialize functions settings `default_serialize_fn` and `default_deserialize_fn`
- Allow js to use pre-stored (de)serialize functions registered by `register_serialize_fn` and `register_deserialize_fn`
6 changes: 1 addition & 5 deletions examples/api/src-tauri/capabilities/base.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,6 @@
],
"deny": ["$APPDATA/db/*.stronghold"]
},
"store:allow-entries",
"store:allow-get",
"store:allow-set",
"store:allow-save",
"store:allow-load"
"store:default"
]
}
3 changes: 2 additions & 1 deletion examples/api/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ pub fn run() {
.user_agent(&format!("Tauri API - {}", std::env::consts::OS))
.title("Tauri API Validation")
.inner_size(1000., 800.)
.min_inner_size(600., 400.);
.min_inner_size(600., 400.)
.visible(false);
}

#[cfg(target_os = "windows")]
Expand Down
78 changes: 60 additions & 18 deletions examples/api/src/views/Store.svelte
Original file line number Diff line number Diff line change
@@ -1,34 +1,71 @@
<script>
import { Store } from "@tauri-apps/plugin-store";
import { LazyStore } from "@tauri-apps/plugin-store";
import { onMount } from "svelte";
export let onMessage;
let key;
let value;
const store = new Store("cache.json");
let store = new LazyStore("cache.json");
let cache = {};
onMount(async () => {
await store.load();
const values = await store.entries();
for (const [key, value] of values) {
cache[key] = value;
async function refreshEntries() {
try {
const values = await store.entries();
cache = {};
for (const [key, value] of values) {
cache[key] = value;
}
} catch (error) {
onMessage(error);
}
cache = cache;
}
onMount(async () => {
await refreshEntries();
});
function write(key, value) {
store
.set(key, value)
.then(() => store.get(key))
.then((v) => {
cache[key] = v;
async function write(key, value) {
try {
if (value) {
await store.set(key, value);
} else {
await store.delete(key);
}
const v = await store.get(key);
if (v === undefined) {
delete cache[key];
cache = cache;
})
.then(() => store.save())
.catch(onMessage);
} else {
cache[key] = v;
}
} catch (error) {
onMessage(error);
}
}
async function reset() {
try {
await store.reset();
} catch (error) {
onMessage(error);
}
await refreshEntries();
}
async function close() {
try {
await store.close();
onMessage("Store is now closed, any new operations will error out");
} catch (error) {
onMessage(error);
}
}
function reopen() {
store = new LazyStore("cache.json");
onMessage("We made a new `LazyStore` instance, operations will now work");
}
</script>

Expand All @@ -44,7 +81,12 @@
<input class="grow input" bind:value />
</div>

<button class="btn" on:click={() => write(key, value)}> Write </button>
<div>
<button class="btn" on:click={() => write(key, value)}>Write</button>
<button class="btn" on:click={() => reset()}>Reset</button>
<button class="btn" on:click={() => close()}>Close</button>
<button class="btn" on:click={() => reopen()}>Re-open</button>
</div>
</div>

<div>
Expand Down
59 changes: 17 additions & 42 deletions plugins/store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Afterwards all the plugin's APIs are available through the JavaScript guest bind
```typescript
import { Store } from '@tauri-apps/plugin-store'

const store = new Store('.settings.dat')
const store = await Store.load('settings.json')

await store.set('some-key', { value: 5 })

Expand All @@ -81,14 +81,11 @@ if (val) {
} else {
console.log('val is null')
}

// This manually saves the store.
await store.save()
```

### Persisting Values

As seen above, values added to the store are not persisted between application loads unless the application is closed gracefully.
Modifications made to the store are automatically saved by default

You can manually save a store with:

Expand All @@ -103,65 +100,43 @@ However, you can also load them manually later like so:
await store.load()
```

### LazyStore

There's also a high level API `LazyStore` which only loads the store on first access, note that the options will be ignored if a `Store` with that path has already been created

```typescript
import { LazyStore } from '@tauri-apps/plugin-store'

const store = new LazyStore('settings.json')
```

## Usage from Rust

You can also create `Store` instances directly in Rust:

```rust
use tauri_plugin_store::StoreBuilder;
use tauri_plugin_store::StoreExt;
use serde_json::json;

fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::default().build())
.setup(|app| {
let mut store = StoreBuilder::new("app_data.bin").build(app.handle().clone());

// Attempt to load the store, if it's saved already.
store.load().expect("Failed to load store from disk");
// This loads the store from disk
let store = app.store("app_data.json")?;

// Note that values must be serde_json::Value instances,
// otherwise, they will not be compatible with the JavaScript bindings.
store.insert("a".to_string(), json!("b"));

// You can manually save the store after making changes.
// Otherwise, it will save upon graceful exit as described above.
store.save()
store.set("a".to_string(), json!("b"));
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```

### Loading Gracefully

If you call `load` on a `Store` that hasn't yet been written to the disk, it will return an error. You must handle this error if you want to gracefully continue and use the default store until you save it to the disk. The example above shows how to do this.

For example, this would cause a panic if the store has not yet been created:

```rust
store.load().unwrap();
```

Rather than silently continuing like you may expect.

You should always handle the error appropriately rather than unwrapping, or you may experience unexpected app crashes:

```rust
store.load().expect("Failed to load store from disk");
```

### Frontend Interoperability

As you may have noticed, the `Store` crated above isn't accessible to the frontend. To interoperate with stores created by JavaScript use the exported `with_store` method:

```rust
use tauri::Wry;
use tauri_plugin_store::StoreExt;

let store = app.store_builder("app_data.bin").build();
store.insert("key", "value");
```
The store created from both Rust side and JavaScript side are stored in the app's resource table and can be accessed by both sides, you can access it by using the same path, with `getStore` and `LazyStore` in the JavaScript side and `get_store` and `store` in the Rust side

## Contributing

Expand Down
2 changes: 1 addition & 1 deletion plugins/store/api-iife.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions plugins/store/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
// SPDX-License-Identifier: MIT

const COMMANDS: &[&str] = &[
"create_store",
"load",
"get_store",
"set",
"get",
"has",
Expand All @@ -12,9 +13,9 @@ const COMMANDS: &[&str] = &[
"reset",
"keys",
"values",
"length",
"entries",
"load",
"length",
"reload",
"save",
];

Expand Down
17 changes: 5 additions & 12 deletions plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use std::time::Duration;

use serde_json::json;
use tauri::Listener;
use tauri_plugin_store::StoreExt;

mod app;
Expand All @@ -18,17 +17,11 @@ fn main() {
.plugin(tauri_plugin_store::Builder::new().build())
.setup(|app| {
// Init store and load it from disk
let store = app
.handle()
.store_builder("settings.json")
.auto_save(Duration::from_millis(100))
.build();

// If there are no saved settings yet, this will return an error so we ignore the return value.
let _ = store.load();

let store = app.store("settings.json")?;
app.listen("store://change", |event| {
dbg!(event);
});
let app_settings = AppSettings::load_from_store(&store);

match app_settings {
Ok(app_settings) => {
let theme = app_settings.theme;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"identifier": "com.tauri.app-settings-manager",
"build": {
"devUrl": "http://localhost:1420",
"frontendDist": "../dist"
},
"app": {
Expand Down
Loading

0 comments on commit 8c67d44

Please sign in to comment.