Skip to content

Commit

Permalink
feat(store)!: fully rework and add auto save (tauri-apps#1550)
Browse files Browse the repository at this point in the history
* Add auto save to store plugin

* Put jsdoc at constructor instead of class level

* Clippy

* Use enum instead of bool

* Some(AutoSaveMessage::Cancel) | None

* from_millis

* u64

* Add change file

* Rename to emit_on_change

* should use Duration in `with_store`

* Add breaking change notice to change file

* Emit change event for inserts by reset

* Update readme example

* Update example

* Remove extra line

* Make description clear it only works with managed

* Fix links in docstring

* Fix doc string closing

* get_mut

* Proof of concept

* fmt

* Load store on create

* cargo fmt

* Fix merge conflits

* Format

* small cleanup

* update docs, use `impl Into<JsonValue>`

* fix doctests, further simplification of api

* add store options

---------

Co-authored-by: Tillmann <[email protected]>
Co-authored-by: Lucas Nogueira <[email protected]>
  • Loading branch information
3 people authored and gezihuzi committed Oct 3, 2024
1 parent fe63544 commit 53dfcce
Show file tree
Hide file tree
Showing 16 changed files with 603 additions and 363 deletions.
5 changes: 5 additions & 0 deletions .changes/store-api-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"store-js": patch
---

**Breaking change**: Removed the `Store` constructor and added the `createStore` API.
7 changes: 7 additions & 0 deletions .changes/store-auto-save.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"store": patch
---

Add a setting `auto_save` to enable a store to debounce save on modification (on calls like set, clear, delete, reset)

**Breaking change**: Removed the `with_store` API and added `StoreExt::store_builder`.
1 change: 1 addition & 0 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions plugins/store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ tauri = { workspace = true }
log = { workspace = true }
thiserror = { workspace = true }
dunce = { workspace = true }
tokio = { version = "1", features = ["sync", "time", "macros"] }

[target.'cfg(target_os = "ios")'.dependencies]
tauri = { workspace = true, features = ["wry"] }

[dev-dependencies]
tauri = { workspace = true, features = ["wry"] }
8 changes: 3 additions & 5 deletions plugins/store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,10 @@ As you may have noticed, the `Store` crated above isn't accessible to the fronte

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

let stores = app.state::<StoreCollection<Wry>>();
let path = PathBuf::from("app_data.bin");

with_store(app_handle, stores, path, |store| store.insert("a".to_string(), json!("b")))
let store = app.store_builder("app_data.bin").build();
store.insert("key", "value");
```

## 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.

13 changes: 12 additions & 1 deletion plugins/store/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@
// SPDX-License-Identifier: MIT

const COMMANDS: &[&str] = &[
"set", "get", "has", "delete", "clear", "reset", "keys", "values", "length", "entries", "load",
"create_store",
"set",
"get",
"has",
"delete",
"clear",
"reset",
"keys",
"values",
"length",
"entries",
"load",
"save",
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ impl AppSettings {

let theme = store
.get("appSettings.theme")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.unwrap_or_else(|| "dark".to_string());
.and_then(|v| v.as_str().map(String::from))
.unwrap_or_else(|| "dark".to_owned());

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

use tauri_plugin_store::StoreBuilder;
use std::time::Duration;

use serde_json::json;
use tauri_plugin_store::StoreExt;

mod app;
use app::settings::AppSettings;

fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::default().build())
.plugin(tauri_plugin_store::Builder::new().build())
.setup(|app| {
// Init store and load it from disk
let mut store = StoreBuilder::new("settings.json").build(app.handle().clone());
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();
Expand All @@ -27,17 +34,20 @@ fn main() {
let theme = app_settings.theme;
let launch_at_login = app_settings.launch_at_login;

println!("theme {}", theme);
println!("launch_at_login {}", launch_at_login);

Ok(())
println!("theme {theme}");
println!("launch_at_login {launch_at_login}");
store.set(
"appSettings",
json!({ "theme": theme, "launchAtLogin": launch_at_login }),
);
}
Err(err) => {
eprintln!("Error loading settings: {}", err);
eprintln!("Error loading settings: {err}");
// Handle the error case if needed
Err(err) // Convert the error to a Box<dyn Error> and return Err(err) here
return Err(err); // Convert the error to a Box<dyn Error> and return Err(err) here
}
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down
76 changes: 42 additions & 34 deletions plugins/store/guest-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import { listen, type UnlistenFn } from '@tauri-apps/api/event'

import { invoke } from '@tauri-apps/api/core'
import { invoke, Resource } from '@tauri-apps/api/core'

interface ChangePayload<T> {
path: string
Expand All @@ -13,12 +13,36 @@ interface ChangePayload<T> {
}

/**
* A key-value store persisted by the backend layer.
* Options to create a store
*/
export class Store {
path: string
constructor(path: string) {
this.path = path
export type StoreOptions = {
/**
* Auto save on modification with debounce duration in milliseconds
*/
autoSave?: boolean
}

/**
* @param path: Path to save the store in `app_data_dir`
* @param options: Store configuration options
*/
export async function createStore(path: string, options?: StoreOptions) {
const resourceId = await invoke<number>('plugin:store|create_store', {
path,
...options
})
return new Store(resourceId, path)
}

/**
* A lazy loaded key-value store persisted by the backend layer.
*/
export class Store extends Resource {
constructor(
rid: number,
private readonly path: string
) {
super(rid)
}

/**
Expand All @@ -30,7 +54,7 @@ export class Store {
*/
async set(key: string, value: unknown): Promise<void> {
await invoke('plugin:store|set', {
path: this.path,
rid: this.rid,
key,
value
})
Expand All @@ -44,7 +68,7 @@ export class Store {
*/
async get<T>(key: string): Promise<T | null> {
return await invoke('plugin:store|get', {
path: this.path,
rid: this.rid,
key
})
}
Expand All @@ -57,7 +81,7 @@ export class Store {
*/
async has(key: string): Promise<boolean> {
return await invoke('plugin:store|has', {
path: this.path,
rid: this.rid,
key
})
}
Expand All @@ -70,7 +94,7 @@ export class Store {
*/
async delete(key: string): Promise<boolean> {
return await invoke('plugin:store|delete', {
path: this.path,
rid: this.rid,
key
})
}
Expand All @@ -82,9 +106,7 @@ export class Store {
* @returns
*/
async clear(): Promise<void> {
await invoke('plugin:store|clear', {
path: this.path
})
await invoke('plugin:store|clear', { rid: this.rid })
}

/**
Expand All @@ -94,9 +116,7 @@ export class Store {
* @returns
*/
async reset(): Promise<void> {
await invoke('plugin:store|reset', {
path: this.path
})
await invoke('plugin:store|reset', { rid: this.rid })
}

/**
Expand All @@ -105,9 +125,7 @@ export class Store {
* @returns
*/
async keys(): Promise<string[]> {
return await invoke('plugin:store|keys', {
path: this.path
})
return await invoke('plugin:store|keys', { rid: this.rid })
}

/**
Expand All @@ -116,9 +134,7 @@ export class Store {
* @returns
*/
async values<T>(): Promise<T[]> {
return await invoke('plugin:store|values', {
path: this.path
})
return await invoke('plugin:store|values', { rid: this.rid })
}

/**
Expand All @@ -127,9 +143,7 @@ export class Store {
* @returns
*/
async entries<T>(): Promise<Array<[key: string, value: T]>> {
return await invoke('plugin:store|entries', {
path: this.path
})
return await invoke('plugin:store|entries', { rid: this.rid })
}

/**
Expand All @@ -138,9 +152,7 @@ export class Store {
* @returns
*/
async length(): Promise<number> {
return await invoke('plugin:store|length', {
path: this.path
})
return await invoke('plugin:store|length', { rid: this.rid })
}

/**
Expand All @@ -152,9 +164,7 @@ export class Store {
* @returns
*/
async load(): Promise<void> {
await invoke('plugin:store|load', {
path: this.path
})
await invoke('plugin:store|load', { rid: this.rid })
}

/**
Expand All @@ -165,9 +175,7 @@ export class Store {
* @returns
*/
async save(): Promise<void> {
await invoke('plugin:store|save', {
path: this.path
})
await invoke('plugin:store|save', { rid: this.rid })
}

/**
Expand Down
13 changes: 13 additions & 0 deletions plugins/store/permissions/autogenerated/commands/create_store.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!

"$schema" = "../../schemas/schema.json"

[[permission]]
identifier = "allow-create-store"
description = "Enables the create_store command without any pre-configured scope."
commands.allow = ["create_store"]

[[permission]]
identifier = "deny-create-store"
description = "Denies the create_store command without any pre-configured scope."
commands.deny = ["create_store"]
27 changes: 27 additions & 0 deletions plugins/store/permissions/autogenerated/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ All operations are enabled by default.



- `allow-create-store`
- `allow-clear`
- `allow-delete`
- `allow-entries`
Expand Down Expand Up @@ -60,6 +61,32 @@ Denies the clear command without any pre-configured scope.
<tr>
<td>

`store:allow-create-store`

</td>
<td>

Enables the create_store command without any pre-configured scope.

</td>
</tr>

<tr>
<td>

`store:deny-create-store`

</td>
<td>

Denies the create_store command without any pre-configured scope.

</td>
</tr>

<tr>
<td>

`store:allow-delete`

</td>
Expand Down
1 change: 1 addition & 0 deletions plugins/store/permissions/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All operations are enabled by default.
"""
permissions = [
"allow-create-store",
"allow-clear",
"allow-delete",
"allow-entries",
Expand Down
10 changes: 10 additions & 0 deletions plugins/store/permissions/schemas/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,16 @@
"type": "string",
"const": "deny-clear"
},
{
"description": "Enables the create_store command without any pre-configured scope.",
"type": "string",
"const": "allow-create-store"
},
{
"description": "Denies the create_store command without any pre-configured scope.",
"type": "string",
"const": "deny-create-store"
},
{
"description": "Enables the delete command without any pre-configured scope.",
"type": "string",
Expand Down
Loading

0 comments on commit 53dfcce

Please sign in to comment.