From 9811756e6cd7891bd8f243091f5bce6e96523f17 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 2 Oct 2024 18:02:44 +0800 Subject: [PATCH 01/70] refactor(store): more reworks --- plugins/store/api-iife.js | 2 +- plugins/store/build.rs | 1 + .../AppSettingsManager/src-tauri/src/main.rs | 13 +- .../src-tauri/tauri.conf.json | 1 + plugins/store/guest-js/index.ts | 10 +- .../autogenerated/commands/get_store.toml | 13 ++ .../permissions/autogenerated/reference.md | 26 +++ plugins/store/permissions/schemas/schema.json | 10 + plugins/store/src/error.rs | 6 +- plugins/store/src/lib.rs | 205 ++++++++---------- plugins/store/src/store.rs | 74 ++++--- 11 files changed, 204 insertions(+), 157 deletions(-) create mode 100644 plugins/store/permissions/autogenerated/commands/get_store.toml diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index 77295d7fe6..3582aa3bf4 100644 --- a/plugins/store/api-iife.js +++ b/plugins/store/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(e){"use strict";var t,r;function a(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}async function i(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;class n{get rid(){return function(e,t,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(e):a?a.value:t.get(e)}(this,t,"f")}constructor(e){t.set(this,void 0),function(e,t,r,a,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");t.set(e,r)}(this,t,e)}async close(){return i("plugin:resources|close",{rid:this.rid})}}async function s(e,t,r){const n={kind:"Any"};return i("plugin:event|listen",{event:e,target:n,handler:a(t)}).then((t=>async()=>async function(e,t){await i("plugin:event|unlisten",{event:e,eventId:t})}(e,t)))}t=new WeakMap,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(r||(r={}));class o extends n{constructor(e,t){super(e),this.path=t}async set(e,t){await i("plugin:store|set",{rid:this.rid,key:e,value:t})}async get(e){return await i("plugin:store|get",{rid:this.rid,key:e})}async has(e){return await i("plugin:store|has",{rid:this.rid,key:e})}async delete(e){return await i("plugin:store|delete",{rid:this.rid,key:e})}async clear(){await i("plugin:store|clear",{rid:this.rid})}async reset(){await i("plugin:store|reset",{rid:this.rid})}async keys(){return await i("plugin:store|keys",{rid:this.rid})}async values(){return await i("plugin:store|values",{rid:this.rid})}async entries(){return await i("plugin:store|entries",{rid:this.rid})}async length(){return await i("plugin:store|length",{rid:this.rid})}async load(){await i("plugin:store|load",{rid:this.rid})}async save(){await i("plugin:store|save",{rid:this.rid})}async onKeyChange(e,t){return await s("store://change",(r=>{r.payload.path===this.path&&r.payload.key===e&&t(r.payload.value)}))}async onChange(e){return await s("store://change",(t=>{t.payload.path===this.path&&e(t.payload.key,t.payload.value)}))}}return e.Store=o,e.createStore=async function(e,t){const r=await i("plugin:store|create_store",{path:e,...t});return new o(r,e)},e}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(e){"use strict";var t,r;function a(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}async function i(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;class n{get rid(){return function(e,t,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(e):a?a.value:t.get(e)}(this,t,"f")}constructor(e){t.set(this,void 0),function(e,t,r,a,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");t.set(e,r)}(this,t,e)}async close(){return i("plugin:resources|close",{rid:this.rid})}}async function s(e,t,r){const n={kind:"Any"};return i("plugin:event|listen",{event:e,target:n,handler:a(t)}).then((t=>async()=>async function(e,t){await i("plugin:event|unlisten",{event:e,eventId:t})}(e,t)))}t=new WeakMap,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(r||(r={}));class o extends n{constructor(e,t){super(e),this.path=t}async set(e,t){await i("plugin:store|set",{rid:this.rid,key:e,value:t})}async get(e){return await i("plugin:store|get",{rid:this.rid,key:e})}async has(e){return await i("plugin:store|has",{rid:this.rid,key:e})}async delete(e){return await i("plugin:store|delete",{rid:this.rid,key:e})}async clear(){await i("plugin:store|clear",{rid:this.rid})}async reset(){await i("plugin:store|reset",{rid:this.rid})}async keys(){return await i("plugin:store|keys",{rid:this.rid})}async values(){return await i("plugin:store|values",{rid:this.rid})}async entries(){return await i("plugin:store|entries",{rid:this.rid})}async length(){return await i("plugin:store|length",{rid:this.rid})}async load(){await i("plugin:store|load",{rid:this.rid})}async save(){await i("plugin:store|save",{rid:this.rid})}async onKeyChange(e,t){return await s("store://change",(r=>{r.payload.path===this.path&&r.payload.key===e&&t(r.payload.value)}))}async onChange(e){return await s("store://change",(t=>{t.payload.path===this.path&&e(t.payload.key,t.payload.value)}))}}return e.Store=o,e.createStore=async function(e,t){const r=await i("plugin:store|create_store",{path:e,...t});return new o(r,e)},e.getStore=async function(e){const t=await i("plugin:store|get_store");return new o(t,e)},e}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} diff --git a/plugins/store/build.rs b/plugins/store/build.rs index 3c9fee0150..a144df296a 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -4,6 +4,7 @@ const COMMANDS: &[&str] = &[ "create_store", + "get_store", "set", "get", "has", diff --git a/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs b/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs index 0dd4e0bc48..45ec5989ac 100644 --- a/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs +++ b/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs @@ -8,6 +8,7 @@ use std::time::Duration; use serde_json::json; +use tauri::Listener; use tauri_plugin_store::StoreExt; mod app; @@ -22,13 +23,13 @@ fn main() { .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(); - + .build() + .unwrap(); + app.share_store(store.clone()); + 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; diff --git a/plugins/store/examples/AppSettingsManager/src-tauri/tauri.conf.json b/plugins/store/examples/AppSettingsManager/src-tauri/tauri.conf.json index 5a67883d26..d3f60daafe 100644 --- a/plugins/store/examples/AppSettingsManager/src-tauri/tauri.conf.json +++ b/plugins/store/examples/AppSettingsManager/src-tauri/tauri.conf.json @@ -3,6 +3,7 @@ "version": "0.1.0", "identifier": "com.tauri.app-settings-manager", "build": { + "devUrl": "http://localhost:1420", "frontendDist": "../dist" }, "app": { diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 259b06626b..d173c3f43d 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -35,7 +35,15 @@ export async function createStore(path: string, options?: StoreOptions) { } /** - * A lazy loaded key-value store persisted by the backend layer. + * @param path: Path of the store in the rust side + */ +export async function getStore(path: string) { + const resourceId = await invoke('plugin:store|get_store') + return new Store(resourceId, path) +} + +/** + * A key-value store persisted by the backend layer. */ export class Store extends Resource { constructor( diff --git a/plugins/store/permissions/autogenerated/commands/get_store.toml b/plugins/store/permissions/autogenerated/commands/get_store.toml new file mode 100644 index 0000000000..7c19173a31 --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/get_store.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-get-store" +description = "Enables the get_store command without any pre-configured scope." +commands.allow = ["get_store"] + +[[permission]] +identifier = "deny-get-store" +description = "Denies the get_store command without any pre-configured scope." +commands.deny = ["get_store"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 4e9bf2cc3e..ce85734605 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -165,6 +165,32 @@ Denies the get command without any pre-configured scope. +`store:allow-get-store` + + + + +Enables the get_store command without any pre-configured scope. + + + + + + + +`store:deny-get-store` + + + + +Denies the get_store command without any pre-configured scope. + + + + + + + `store:allow-has` diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index 6ebf788ea8..bfd50f9cb6 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -344,6 +344,16 @@ "type": "string", "const": "deny-get" }, + { + "description": "Enables the get_store command without any pre-configured scope.", + "type": "string", + "const": "allow-get-store" + }, + { + "description": "Denies the get_store command without any pre-configured scope.", + "type": "string", + "const": "deny-get-store" + }, { "description": "Enables the has command without any pre-configured scope.", "type": "string", diff --git a/plugins/store/src/error.rs b/plugins/store/src/error.rs index afd43addfd..612edcfdc8 100644 --- a/plugins/store/src/error.rs +++ b/plugins/store/src/error.rs @@ -21,9 +21,9 @@ pub enum Error { /// IO error. #[error(transparent)] Io(#[from] std::io::Error), - /// Store not found - #[error("Store \"{0}\" not found")] - NotFound(PathBuf), + /// Store already exists + #[error("Store at \"{0}\" already exists")] + AlreadyExists(PathBuf), /// Some Tauri API failed #[error(transparent)] Tauri(#[from] tauri::Error), diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index b05bf4b420..53c71ea103 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -18,13 +18,13 @@ pub use serde_json::Value as JsonValue; use std::{ collections::HashMap, path::{Path, PathBuf}, - sync::{Mutex, Weak}, + sync::{Arc, Mutex, Weak}, time::Duration, }; pub use store::{Store, StoreBuilder, StoreInner}; use tauri::{ plugin::{self, TauriPlugin}, - AppHandle, Manager, ResourceId, RunEvent, Runtime, Webview, + AppHandle, Manager, ResourceId, RunEvent, Runtime, }; mod error; @@ -38,138 +38,185 @@ struct ChangePayload<'a> { } pub struct StoreCollection { - stores: Mutex>>>>, - // frozen: bool, + /// This weak pointer is always pointing to a real reference since we will remove it on drop + stores: Mutex>, Option)>>, } #[tauri::command] async fn create_store( app: AppHandle, - webview: Webview, path: PathBuf, auto_save: Option, ) -> Result { - let mut builder = app.store_builder(path); + let mut builder = app.store_builder(path.clone()); if let Some(auto_save) = auto_save { builder = builder.auto_save(Duration::from_millis(auto_save)); } - let store = builder.build(); - Ok(webview.resources_table().add(store)) + let store = builder.build()?; + let rid = app.resources_table().add_arc(store); + let collection = app.state::>(); + let mut stores = collection.stores.lock().unwrap(); + if let Some((_, resource_id)) = stores.get_mut(&path) { + resource_id.replace(rid); + } + Ok(rid) +} + +#[tauri::command] +async fn get_store(app: AppHandle, path: PathBuf) -> Option { + let collection = app.state::>(); + let mut stores = collection.stores.lock().unwrap(); + if let Some((store, resource_id)) = stores.get_mut(&path) { + let rid = if let Some(resource_id) = resource_id { + *resource_id + } else { + let rid = app.resources_table().add_arc(store.upgrade().unwrap()); + resource_id.replace(rid); + rid + }; + Some(rid) + } else { + None + } } #[tauri::command] async fn set( - webview: Webview, + app: AppHandle, rid: ResourceId, key: String, value: JsonValue, ) -> Result<()> { - let store = webview.resources_table().get::>(rid)?; + let store = app.resources_table().get::>(rid)?; store.set(key, value); Ok(()) } #[tauri::command] async fn get( - webview: Webview, + app: AppHandle, rid: ResourceId, key: String, ) -> Result> { - let store = webview.resources_table().get::>(rid)?; + let store = app.resources_table().get::>(rid)?; Ok(store.get(key)) } #[tauri::command] -async fn has(webview: Webview, rid: ResourceId, key: String) -> Result { - let store = webview.resources_table().get::>(rid)?; +async fn has(app: AppHandle, rid: ResourceId, key: String) -> Result { + let store = app.resources_table().get::>(rid)?; Ok(store.has(key)) } #[tauri::command] -async fn delete(webview: Webview, rid: ResourceId, key: String) -> Result { - let store = webview.resources_table().get::>(rid)?; +async fn delete(app: AppHandle, rid: ResourceId, key: String) -> Result { + let store = app.resources_table().get::>(rid)?; Ok(store.delete(key)) } #[tauri::command] -async fn clear(webview: Webview, rid: ResourceId) -> Result<()> { - let store = webview.resources_table().get::>(rid)?; +async fn clear(app: AppHandle, rid: ResourceId) -> Result<()> { + let store = app.resources_table().get::>(rid)?; store.clear(); Ok(()) } #[tauri::command] -async fn reset(webview: Webview, rid: ResourceId) -> Result<()> { - let store = webview.resources_table().get::>(rid)?; +async fn reset(app: AppHandle, rid: ResourceId) -> Result<()> { + let store = app.resources_table().get::>(rid)?; store.reset(); Ok(()) } #[tauri::command] -async fn keys(webview: Webview, rid: ResourceId) -> Result> { - let store = webview.resources_table().get::>(rid)?; +async fn keys(app: AppHandle, rid: ResourceId) -> Result> { + let store = app.resources_table().get::>(rid)?; Ok(store.keys()) } #[tauri::command] -async fn values(webview: Webview, rid: ResourceId) -> Result> { - let store = webview.resources_table().get::>(rid)?; +async fn values(app: AppHandle, rid: ResourceId) -> Result> { + let store = app.resources_table().get::>(rid)?; Ok(store.values()) } #[tauri::command] async fn entries( - webview: Webview, + app: AppHandle, rid: ResourceId, ) -> Result> { - let store = webview.resources_table().get::>(rid)?; + let store = app.resources_table().get::>(rid)?; Ok(store.entries()) } #[tauri::command] -async fn length(webview: Webview, rid: ResourceId) -> Result { - let store = webview.resources_table().get::>(rid)?; +async fn length(app: AppHandle, rid: ResourceId) -> Result { + let store = app.resources_table().get::>(rid)?; Ok(store.length()) } #[tauri::command] -async fn load(webview: Webview, rid: ResourceId) -> Result<()> { - let store = webview.resources_table().get::>(rid)?; +async fn load(app: AppHandle, rid: ResourceId) -> Result<()> { + let store = app.resources_table().get::>(rid)?; store.load() } #[tauri::command] -async fn save(webview: Webview, rid: ResourceId) -> Result<()> { - let store = webview.resources_table().get::>(rid)?; +async fn save(app: AppHandle, rid: ResourceId) -> Result<()> { + let store = app.resources_table().get::>(rid)?; store.save() } pub trait StoreExt { - fn store(&self, path: impl AsRef) -> Store; + fn create_store(&self, path: impl AsRef) -> Result>>; fn store_builder(&self, path: impl AsRef) -> StoreBuilder; + fn share_store(&self, store: Arc>); + fn get_store(&self, path: impl AsRef) -> Option>>; } impl> StoreExt for T { - fn store(&self, path: impl AsRef) -> Store { + fn create_store(&self, path: impl AsRef) -> Result>> { StoreBuilder::new(self.app_handle(), path).build() } fn store_builder(&self, path: impl AsRef) -> StoreBuilder { StoreBuilder::new(self.app_handle(), path) } + + fn share_store(&self, store: Arc>) { + let collection = self.state::>(); + let mut stores = collection.stores.lock().unwrap(); + if let Some(path) = store.with_store(|inner_store| { + if stores.contains_key(&inner_store.path) { + None + } else { + Some(inner_store.path.clone()) + } + }) { + let weak_store = Arc::downgrade(&store); + let rid = self.resources_table().add_arc(store); + stores.insert(path, (weak_store, Some(rid))); + } + } + + fn get_store(&self, path: impl AsRef) -> Option>> { + let collection = self.state::>(); + let stores = collection.stores.lock().unwrap(); + stores + .get(path.as_ref()) + .and_then(|(store, _)| store.upgrade()) + } } // #[derive(Default)] pub struct Builder { stores: HashMap>, - // frozen: bool, } impl Default for Builder { fn default() -> Self { Self { stores: Default::default(), - // frozen: false, } } } @@ -179,68 +226,6 @@ impl Builder { Self::default() } - // /// Registers a store with the plugin. - // /// - // /// # Examples - // /// - // /// ``` - // /// use tauri_plugin_store::{StoreBuilder, Builder}; - // /// - // /// tauri::Builder::default() - // /// .setup(|app| { - // /// let store = StoreBuilder::new("store.bin").build(app.handle().clone()); - // /// let builder = Builder::default().store(store); - // /// Ok(()) - // /// }); - // /// ``` - // pub fn store(mut self, store: Store) -> Self { - // self.stores.insert(store.path.clone(), store); - // self - // } - - // /// Registers multiple stores with the plugin. - // /// - // /// # Examples - // /// - // /// ``` - // /// use tauri_plugin_store::{StoreBuilder, Builder}; - // /// - // /// tauri::Builder::default() - // /// .setup(|app| { - // /// let store = StoreBuilder::new("store.bin").build(app.handle().clone()); - // /// let builder = Builder::default().stores([store]); - // /// Ok(()) - // /// }); - // /// ``` - // pub fn stores>>(mut self, stores: T) -> Self { - // self.stores = stores - // .into_iter() - // .map(|store| (store.path.clone(), store)) - // .collect(); - // self - // } - - // /// Freezes the collection. - // /// - // /// This causes requests for plugins that haven't been registered to fail - // /// - // /// # Examples - // /// - // /// ``` - // /// use tauri_plugin_store::{StoreBuilder, Builder}; - // /// - // /// tauri::Builder::default() - // /// .setup(|app| { - // /// let store = StoreBuilder::new("store.bin").build(app.handle().clone()); - // /// app.handle().plugin(Builder::default().freeze().build()); - // /// Ok(()) - // /// }); - // /// ``` - // pub fn freeze(mut self) -> Self { - // self.frozen = true; - // self - // } - /// Builds the plugin. /// /// # Examples @@ -249,7 +234,7 @@ impl Builder { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin").build(); + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin").build()?; /// Ok(()) /// }); /// ``` @@ -257,6 +242,7 @@ impl Builder { plugin::Builder::new("store") .invoke_handler(tauri::generate_handler![ create_store, + get_store, set, get, has, @@ -282,23 +268,20 @@ impl Builder { app_handle.manage(StoreCollection:: { stores: Mutex::new(HashMap::new()), - // frozen: self.frozen, }); Ok(()) }) - .on_event(|_app_handle, event| { + .on_event(|app_handle, event| { if let RunEvent::Exit = event { - // let collection = app_handle.state::>(); - - // for store in collection.stores.lock().expect("mutex poisoned").values_mut() { - // if let Some(sender) = store.auto_save_debounce_sender.take() { - // let _ = sender.send(AutoSaveMessage::Cancel); - // } - // if let Err(err) = store.save() { - // eprintln!("failed to save store {:?} with error {:?}", store.path, err); - // } - // } + let collection = app_handle.state::>(); + let stores = collection.stores.lock().unwrap(); + for (path, (store, _)) in stores.iter() { + let store = store.upgrade().unwrap(); + if let Err(err) = store.save() { + eprintln!("failed to save store {path:?} with error {err:?}"); + } + } } }) .build() diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index d610525d3c..542a52de04 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -41,7 +41,6 @@ pub struct StoreBuilder { app: AppHandle, path: PathBuf, defaults: Option>, - cache: HashMap, serialize: SerializeFn, deserialize: DeserializeFn, auto_save: Option, @@ -65,7 +64,6 @@ impl StoreBuilder { // Since Store.path is only exposed to the user in emit calls we may as well simplify it here already. path: dunce::simplified(path.as_ref()).to_path_buf(), defaults: None, - cache: Default::default(), serialize: default_serialize, deserialize: default_deserialize, auto_save: None, @@ -84,12 +82,11 @@ impl StoreBuilder { /// /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin") /// .defaults(defaults) - /// .build(); + /// .build()?; /// Ok(()) /// }); /// ``` pub fn defaults(mut self, defaults: HashMap) -> Self { - self.cache.clone_from(&defaults); self.defaults = Some(defaults); self } @@ -103,14 +100,13 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin") /// .default("foo".to_string(), "bar") - /// .build(); + /// .build()?; /// Ok(()) /// }); /// ``` pub fn default(mut self, key: impl Into, value: impl Into) -> Self { let key = key.into(); let value = value.into(); - self.cache.insert(key.clone(), value.clone()); self.defaults .get_or_insert(HashMap::new()) .insert(key, value); @@ -126,7 +122,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .serialize(|cache| serde_json::to_vec(&cache).map_err(Into::into)) - /// .build(); + /// .build()?; /// Ok(()) /// }); /// ``` @@ -144,7 +140,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .deserialize(|bytes| serde_json::from_slice(&bytes).map_err(Into::into)) - /// .build(); + /// .build()?; /// Ok(()) /// }); /// ``` @@ -155,17 +151,14 @@ impl StoreBuilder { /// Auto save on modified with a debounce duration /// - /// Note: only works if this store is managed by the plugin (e.g. made using [`crate::with_store`] or inserted into [`crate::Builder`]) - /// /// # Examples /// ``` - /// /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .auto_save(std::time::Duration::from_millis(100)) - /// .build(); + /// .build()?; /// Ok(()) /// }); /// ``` @@ -181,35 +174,35 @@ impl StoreBuilder { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").build(); + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").build()?; /// Ok(()) /// }); /// ``` - pub fn build(self) -> Store { + pub fn build(self) -> crate::Result>> { let collection = self.app.state::>(); let mut stores = collection.stores.lock().unwrap(); - let store = stores - .get(&self.path) - .and_then(|store| store.upgrade()) - .unwrap_or_else(|| { - let mut store = StoreInner::new(self.app.clone(), self.path.clone()); - let _ = store.load(self.deserialize); - let store = Arc::new(Mutex::new(store)); - stores.insert( - self.path.clone(), - Arc::>>::downgrade(&store), - ); - store - }); - drop(stores); - Store { + + if stores.contains_key(&self.path) { + return Err(crate::Error::AlreadyExists(self.path)); + } + + let mut store_inner = StoreInner::new(self.app.clone(), self.path.clone()); + if let Some(defaults) = &self.defaults { + store_inner.cache.clone_from(defaults); + } + let _ = store_inner.load(self.deserialize); + + let store = Store { defaults: self.defaults, serialize: self.serialize, deserialize: self.deserialize, auto_save: self.auto_save, auto_save_debounce_sender: Arc::new(Mutex::new(None)), - store, - } + store: Arc::new(Mutex::new(store_inner)), + }; + let store = Arc::new(store); + stores.insert(self.path, (Arc::downgrade(&store), None)); + Ok(store) } } @@ -372,10 +365,7 @@ pub struct Store { impl Resource for Store {} impl Store { - pub fn with_store( - &self, - f: impl FnOnce(&mut StoreInner) -> crate::Result, - ) -> crate::Result { + pub fn with_store(&self, f: impl FnOnce(&mut StoreInner) -> T) -> T { let mut store = self.store.lock().unwrap(); f(&mut store) } @@ -480,3 +470,17 @@ impl Store { Ok(()) } } + +impl Drop for Store { + fn drop(&mut self) { + let store = self.store.lock().unwrap(); + // Cancel and save if auto save is pending + if let Some(sender) = self.auto_save_debounce_sender.lock().unwrap().take() { + let _ = sender.send(AutoSaveMessage::Cancel); + let _ = store.save(self.serialize); + }; + let collection = store.app.state::>(); + let mut stores = collection.stores.lock().unwrap(); + stores.remove(&store.path); + } +} From 35b439f70fa9e713fde9ed5b2aee440eec212131 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 2 Oct 2024 18:19:31 +0800 Subject: [PATCH 02/70] Enable auto save by default --- plugins/store/guest-js/index.ts | 4 ++-- plugins/store/src/lib.rs | 21 ++++++++++++++++++--- plugins/store/src/store.rs | 20 +++++++++++++++++++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index d173c3f43d..5f910192f6 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -17,9 +17,9 @@ interface ChangePayload { */ export type StoreOptions = { /** - * Auto save on modification with debounce duration in milliseconds + * Auto save on modification with debounce duration in milliseconds, it's 100ms by default, pass in `false` to disable it */ - autoSave?: boolean + autoSave?: boolean | number } /** diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 53c71ea103..63951190da 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -13,7 +13,7 @@ pub use error::{Error, Result}; use log::warn; -use serde::Serialize; +use serde::{Deserialize, Serialize}; pub use serde_json::Value as JsonValue; use std::{ collections::HashMap, @@ -42,15 +42,30 @@ pub struct StoreCollection { stores: Mutex>, Option)>>, } +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +enum AutoSave { + DebounceDuration(u64), + Bool(bool), +} + #[tauri::command] async fn create_store( app: AppHandle, path: PathBuf, - auto_save: Option, + auto_save: Option, ) -> Result { let mut builder = app.store_builder(path.clone()); if let Some(auto_save) = auto_save { - builder = builder.auto_save(Duration::from_millis(auto_save)); + match auto_save { + AutoSave::DebounceDuration(duration) => { + builder = builder.auto_save(Duration::from_millis(duration)); + } + AutoSave::Bool(false) => { + builder = builder.disable_auto_save(); + } + _ => {} + } } let store = builder.build()?; let rid = app.resources_table().add_arc(store); diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 542a52de04..ce9f2fea1e 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -66,7 +66,7 @@ impl StoreBuilder { defaults: None, serialize: default_serialize, deserialize: default_deserialize, - auto_save: None, + auto_save: Some(Duration::from_millis(100)), } } @@ -167,6 +167,24 @@ impl StoreBuilder { self } + /// Auto save on modified with a debounce duration + /// + /// # Examples + /// ``` + /// tauri::Builder::default() + /// .plugin(tauri_plugin_store::Builder::default().build()) + /// .setup(|app| { + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") + /// .auto_save(std::time::Duration::from_millis(100)) + /// .build()?; + /// Ok(()) + /// }); + /// ``` + pub fn disable_auto_save(mut self) -> Self { + self.auto_save = None; + self + } + /// Builds the [`Store`]. /// /// # Examples From ea9c101da8a7c1ca91fe576cf332f9eac56e1240 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 09:33:21 +0800 Subject: [PATCH 03/70] Store to resource table by default --- plugins/store/src/store.rs | 138 +++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 67 deletions(-) diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index ce9f2fea1e..1a34575e46 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -12,7 +12,7 @@ use std::{ sync::{Arc, Mutex}, time::Duration, }; -use tauri::{AppHandle, Emitter, Manager, Resource, Runtime}; +use tauri::{AppHandle, Emitter, Manager, Resource, ResourceId, Runtime}; use tokio::{ select, sync::mpsc::{unbounded_channel, UnboundedSender}, @@ -41,8 +41,8 @@ pub struct StoreBuilder { app: AppHandle, path: PathBuf, defaults: Option>, - serialize: SerializeFn, - deserialize: DeserializeFn, + serialize_fn: SerializeFn, + deserialize_fn: DeserializeFn, auto_save: Option, } @@ -64,8 +64,8 @@ impl StoreBuilder { // Since Store.path is only exposed to the user in emit calls we may as well simplify it here already. path: dunce::simplified(path.as_ref()).to_path_buf(), defaults: None, - serialize: default_serialize, - deserialize: default_deserialize, + serialize_fn: default_serialize, + deserialize_fn: default_deserialize, auto_save: Some(Duration::from_millis(100)), } } @@ -91,7 +91,7 @@ impl StoreBuilder { self } - /// Inserts multiple key-value pairs. + /// Inserts multiple default key-value pairs. /// /// # Examples /// ``` @@ -127,7 +127,7 @@ impl StoreBuilder { /// }); /// ``` pub fn serialize(mut self, serialize: SerializeFn) -> Self { - self.serialize = serialize; + self.serialize_fn = serialize; self } @@ -145,7 +145,7 @@ impl StoreBuilder { /// }); /// ``` pub fn deserialize(mut self, deserialize: DeserializeFn) -> Self { - self.deserialize = deserialize; + self.deserialize_fn = deserialize; self } @@ -167,59 +167,55 @@ impl StoreBuilder { self } - /// Auto save on modified with a debounce duration - /// - /// # Examples - /// ``` - /// tauri::Builder::default() - /// .plugin(tauri_plugin_store::Builder::default().build()) - /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") - /// .auto_save(std::time::Duration::from_millis(100)) - /// .build()?; - /// Ok(()) - /// }); - /// ``` + /// Disable auto save on modified with a debounce duration pub fn disable_auto_save(mut self) -> Self { self.auto_save = None; self } - /// Builds the [`Store`]. - /// - /// # Examples - /// ``` - /// tauri::Builder::default() - /// .plugin(tauri_plugin_store::Builder::default().build()) - /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").build()?; - /// Ok(()) - /// }); - /// ``` - pub fn build(self) -> crate::Result>> { - let collection = self.app.state::>(); + pub(crate) fn build_inner(mut self) -> crate::Result<(Arc>, ResourceId)> { + let collection = self.app.state::(); let mut stores = collection.stores.lock().unwrap(); if stores.contains_key(&self.path) { return Err(crate::Error::AlreadyExists(self.path)); } - let mut store_inner = StoreInner::new(self.app.clone(), self.path.clone()); - if let Some(defaults) = &self.defaults { - store_inner.cache.clone_from(defaults); - } - let _ = store_inner.load(self.deserialize); + let mut store_inner = StoreInner::new( + self.app.clone(), + self.path.clone(), + self.defaults.take(), + self.serialize_fn, + self.deserialize_fn, + ); + let _ = store_inner.load(); let store = Store { - defaults: self.defaults, - serialize: self.serialize, - deserialize: self.deserialize, auto_save: self.auto_save, auto_save_debounce_sender: Arc::new(Mutex::new(None)), store: Arc::new(Mutex::new(store_inner)), }; + let store = Arc::new(store); - stores.insert(self.path, (Arc::downgrade(&store), None)); + let rid = self.app.resources_table().add_arc(store.clone()); + stores.insert(self.path, rid); + + Ok((store, rid)) + } + + /// Builds the [`Store`]. + /// + /// # Examples + /// ``` + /// tauri::Builder::default() + /// .plugin(tauri_plugin_store::Builder::default().build()) + /// .setup(|app| { + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").build()?; + /// Ok(()) + /// }); + /// ``` + pub fn build(self) -> crate::Result>> { + let (store, _) = self.build_inner()?; Ok(store) } } @@ -234,18 +230,30 @@ pub struct StoreInner { pub(crate) app: AppHandle, pub(crate) path: PathBuf, pub(crate) cache: HashMap, + pub(crate) defaults: Option>, + pub(crate) serialize_fn: SerializeFn, + pub(crate) deserialize_fn: DeserializeFn, } impl StoreInner { - pub fn new(app: AppHandle, path: PathBuf) -> Self { + pub fn new( + app: AppHandle, + path: PathBuf, + defaults: Option>, + serialize_fn: SerializeFn, + deserialize_fn: DeserializeFn, + ) -> Self { Self { app, path, - cache: HashMap::new(), + cache: defaults.clone().unwrap_or_default(), + defaults, + serialize_fn, + deserialize_fn, } } - pub fn save(&self, serialize_fn: SerializeFn) -> crate::Result<()> { + pub fn save(&self) -> crate::Result<()> { let app_dir = self .app .path() @@ -255,7 +263,7 @@ impl StoreInner { create_dir_all(store_path.parent().expect("invalid store path"))?; - let bytes = serialize_fn(&self.cache).map_err(crate::Error::Serialize)?; + let bytes = (self.serialize_fn)(&self.cache).map_err(crate::Error::Serialize)?; let mut f = File::create(&store_path)?; f.write_all(&bytes)?; @@ -263,7 +271,7 @@ impl StoreInner { } /// Update the store from the on-disk state - pub fn load(&mut self, deserialize_fn: DeserializeFn) -> crate::Result<()> { + pub fn load(&mut self) -> crate::Result<()> { let app_dir = self .app .path() @@ -274,7 +282,7 @@ impl StoreInner { let bytes = read(store_path)?; self.cache - .extend(deserialize_fn(&bytes).map_err(crate::Error::Deserialize)?); + .extend((self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?); Ok(()) } @@ -310,8 +318,8 @@ impl StoreInner { } } - pub fn reset(&mut self, defaults: &Option>) { - if let Some(defaults) = &defaults { + pub fn reset(&mut self) { + if let Some(defaults) = &self.defaults { for (key, value) in &self.cache { if defaults.get(key) != Some(value) { let _ = @@ -372,9 +380,6 @@ impl std::fmt::Debug for StoreInner { } pub struct Store { - defaults: Option>, - serialize: SerializeFn, - deserialize: DeserializeFn, auto_save: Option, auto_save_debounce_sender: Arc>>>, store: Arc>>, @@ -415,7 +420,7 @@ impl Store { } pub fn reset(&self) { - self.store.lock().unwrap().reset(&self.defaults); + self.store.lock().unwrap().reset(); let _ = self.trigger_auto_save(); } @@ -441,14 +446,14 @@ impl Store { } pub fn load(&self) -> crate::Result<()> { - self.store.lock().unwrap().load(self.deserialize) + self.store.lock().unwrap().load() } pub fn save(&self) -> crate::Result<()> { if let Some(sender) = self.auto_save_debounce_sender.lock().unwrap().take() { let _ = sender.send(AutoSaveMessage::Cancel); } - self.store.lock().unwrap().save(self.serialize) + self.store.lock().unwrap().save() } fn trigger_auto_save(&self) -> crate::Result<()> { @@ -467,7 +472,6 @@ impl Store { auto_save_debounce_sender.replace(sender); drop(auto_save_debounce_sender); let store = self.store.clone(); - let serialize_fn = self.serialize; let auto_save_debounce_sender = self.auto_save_debounce_sender.clone(); tauri::async_runtime::spawn(async move { loop { @@ -479,7 +483,7 @@ impl Store { } _ = sleep(auto_save_delay) => { auto_save_debounce_sender.lock().unwrap().take(); - let _ = store.lock().unwrap().save(serialize_fn); + let _ = store.lock().unwrap().save(); return; } }; @@ -487,18 +491,18 @@ impl Store { }); Ok(()) } -} -impl Drop for Store { - fn drop(&mut self) { - let store = self.store.lock().unwrap(); + fn apply_pending_auto_save(&self) { // Cancel and save if auto save is pending if let Some(sender) = self.auto_save_debounce_sender.lock().unwrap().take() { let _ = sender.send(AutoSaveMessage::Cancel); - let _ = store.save(self.serialize); + let _ = self.save(); }; - let collection = store.app.state::>(); - let mut stores = collection.stores.lock().unwrap(); - stores.remove(&store.path); + } +} + +impl Drop for Store { + fn drop(&mut self) { + self.apply_pending_auto_save(); } } From 98d4fc4e9b61fabe5e3150da9fe164bff34be881 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 09:33:42 +0800 Subject: [PATCH 04/70] Remove share store --- plugins/store/src/lib.rs | 66 ++++++++++------------------------------ 1 file changed, 16 insertions(+), 50 deletions(-) diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 63951190da..713d767dfc 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -18,7 +18,7 @@ pub use serde_json::Value as JsonValue; use std::{ collections::HashMap, path::{Path, PathBuf}, - sync::{Arc, Mutex, Weak}, + sync::{Arc, Mutex}, time::Duration, }; pub use store::{Store, StoreBuilder, StoreInner}; @@ -37,9 +37,8 @@ struct ChangePayload<'a> { value: &'a JsonValue, } -pub struct StoreCollection { - /// This weak pointer is always pointing to a real reference since we will remove it on drop - stores: Mutex>, Option)>>, +pub struct StoreCollection { + stores: Mutex>, } #[derive(Serialize, Deserialize)] @@ -67,32 +66,15 @@ async fn create_store( _ => {} } } - let store = builder.build()?; - let rid = app.resources_table().add_arc(store); - let collection = app.state::>(); - let mut stores = collection.stores.lock().unwrap(); - if let Some((_, resource_id)) = stores.get_mut(&path) { - resource_id.replace(rid); - } + let (_, rid) = builder.build_inner()?; Ok(rid) } #[tauri::command] async fn get_store(app: AppHandle, path: PathBuf) -> Option { - let collection = app.state::>(); - let mut stores = collection.stores.lock().unwrap(); - if let Some((store, resource_id)) = stores.get_mut(&path) { - let rid = if let Some(resource_id) = resource_id { - *resource_id - } else { - let rid = app.resources_table().add_arc(store.upgrade().unwrap()); - resource_id.replace(rid); - rid - }; - Some(rid) - } else { - None - } + let collection = app.state::(); + let stores = collection.stores.lock().unwrap(); + stores.get(&path).copied() } #[tauri::command] @@ -185,7 +167,6 @@ async fn save(app: AppHandle, rid: ResourceId) -> Result<()> { pub trait StoreExt { fn create_store(&self, path: impl AsRef) -> Result>>; fn store_builder(&self, path: impl AsRef) -> StoreBuilder; - fn share_store(&self, store: Arc>); fn get_store(&self, path: impl AsRef) -> Option>>; } @@ -198,28 +179,12 @@ impl> StoreExt for T { StoreBuilder::new(self.app_handle(), path) } - fn share_store(&self, store: Arc>) { - let collection = self.state::>(); - let mut stores = collection.stores.lock().unwrap(); - if let Some(path) = store.with_store(|inner_store| { - if stores.contains_key(&inner_store.path) { - None - } else { - Some(inner_store.path.clone()) - } - }) { - let weak_store = Arc::downgrade(&store); - let rid = self.resources_table().add_arc(store); - stores.insert(path, (weak_store, Some(rid))); - } - } - fn get_store(&self, path: impl AsRef) -> Option>> { - let collection = self.state::>(); + let collection = self.state::(); let stores = collection.stores.lock().unwrap(); stores .get(path.as_ref()) - .and_then(|(store, _)| store.upgrade()) + .and_then(|rid| self.resources_table().get(*rid).ok()) } } @@ -281,7 +246,7 @@ impl Builder { } } - app_handle.manage(StoreCollection:: { + app_handle.manage(StoreCollection { stores: Mutex::new(HashMap::new()), }); @@ -289,12 +254,13 @@ impl Builder { }) .on_event(|app_handle, event| { if let RunEvent::Exit = event { - let collection = app_handle.state::>(); + let collection = app_handle.state::(); let stores = collection.stores.lock().unwrap(); - for (path, (store, _)) in stores.iter() { - let store = store.upgrade().unwrap(); - if let Err(err) = store.save() { - eprintln!("failed to save store {path:?} with error {err:?}"); + for (path, rid) in stores.iter() { + if let Ok(store) = app_handle.resources_table().get::>(*rid) { + if let Err(err) = store.save() { + eprintln!("failed to save store {path:?} with error {err:?}"); + } } } } From 8975d10bb7110e0744184e7b04331f13af780794 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 09:44:46 +0800 Subject: [PATCH 05/70] Clean up --- plugins/store/api-iife.js | 2 +- .../AppSettingsManager/src-tauri/src/main.rs | 10 +-------- plugins/store/guest-js/index.ts | 13 ++++++++---- plugins/store/src/lib.rs | 21 +++++-------------- plugins/store/src/store.rs | 9 +++++++- 5 files changed, 24 insertions(+), 31 deletions(-) diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index 3582aa3bf4..e7b7bea291 100644 --- a/plugins/store/api-iife.js +++ b/plugins/store/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(e){"use strict";var t,r;function a(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}async function i(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;class n{get rid(){return function(e,t,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(e):a?a.value:t.get(e)}(this,t,"f")}constructor(e){t.set(this,void 0),function(e,t,r,a,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");t.set(e,r)}(this,t,e)}async close(){return i("plugin:resources|close",{rid:this.rid})}}async function s(e,t,r){const n={kind:"Any"};return i("plugin:event|listen",{event:e,target:n,handler:a(t)}).then((t=>async()=>async function(e,t){await i("plugin:event|unlisten",{event:e,eventId:t})}(e,t)))}t=new WeakMap,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(r||(r={}));class o extends n{constructor(e,t){super(e),this.path=t}async set(e,t){await i("plugin:store|set",{rid:this.rid,key:e,value:t})}async get(e){return await i("plugin:store|get",{rid:this.rid,key:e})}async has(e){return await i("plugin:store|has",{rid:this.rid,key:e})}async delete(e){return await i("plugin:store|delete",{rid:this.rid,key:e})}async clear(){await i("plugin:store|clear",{rid:this.rid})}async reset(){await i("plugin:store|reset",{rid:this.rid})}async keys(){return await i("plugin:store|keys",{rid:this.rid})}async values(){return await i("plugin:store|values",{rid:this.rid})}async entries(){return await i("plugin:store|entries",{rid:this.rid})}async length(){return await i("plugin:store|length",{rid:this.rid})}async load(){await i("plugin:store|load",{rid:this.rid})}async save(){await i("plugin:store|save",{rid:this.rid})}async onKeyChange(e,t){return await s("store://change",(r=>{r.payload.path===this.path&&r.payload.key===e&&t(r.payload.value)}))}async onChange(e){return await s("store://change",(t=>{t.payload.path===this.path&&e(t.payload.key,t.payload.value)}))}}return e.Store=o,e.createStore=async function(e,t){const r=await i("plugin:store|create_store",{path:e,...t});return new o(r,e)},e.getStore=async function(e){const t=await i("plugin:store|get_store");return new o(t,e)},e}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(e){"use strict";var t,r;function a(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}async function i(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;class n{get rid(){return function(e,t,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(e):a?a.value:t.get(e)}(this,t,"f")}constructor(e){t.set(this,void 0),function(e,t,r,a,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");t.set(e,r)}(this,t,e)}async close(){return i("plugin:resources|close",{rid:this.rid})}}async function s(e,t,r){const n={kind:"Any"};return i("plugin:event|listen",{event:e,target:n,handler:a(t)}).then((t=>async()=>async function(e,t){await i("plugin:event|unlisten",{event:e,eventId:t})}(e,t)))}t=new WeakMap,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(r||(r={}));class o extends n{constructor(e,t){super(e),this.path=t}async set(e,t){await i("plugin:store|set",{rid:this.rid,key:e,value:t})}async get(e){return await i("plugin:store|get",{rid:this.rid,key:e})}async has(e){return await i("plugin:store|has",{rid:this.rid,key:e})}async delete(e){return await i("plugin:store|delete",{rid:this.rid,key:e})}async clear(){await i("plugin:store|clear",{rid:this.rid})}async reset(){await i("plugin:store|reset",{rid:this.rid})}async keys(){return await i("plugin:store|keys",{rid:this.rid})}async values(){return await i("plugin:store|values",{rid:this.rid})}async entries(){return await i("plugin:store|entries",{rid:this.rid})}async length(){return await i("plugin:store|length",{rid:this.rid})}async load(){await i("plugin:store|load",{rid:this.rid})}async save(){await i("plugin:store|save",{rid:this.rid})}async onKeyChange(e,t){return await s("store://change",(r=>{r.payload.path===this.path&&r.payload.key===e&&t(r.payload.value)}))}async onChange(e){return await s("store://change",(t=>{t.payload.path===this.path&&e(t.payload.key,t.payload.value)}))}}return e.Store=o,e.createStore=async function(e,t){const r=await i("plugin:store|create_store",{path:e,...t});return new o(r,e)},e.getStore=async function(e){const t=await i("plugin:store|get_store");return t?new o(t,e):void 0},e}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} diff --git a/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs b/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs index 45ec5989ac..95da5bee90 100644 --- a/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs +++ b/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs @@ -5,8 +5,6 @@ // 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; @@ -19,13 +17,7 @@ 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() - .unwrap(); - app.share_store(store.clone()); + let store = app.handle().store_builder("settings.json").build().unwrap(); app.listen("store://change", |event| { dbg!(event); }); diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 5f910192f6..598a33e93b 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -25,8 +25,13 @@ export type StoreOptions = { /** * @param path: Path to save the store in `app_data_dir` * @param options: Store configuration options + * + * Throws if the store at that path already exists */ -export async function createStore(path: string, options?: StoreOptions) { +export async function createStore( + path: string, + options?: StoreOptions +): Promise { const resourceId = await invoke('plugin:store|create_store', { path, ...options @@ -37,9 +42,9 @@ export async function createStore(path: string, options?: StoreOptions) { /** * @param path: Path of the store in the rust side */ -export async function getStore(path: string) { - const resourceId = await invoke('plugin:store|get_store') - return new Store(resourceId, path) +export async function getStore(path: string): Promise { + const resourceId = await invoke('plugin:store|get_store') + return resourceId ? new Store(resourceId, path) : undefined } /** diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 713d767dfc..a4fd20e92d 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -12,11 +12,11 @@ )] pub use error::{Error, Result}; -use log::warn; use serde::{Deserialize, Serialize}; pub use serde_json::Value as JsonValue; use std::{ collections::HashMap, + marker::PhantomData, path::{Path, PathBuf}, sync::{Arc, Mutex}, time::Duration, @@ -188,15 +188,14 @@ impl> StoreExt for T { } } -// #[derive(Default)] pub struct Builder { - stores: HashMap>, + phantom_data: PhantomData, } impl Default for Builder { fn default() -> Self { Self { - stores: Default::default(), + phantom_data: Default::default(), } } } @@ -218,7 +217,7 @@ impl Builder { /// Ok(()) /// }); /// ``` - pub fn build(mut self) -> TauriPlugin { + pub fn build(self) -> TauriPlugin { plugin::Builder::new("store") .invoke_handler(tauri::generate_handler![ create_store, @@ -237,19 +236,9 @@ impl Builder { save ]) .setup(move |app_handle, _api| { - for (path, store) in self.stores.iter_mut() { - // ignore loading errors, just use the default - if let Err(err) = store.load() { - warn!( - "Failed to load store {path:?} from disk: {err}. Falling back to default values." - ); - } - } - app_handle.manage(StoreCollection { stores: Mutex::new(HashMap::new()), }); - Ok(()) }) .on_event(|app_handle, event| { @@ -259,7 +248,7 @@ impl Builder { for (path, rid) in stores.iter() { if let Ok(store) = app_handle.resources_table().get::>(*rid) { if let Err(err) = store.save() { - eprintln!("failed to save store {path:?} with error {err:?}"); + log::error!("failed to save store {path:?} with error {err:?}"); } } } diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 1a34575e46..6e363c3306 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -385,7 +385,14 @@ pub struct Store { store: Arc>>, } -impl Resource for Store {} +impl Resource for Store { + fn close(self: Arc) { + let store = self.store.lock().unwrap(); + let collection = store.app.state::(); + let mut stores = collection.stores.lock().unwrap(); + stores.remove(&store.path); + } +} impl Store { pub fn with_store(&self, f: impl FnOnce(&mut StoreInner) -> T) -> T { From 4cb9d8de5ef6be74cba6337b8549b336f991a096 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 10:09:43 +0800 Subject: [PATCH 06/70] Add close store --- plugins/store/guest-js/index.ts | 2 +- plugins/store/src/store.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 598a33e93b..c9df7a3190 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -26,7 +26,7 @@ export type StoreOptions = { * @param path: Path to save the store in `app_data_dir` * @param options: Store configuration options * - * Throws if the store at that path already exists + * @throws If a store at that path already exists */ export async function createStore( path: string, diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 6e363c3306..c6c995582c 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -463,6 +463,18 @@ impl Store { self.store.lock().unwrap().save() } + pub fn close_store(self) { + let store = self.store.lock().unwrap(); + let app = store.app.clone(); + let collection = app.state::(); + let stores = collection.stores.lock().unwrap(); + if let Some(rid) = stores.get(&store.path).copied() { + drop(store); + drop(stores); + let _ = app.resources_table().take::>(rid); + } + } + fn trigger_auto_save(&self) -> crate::Result<()> { let Some(auto_save_delay) = self.auto_save else { return Ok(()); From 6cdaa319598cad17c90cb19f7bbae5125ca7385d Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 10:17:47 +0800 Subject: [PATCH 07/70] Add store function --- plugins/store/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index a4fd20e92d..532ee1ab7e 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -165,12 +165,22 @@ async fn save(app: AppHandle, rid: ResourceId) -> Result<()> { } pub trait StoreExt { + /// Create a store or get an existing store with default settings at path + fn store(&self, path: impl AsRef) -> Arc>; + /// Create a store with default settings fn create_store(&self, path: impl AsRef) -> Result>>; + /// Get a store builder fn store_builder(&self, path: impl AsRef) -> StoreBuilder; + /// Get an existing store fn get_store(&self, path: impl AsRef) -> Option>>; } impl> StoreExt for T { + fn store(&self, path: impl AsRef) -> Arc> { + self.create_store(&path) + .unwrap_or_else(|_| self.get_store(path).unwrap()) + } + fn create_store(&self, path: impl AsRef) -> Result>> { StoreBuilder::new(self.app_handle(), path).build() } From 79bc8d08f47a79260486acf65720838c4ce26f62 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 10:52:12 +0800 Subject: [PATCH 08/70] Add lazy store --- examples/api/src/views/Store.svelte | 23 +-- plugins/store/api-iife.js | 2 +- plugins/store/guest-js/index.ts | 304 +++++++++++++++++++++------- plugins/store/src/store.rs | 3 + 4 files changed, 240 insertions(+), 92 deletions(-) diff --git a/examples/api/src/views/Store.svelte b/examples/api/src/views/Store.svelte index d8e6653b50..5c16fe93c8 100644 --- a/examples/api/src/views/Store.svelte +++ b/examples/api/src/views/Store.svelte @@ -1,5 +1,5 @@ diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index e7b7bea291..681126e151 100644 --- a/plugins/store/api-iife.js +++ b/plugins/store/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(e){"use strict";var t,r;function a(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}async function i(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;class n{get rid(){return function(e,t,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(e):a?a.value:t.get(e)}(this,t,"f")}constructor(e){t.set(this,void 0),function(e,t,r,a,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");t.set(e,r)}(this,t,e)}async close(){return i("plugin:resources|close",{rid:this.rid})}}async function s(e,t,r){const n={kind:"Any"};return i("plugin:event|listen",{event:e,target:n,handler:a(t)}).then((t=>async()=>async function(e,t){await i("plugin:event|unlisten",{event:e,eventId:t})}(e,t)))}t=new WeakMap,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(r||(r={}));class o extends n{constructor(e,t){super(e),this.path=t}async set(e,t){await i("plugin:store|set",{rid:this.rid,key:e,value:t})}async get(e){return await i("plugin:store|get",{rid:this.rid,key:e})}async has(e){return await i("plugin:store|has",{rid:this.rid,key:e})}async delete(e){return await i("plugin:store|delete",{rid:this.rid,key:e})}async clear(){await i("plugin:store|clear",{rid:this.rid})}async reset(){await i("plugin:store|reset",{rid:this.rid})}async keys(){return await i("plugin:store|keys",{rid:this.rid})}async values(){return await i("plugin:store|values",{rid:this.rid})}async entries(){return await i("plugin:store|entries",{rid:this.rid})}async length(){return await i("plugin:store|length",{rid:this.rid})}async load(){await i("plugin:store|load",{rid:this.rid})}async save(){await i("plugin:store|save",{rid:this.rid})}async onKeyChange(e,t){return await s("store://change",(r=>{r.payload.path===this.path&&r.payload.key===e&&t(r.payload.value)}))}async onChange(e){return await s("store://change",(t=>{t.payload.path===this.path&&e(t.payload.key,t.payload.value)}))}}return e.Store=o,e.createStore=async function(e,t){const r=await i("plugin:store|create_store",{path:e,...t});return new o(r,e)},e.getStore=async function(e){const t=await i("plugin:store|get_store");return t?new o(t,e):void 0},e}({});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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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,e){super(t),this.path=e}static async createStore(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new u(a,t)}static async getStore(t){const e=await s("plugin:store|get_store");return e?new u(e,t):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){return await s("plugin:store|get",{rid:this.rid,key:t})}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 load(){await s("plugin:store|load",{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.path===this.path&&a.payload.key===t&&e(a.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.path===this.path&&t(e.payload.key,e.payload.value)}))}}return t.LazyStore=class{constructor(t,e){this.path=t,this.options=e}get store(){return this._store||(this._store=o(this.path,this.options).catch((async()=>await c(this.path)))),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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,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 c9df7a3190..15e55681da 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -32,26 +32,110 @@ export async function createStore( path: string, options?: StoreOptions ): Promise { - const resourceId = await invoke('plugin:store|create_store', { - path, - ...options - }) - return new Store(resourceId, path) + return await Store.createStore(path, options) } /** * @param path: Path of the store in the rust side */ export async function getStore(path: string): Promise { - const resourceId = await invoke('plugin:store|get_store') - return resourceId ? new Store(resourceId, path) : undefined + return await Store.getStore(path) } /** - * A key-value store persisted by the backend layer. + * A lazy loaded key-value store persisted by the backend layer. + * + * Note that the options are not applied if someone else already created the store */ -export class Store extends Resource { +export class LazyStore implements IStore { + private _store?: Promise constructor( + private readonly path: string, + private readonly options?: StoreOptions + ) {} + + public get store(): Promise { + if (!this._store) { + this._store = createStore(this.path, this.options).catch( + async () => (await getStore(this.path))! + ) + } + return this._store + } + + async set(key: string, value: unknown): Promise { + return (await this.store).set(key, value) + } + + async get(key: string): Promise { + return (await this.store).get(key) + } + + async has(key: string): Promise { + return (await this.store).has(key) + } + + async delete(key: string): Promise { + return (await this.store).delete(key) + } + + async clear(): Promise { + await (await this.store).clear() + } + + async reset(): Promise { + await (await this.store).reset() + } + + async keys(): Promise { + return (await this.store).keys() + } + + async values(): Promise { + return (await this.store).values() + } + + async entries(): Promise> { + return (await this.store).entries() + } + + async length(): Promise { + return (await this.store).length() + } + + async load(): Promise { + await (await this.store).load() + } + + async save(): Promise { + await (await this.store).save() + } + + async onKeyChange( + key: string, + cb: (value: T | null) => void + ): Promise { + return (await this.store).onKeyChange(key, cb) + } + + async onChange( + cb: (key: string, value: T | null) => void + ): Promise { + return (await this.store).onChange(cb) + } + + async close(): Promise { + if (this._store) { + await (await this._store).close() + } + } +} + +/** + * A key-value store persisted by the backend layer. + */ +export class Store extends Resource implements IStore { + private constructor( rid: number, private readonly path: string ) { @@ -59,12 +143,30 @@ export class Store extends Resource { } /** - * Inserts a key-value pair into the store. + * @param path: Path to save the store in `app_data_dir` + * @param options: Store configuration options * - * @param key - * @param value - * @returns + * @throws If a store at that path already exists + */ + static async createStore( + path: string, + options?: StoreOptions + ): Promise { + const resourceId = await invoke('plugin:store|create_store', { + path, + ...options + }) + return new Store(resourceId, path) + } + + /** + * @param path: Path of the store in the rust side */ + static async getStore(path: string): Promise { + const resourceId = await invoke('plugin:store|get_store') + return resourceId ? new Store(resourceId, path) : undefined + } + async set(key: string, value: unknown): Promise { await invoke('plugin:store|set', { rid: this.rid, @@ -73,12 +175,6 @@ export class Store extends Resource { }) } - /** - * Returns the value for the given `key` or `null` the key does not exist. - * - * @param key - * @returns - */ async get(key: string): Promise { return await invoke('plugin:store|get', { rid: this.rid, @@ -86,12 +182,6 @@ export class Store extends Resource { }) } - /** - * Returns `true` if the given `key` exists in the store. - * - * @param key - * @returns - */ async has(key: string): Promise { return await invoke('plugin:store|has', { rid: this.rid, @@ -99,12 +189,6 @@ export class Store extends Resource { }) } - /** - * Removes a key-value pair from the store. - * - * @param key - * @returns - */ async delete(key: string): Promise { return await invoke('plugin:store|delete', { rid: this.rid, @@ -112,84 +196,156 @@ export class Store extends Resource { }) } + async clear(): Promise { + await invoke('plugin:store|clear', { rid: this.rid }) + } + + async reset(): Promise { + await invoke('plugin:store|reset', { rid: this.rid }) + } + + async keys(): Promise { + return await invoke('plugin:store|keys', { rid: this.rid }) + } + + async values(): Promise { + return await invoke('plugin:store|values', { rid: this.rid }) + } + + async entries(): Promise> { + return await invoke('plugin:store|entries', { rid: this.rid }) + } + + async length(): Promise { + return await invoke('plugin:store|length', { rid: this.rid }) + } + + async load(): Promise { + await invoke('plugin:store|load', { rid: this.rid }) + } + + async save(): Promise { + await invoke('plugin:store|save', { rid: this.rid }) + } + + async onKeyChange( + key: string, + cb: (value: T | null) => void + ): Promise { + return await listen>('store://change', (event) => { + if (event.payload.path === this.path && event.payload.key === key) { + cb(event.payload.value) + } + }) + } + + async onChange( + cb: (key: string, value: T | null) => void + ): Promise { + return await listen>('store://change', (event) => { + if (event.payload.path === this.path) { + cb(event.payload.key, event.payload.value) + } + }) + } +} + +interface IStore { + /** + * Inserts a key-value pair into the store. + * + * @param key + * @param value + * @returns + */ + set(key: string, value: unknown): Promise + + /** + * Returns the value for the given `key` or `null` if the key does not exist. + * + * @param key + * @returns + */ + get(key: string): Promise + + /** + * Returns `true` if the given `key` exists in the store. + * + * @param key + * @returns + */ + has(key: string): Promise + + /** + * Removes a key-value pair from the store. + * + * @param key + * @returns + */ + delete(key: string): Promise + /** * Clears the store, removing all key-value pairs. * - * Note: To clear the storage and reset it to it's `default` value, use `reset` instead. + * Note: To clear the storage and reset it to its `default` value, use `reset` instead. * @returns */ - async clear(): Promise { - await invoke('plugin:store|clear', { rid: this.rid }) - } + clear(): Promise /** - * Resets the store to it's `default` value. + * Resets the store to its `default` value. * * If no default value has been set, this method behaves identical to `clear`. * @returns */ - async reset(): Promise { - await invoke('plugin:store|reset', { rid: this.rid }) - } + reset(): Promise /** - * Returns a list of all key in the store. + * Returns a list of all keys in the store. * * @returns */ - async keys(): Promise { - return await invoke('plugin:store|keys', { rid: this.rid }) - } + keys(): Promise /** * Returns a list of all values in the store. * * @returns */ - async values(): Promise { - return await invoke('plugin:store|values', { rid: this.rid }) - } + values(): Promise /** * Returns a list of all entries in the store. * * @returns */ - async entries(): Promise> { - return await invoke('plugin:store|entries', { rid: this.rid }) - } + entries(): Promise> /** * Returns the number of key-value pairs in the store. * * @returns */ - async length(): Promise { - return await invoke('plugin:store|length', { rid: this.rid }) - } + length(): Promise /** - * Attempts to load the on-disk state at the stores `path` into memory. + * Attempts to load the on-disk state at the store's `path` into memory. * * 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. * @returns */ - async load(): Promise { - await invoke('plugin:store|load', { rid: this.rid }) - } + load(): Promise /** - * Saves the store to disk at the stores `path`. + * Saves the store to disk at the store's `path`. * - * As the store is only persisted to disk before the apps exit, changes might be lost in a crash. + * As the store is only persisted to disk before the app's exit, changes might be lost in a crash. * This method lets you persist the store to disk whenever you deem necessary. * @returns */ - async save(): Promise { - await invoke('plugin:store|save', { rid: this.rid }) - } + save(): Promise /** * Listen to changes on a store key. @@ -199,16 +355,10 @@ export class Store extends Resource { * * @since 2.0.0 */ - async onKeyChange( + onKeyChange( key: string, cb: (value: T | null) => void - ): Promise { - return await listen>('store://change', (event) => { - if (event.payload.path === this.path && event.payload.key === key) { - cb(event.payload.value) - } - }) - } + ): Promise /** * Listen to changes on the store. @@ -217,13 +367,11 @@ export class Store extends Resource { * * @since 2.0.0 */ - async onChange( - cb: (key: string, value: T | null) => void - ): Promise { - return await listen>('store://change', (event) => { - if (event.payload.path === this.path) { - cb(event.payload.key, event.payload.value) - } - }) - } + onChange(cb: (key: string, value: T | null) => void): Promise + + /** + * Close the store and cleans up this resource from memory. + * **You should not call any method on this object anymore and should drop any reference to it.** + */ + close(): Promise } diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index c6c995582c..aba7f90245 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -463,6 +463,9 @@ impl Store { self.store.lock().unwrap().save() } + /// Removes the store from the resource table, + /// this doesn't remove other references of the same store held by you, + /// and the store is only truely closed once all reference held by you are also dropped pub fn close_store(self) { let store = self.store.lock().unwrap(); let app = store.app.clone(); From bbd34b16f4e04e05916b33e9c7d1f6ecc95163a5 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 11:48:19 +0800 Subject: [PATCH 09/70] Add init to lazy store --- plugins/store/api-iife.js | 2 +- plugins/store/guest-js/index.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index 681126e151..77043eaebb 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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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,e){super(t),this.path=e}static async createStore(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new u(a,t)}static async getStore(t){const e=await s("plugin:store|get_store");return e?new u(e,t):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){return await s("plugin:store|get",{rid:this.rid,key:t})}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 load(){await s("plugin:store|load",{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.path===this.path&&a.payload.key===t&&e(a.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.path===this.path&&t(e.payload.key,e.payload.value)}))}}return t.LazyStore=class{constructor(t,e){this.path=t,this.options=e}get store(){return this._store||(this._store=o(this.path,this.options).catch((async()=>await c(this.path)))),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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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,e){super(t),this.path=e}static async createStore(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new u(a,t)}static async getStore(t){const e=await s("plugin:store|get_store");return e?new u(e,t):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){return await s("plugin:store|get",{rid:this.rid,key:t})}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 load(){await s("plugin:store|load",{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.path===this.path&&a.payload.key===t&&e(a.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.path===this.path&&t(e.payload.key,e.payload.value)}))}}return t.LazyStore=class{constructor(t,e){this.path=t,this.options=e}get store(){return this._store||(this._store=o(this.path,this.options).catch((async()=>await c(this.path)))),this._store}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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,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 15e55681da..2c2be47687 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -54,7 +54,7 @@ export class LazyStore implements IStore { private readonly options?: StoreOptions ) {} - public get store(): Promise { + private get store(): Promise { if (!this._store) { this._store = createStore(this.path, this.options).catch( async () => (await getStore(this.path))! @@ -63,6 +63,13 @@ export class LazyStore implements IStore { return this._store } + /** + * Init/load the store if it's not already + */ + async init(): Promise { + await this.store + } + async set(key: string, value: unknown): Promise { return (await this.store).set(key, value) } From 17d910b3818c0ce0f39c531aad85a5ce54ab15c4 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 12:00:35 +0800 Subject: [PATCH 10/70] refresh cache in example --- examples/api/src/views/Store.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/api/src/views/Store.svelte b/examples/api/src/views/Store.svelte index 5c16fe93c8..48288a5079 100644 --- a/examples/api/src/views/Store.svelte +++ b/examples/api/src/views/Store.svelte @@ -23,6 +23,7 @@ await store.set(key, value); const v = await store.get(key); cache[key] = v; + cache = cache; } catch (error) { onMessage(error); } From 7ffd769240b1973613259bd7b2e5d999db1ce329 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 13:34:38 +0800 Subject: [PATCH 11/70] Add get-or-create-store --- plugins/store/build.rs | 1 + plugins/store/guest-js/index.ts | 33 +++++++++++++++++-- .../commands/get_or_create_store.toml | 13 ++++++++ .../permissions/autogenerated/reference.md | 26 +++++++++++++++ plugins/store/permissions/default.toml | 2 ++ plugins/store/permissions/schemas/schema.json | 10 ++++++ plugins/store/src/lib.rs | 14 ++++++++ 7 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 plugins/store/permissions/autogenerated/commands/get_or_create_store.toml diff --git a/plugins/store/build.rs b/plugins/store/build.rs index a144df296a..5d30417d0c 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -5,6 +5,7 @@ const COMMANDS: &[&str] = &[ "create_store", "get_store", + "get_or_create_store", "set", "get", "has", diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 2c2be47687..d21ce45c3b 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -42,6 +42,17 @@ export async function getStore(path: string): Promise { return await Store.getStore(path) } +/** + * @param path: Path to save the store in `app_data_dir` + * @param options: Store configuration options + */ +export async function getOrCreateStore( + path: string, + options?: StoreOptions +): Promise { + return await Store.getOrCreateStore(path, options) +} + /** * A lazy loaded key-value store persisted by the backend layer. * @@ -56,9 +67,7 @@ export class LazyStore implements IStore { private get store(): Promise { if (!this._store) { - this._store = createStore(this.path, this.options).catch( - async () => (await getStore(this.path))! - ) + this._store = getOrCreateStore(this.path, this.options) } return this._store } @@ -174,6 +183,24 @@ export class Store extends Resource implements IStore { return resourceId ? new Store(resourceId, path) : undefined } + /** + * @param path: Path to save the store in `app_data_dir` + * @param options: Store configuration options + */ + static async getOrCreateStore( + path: string, + options?: StoreOptions + ): Promise { + const resourceId = await invoke( + 'plugin:store|get_or_create_store', + { + path, + ...options + } + ) + return new Store(resourceId, path) + } + async set(key: string, value: unknown): Promise { await invoke('plugin:store|set', { rid: this.rid, diff --git a/plugins/store/permissions/autogenerated/commands/get_or_create_store.toml b/plugins/store/permissions/autogenerated/commands/get_or_create_store.toml new file mode 100644 index 0000000000..73f12fd7d6 --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/get_or_create_store.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-get-or-create-store" +description = "Enables the get_or_create_store command without any pre-configured scope." +commands.allow = ["get_or_create_store"] + +[[permission]] +identifier = "deny-get-or-create-store" +description = "Denies the get_or_create_store command without any pre-configured scope." +commands.deny = ["get_or_create_store"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index ce85734605..203bdc7a80 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -165,6 +165,32 @@ Denies the get command without any pre-configured scope. +`store:allow-get-or-create-store` + + + + +Enables the get_or_create_store command without any pre-configured scope. + + + + + + + +`store:deny-get-or-create-store` + + + + +Denies the get_or_create_store command without any pre-configured scope. + + + + + + + `store:allow-get-store` diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index bf888679ff..225a4b261a 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -12,6 +12,8 @@ All operations are enabled by default. """ permissions = [ "allow-create-store", + "allow-get-store", + "allow-get-or-create-store", "allow-clear", "allow-delete", "allow-entries", diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index bfd50f9cb6..69ef4f64ad 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -344,6 +344,16 @@ "type": "string", "const": "deny-get" }, + { + "description": "Enables the get_or_create_store command without any pre-configured scope.", + "type": "string", + "const": "allow-get-or-create-store" + }, + { + "description": "Denies the get_or_create_store command without any pre-configured scope.", + "type": "string", + "const": "deny-get-or-create-store" + }, { "description": "Enables the get_store command without any pre-configured scope.", "type": "string", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 532ee1ab7e..ac444aef2b 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -77,6 +77,19 @@ async fn get_store(app: AppHandle, path: PathBuf) -> Option( + app: AppHandle, + path: PathBuf, + auto_save: Option, +) -> Result { + if let Some(rid) = get_store(app.clone(), path.clone()).await { + Ok(rid) + } else { + create_store(app, path, auto_save).await + } +} + #[tauri::command] async fn set( app: AppHandle, @@ -232,6 +245,7 @@ impl Builder { .invoke_handler(tauri::generate_handler![ create_store, get_store, + get_or_create_store, set, get, has, From 3acf66ee57f92de710b653f08d9bac1d5d183f65 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 13:53:59 +0800 Subject: [PATCH 12/70] Revert "Add get-or-create-store" This reverts commit 7ffd769240b1973613259bd7b2e5d999db1ce329. --- plugins/store/build.rs | 1 - plugins/store/guest-js/index.ts | 33 ++----------------- .../commands/get_or_create_store.toml | 13 -------- .../permissions/autogenerated/reference.md | 26 --------------- plugins/store/permissions/default.toml | 2 -- plugins/store/permissions/schemas/schema.json | 10 ------ plugins/store/src/lib.rs | 14 -------- 7 files changed, 3 insertions(+), 96 deletions(-) delete mode 100644 plugins/store/permissions/autogenerated/commands/get_or_create_store.toml diff --git a/plugins/store/build.rs b/plugins/store/build.rs index 5d30417d0c..a144df296a 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -5,7 +5,6 @@ const COMMANDS: &[&str] = &[ "create_store", "get_store", - "get_or_create_store", "set", "get", "has", diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index d21ce45c3b..2c2be47687 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -42,17 +42,6 @@ export async function getStore(path: string): Promise { return await Store.getStore(path) } -/** - * @param path: Path to save the store in `app_data_dir` - * @param options: Store configuration options - */ -export async function getOrCreateStore( - path: string, - options?: StoreOptions -): Promise { - return await Store.getOrCreateStore(path, options) -} - /** * A lazy loaded key-value store persisted by the backend layer. * @@ -67,7 +56,9 @@ export class LazyStore implements IStore { private get store(): Promise { if (!this._store) { - this._store = getOrCreateStore(this.path, this.options) + this._store = createStore(this.path, this.options).catch( + async () => (await getStore(this.path))! + ) } return this._store } @@ -183,24 +174,6 @@ export class Store extends Resource implements IStore { return resourceId ? new Store(resourceId, path) : undefined } - /** - * @param path: Path to save the store in `app_data_dir` - * @param options: Store configuration options - */ - static async getOrCreateStore( - path: string, - options?: StoreOptions - ): Promise { - const resourceId = await invoke( - 'plugin:store|get_or_create_store', - { - path, - ...options - } - ) - return new Store(resourceId, path) - } - async set(key: string, value: unknown): Promise { await invoke('plugin:store|set', { rid: this.rid, diff --git a/plugins/store/permissions/autogenerated/commands/get_or_create_store.toml b/plugins/store/permissions/autogenerated/commands/get_or_create_store.toml deleted file mode 100644 index 73f12fd7d6..0000000000 --- a/plugins/store/permissions/autogenerated/commands/get_or_create_store.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-get-or-create-store" -description = "Enables the get_or_create_store command without any pre-configured scope." -commands.allow = ["get_or_create_store"] - -[[permission]] -identifier = "deny-get-or-create-store" -description = "Denies the get_or_create_store command without any pre-configured scope." -commands.deny = ["get_or_create_store"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 203bdc7a80..ce85734605 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -165,32 +165,6 @@ Denies the get command without any pre-configured scope. -`store:allow-get-or-create-store` - - - - -Enables the get_or_create_store command without any pre-configured scope. - - - - - - - -`store:deny-get-or-create-store` - - - - -Denies the get_or_create_store command without any pre-configured scope. - - - - - - - `store:allow-get-store` diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index 225a4b261a..bf888679ff 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -12,8 +12,6 @@ All operations are enabled by default. """ permissions = [ "allow-create-store", - "allow-get-store", - "allow-get-or-create-store", "allow-clear", "allow-delete", "allow-entries", diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index 69ef4f64ad..bfd50f9cb6 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -344,16 +344,6 @@ "type": "string", "const": "deny-get" }, - { - "description": "Enables the get_or_create_store command without any pre-configured scope.", - "type": "string", - "const": "allow-get-or-create-store" - }, - { - "description": "Denies the get_or_create_store command without any pre-configured scope.", - "type": "string", - "const": "deny-get-or-create-store" - }, { "description": "Enables the get_store command without any pre-configured scope.", "type": "string", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index ac444aef2b..532ee1ab7e 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -77,19 +77,6 @@ async fn get_store(app: AppHandle, path: PathBuf) -> Option( - app: AppHandle, - path: PathBuf, - auto_save: Option, -) -> Result { - if let Some(rid) = get_store(app.clone(), path.clone()).await { - Ok(rid) - } else { - create_store(app, path, auto_save).await - } -} - #[tauri::command] async fn set( app: AppHandle, @@ -245,7 +232,6 @@ impl Builder { .invoke_handler(tauri::generate_handler![ create_store, get_store, - get_or_create_store, set, get, has, From 42c5a1cb5613eacee315bcecd2b0127c4e59ddcf Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 13:56:43 +0800 Subject: [PATCH 13/70] try get first --- plugins/store/api-iife.js | 2 +- plugins/store/guest-js/index.ts | 4 ++-- plugins/store/permissions/autogenerated/reference.md | 1 + plugins/store/permissions/default.toml | 1 + plugins/store/src/lib.rs | 4 ++-- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index 77043eaebb..cd3559d3d8 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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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,e){super(t),this.path=e}static async createStore(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new u(a,t)}static async getStore(t){const e=await s("plugin:store|get_store");return e?new u(e,t):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){return await s("plugin:store|get",{rid:this.rid,key:t})}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 load(){await s("plugin:store|load",{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.path===this.path&&a.payload.key===t&&e(a.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.path===this.path&&t(e.payload.key,e.payload.value)}))}}return t.LazyStore=class{constructor(t,e){this.path=t,this.options=e}get store(){return this._store||(this._store=o(this.path,this.options).catch((async()=>await c(this.path)))),this._store}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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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,e){super(t),this.path=e}static async createStore(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new u(a,t)}static async getStore(t){const e=await s("plugin:store|get_store");return e?new u(e,t):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){return await s("plugin:store|get",{rid:this.rid,key:t})}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 load(){await s("plugin:store|load",{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.path===this.path&&a.payload.key===t&&e(a.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.path===this.path&&t(e.payload.key,e.payload.value)}))}}return t.LazyStore=class{constructor(t,e){this.path=t,this.options=e}get store(){return this._store||(this._store=c(this.path).then((async t=>t||await o(this.path,this.options)))),this._store}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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,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 2c2be47687..b42ed0c647 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -56,8 +56,8 @@ export class LazyStore implements IStore { private get store(): Promise { if (!this._store) { - this._store = createStore(this.path, this.options).catch( - async () => (await getStore(this.path))! + this._store = getStore(this.path).then( + async (store) => store || (await createStore(this.path, this.options)) ) } return this._store diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index ce85734605..8d05169d33 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -10,6 +10,7 @@ All operations are enabled by default. - `allow-create-store` +- `allow-get-store` - `allow-clear` - `allow-delete` - `allow-entries` diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index bf888679ff..3a38e6e8b2 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -12,6 +12,7 @@ All operations are enabled by default. """ permissions = [ "allow-create-store", + "allow-get-store", "allow-clear", "allow-delete", "allow-entries", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 532ee1ab7e..dbc53d5f3f 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -177,8 +177,8 @@ pub trait StoreExt { impl> StoreExt for T { fn store(&self, path: impl AsRef) -> Arc> { - self.create_store(&path) - .unwrap_or_else(|_| self.get_store(path).unwrap()) + self.get_store(&path) + .unwrap_or_else(|| self.create_store(&path).unwrap()) } fn create_store(&self, path: impl AsRef) -> Result>> { From 45b2af4cfed764a4e7c49de5884365c9242595fe Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 15:23:22 +0800 Subject: [PATCH 14/70] Docs --- plugins/store/guest-js/index.ts | 7 ++----- plugins/store/src/store.rs | 36 ++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index b42ed0c647..47f15f3452 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -294,7 +294,7 @@ interface IStore { /** * Clears the store, removing all key-value pairs. * - * Note: To clear the storage and reset it to its `default` value, use `reset` instead. + * Note: To clear the storage and reset it to its `default` value, use {@linkcode reset} instead. * @returns */ clear(): Promise @@ -302,7 +302,7 @@ interface IStore { /** * Resets the store to its `default` value. * - * If no default value has been set, this method behaves identical to `clear`. + * If no default value has been set, this method behaves identical to {@linkcode clear}. * @returns */ reset(): Promise @@ -347,9 +347,6 @@ interface IStore { /** * Saves the store to disk at the store's `path`. - * - * As the store is only persisted to disk before the app's exit, changes might be lost in a crash. - * This method lets you persist the store to disk whenever you deem necessary. * @returns */ save(): Promise diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index aba7f90245..15c5f4acbc 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -236,7 +236,7 @@ pub struct StoreInner { } impl StoreInner { - pub fn new( + fn new( app: AppHandle, path: PathBuf, defaults: Option>, @@ -253,6 +253,7 @@ impl StoreInner { } } + /// Saves the store to disk at the store's `path`. pub fn save(&self) -> crate::Result<()> { let app_dir = self .app @@ -287,6 +288,7 @@ impl StoreInner { Ok(()) } + /// Inserts a key-value pair into the store. pub fn insert(&mut self, key: impl Into, value: impl Into) { let key = key.into(); let value = value.into(); @@ -294,14 +296,17 @@ impl StoreInner { let _ = self.emit_change_event(&key, &value); } + /// Returns a reference to the value corresponding to the key. pub fn get(&self, key: impl AsRef) -> Option<&JsonValue> { self.cache.get(key.as_ref()) } + /// Returns `true` if the given `key` exists in the store. pub fn has(&self, key: impl AsRef) -> bool { self.cache.contains_key(key.as_ref()) } + /// Removes a key-value pair from the store. pub fn delete(&mut self, key: impl AsRef) -> bool { let flag = self.cache.remove(key.as_ref()).is_some(); if flag { @@ -310,6 +315,9 @@ impl StoreInner { flag } + /// Clears the store, removing all key-value pairs. + /// + /// Note: To clear the storage and reset it to its `default` value, use [`reset`](Self::reset) instead. pub fn clear(&mut self) { let keys: Vec = self.cache.keys().cloned().collect(); self.cache.clear(); @@ -318,6 +326,9 @@ impl StoreInner { } } + /// Resets the store to its `default` value. + /// + /// If no default value has been set, this method behaves identical to [`clear`](Self::clear). pub fn reset(&mut self) { if let Some(defaults) = &self.defaults { for (key, value) in &self.cache { @@ -337,22 +348,27 @@ impl StoreInner { } } + /// An iterator visiting all keys in arbitrary order. pub fn keys(&self) -> impl Iterator { self.cache.keys() } + /// An iterator visiting all values in arbitrary order. pub fn values(&self) -> impl Iterator { self.cache.values() } + /// An iterator visiting all key-value pairs in arbitrary order. pub fn entries(&self) -> impl Iterator { self.cache.iter() } + /// Returns the number of elements in the store. pub fn len(&self) -> usize { self.cache.len() } + /// Returns true if the store contains no elements. pub fn is_empty(&self) -> bool { self.cache.is_empty() } @@ -395,24 +411,30 @@ impl Resource for Store { } impl Store { + /// Do something with the inner store, + /// useful for batching some work if you need higher performance pub fn with_store(&self, f: impl FnOnce(&mut StoreInner) -> T) -> T { let mut store = self.store.lock().unwrap(); f(&mut store) } + /// Inserts a key-value pair into the store. pub fn set(&self, key: impl Into, value: impl Into) { self.store.lock().unwrap().insert(key.into(), value.into()); let _ = self.trigger_auto_save(); } + /// Returns the value for the given `key` or `None` if the key does not exist. pub fn get(&self, key: impl AsRef) -> Option { self.store.lock().unwrap().get(key).cloned() } + /// Returns `true` if the given `key` exists in the store. pub fn has(&self, key: impl AsRef) -> bool { self.store.lock().unwrap().has(key) } + /// Removes a key-value pair from the store. pub fn delete(&self, key: impl AsRef) -> bool { let deleted = self.store.lock().unwrap().delete(key); if deleted { @@ -421,24 +443,33 @@ impl Store { deleted } + /// Clears the store, removing all key-value pairs. + /// + /// Note: To clear the storage and reset it to its `default` value, use [`reset`](Self::reset) instead. pub fn clear(&self) { self.store.lock().unwrap().clear(); let _ = self.trigger_auto_save(); } + /// Resets the store to its `default` value. + /// + /// If no default value has been set, this method behaves identical to [`clear`](Self::clear). pub fn reset(&self) { self.store.lock().unwrap().reset(); let _ = self.trigger_auto_save(); } + /// Returns a list of all keys in the store. pub fn keys(&self) -> Vec { self.store.lock().unwrap().keys().cloned().collect() } + /// Returns a list of all values in the store. pub fn values(&self) -> Vec { self.store.lock().unwrap().values().cloned().collect() } + /// Returns a list of all key-value pairs in the store. pub fn entries(&self) -> Vec<(String, JsonValue)> { self.store .lock() @@ -448,14 +479,17 @@ impl Store { .collect() } + /// Returns the number of elements in the store. pub fn length(&self) -> usize { self.store.lock().unwrap().len() } + /// Update the store from the on-disk state pub fn load(&self) -> crate::Result<()> { self.store.lock().unwrap().load() } + /// 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() { let _ = sender.send(AutoSaveMessage::Cancel); From d22de0257d85877f8fd07164fe3e17d93845f530 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 16:11:53 +0800 Subject: [PATCH 15/70] Use absolute path for store --- plugins/store/api-iife.js | 2 +- plugins/store/guest-js/index.ts | 25 ++++++++++++++++-------- plugins/store/src/lib.rs | 2 ++ plugins/store/src/store.rs | 34 ++++++++++++++------------------- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index cd3559d3d8..9b27a2dc2c 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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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,e){super(t),this.path=e}static async createStore(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new u(a,t)}static async getStore(t){const e=await s("plugin:store|get_store");return e?new u(e,t):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){return await s("plugin:store|get",{rid:this.rid,key:t})}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 load(){await s("plugin:store|load",{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.path===this.path&&a.payload.key===t&&e(a.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.path===this.path&&t(e.payload.key,e.payload.value)}))}}return t.LazyStore=class{constructor(t,e){this.path=t,this.options=e}get store(){return this._store||(this._store=c(this.path).then((async t=>t||await o(this.path,this.options)))),this._store}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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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"}(r||(r={}));class u extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new u(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new u(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){return await s("plugin:store|get",{rid:this.rid,key:t})}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 load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.value)}))}}return t.LazyStore=class{constructor(t,e){this.path=t,this.options=e}get store(){return this._store||(this._store=c(this.path).then((async t=>t||await o(this.path,this.options)))),this._store}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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,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 47f15f3452..69e4034852 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -8,6 +8,7 @@ import { invoke, Resource } from '@tauri-apps/api/core' interface ChangePayload { path: string + resourceId?: number key: string value: T | null } @@ -143,8 +144,8 @@ export class LazyStore implements IStore { */ export class Store extends Resource implements IStore { private constructor( - rid: number, - private readonly path: string + rid: number + // private readonly path: string ) { super(rid) } @@ -159,19 +160,27 @@ export class Store extends Resource implements IStore { path: string, options?: StoreOptions ): Promise { - const resourceId = await invoke('plugin:store|create_store', { + const rid = await invoke('plugin:store|create_store', { path, ...options }) - return new Store(resourceId, path) + return new Store( + rid + // path + ) } /** * @param path: Path of the store in the rust side */ static async getStore(path: string): Promise { - const resourceId = await invoke('plugin:store|get_store') - return resourceId ? new Store(resourceId, path) : undefined + const rid = await invoke('plugin:store|get_store', { path }) + return rid + ? new Store( + rid + // path + ) + : undefined } async set(key: string, value: unknown): Promise { @@ -240,7 +249,7 @@ export class Store extends Resource implements IStore { cb: (value: T | null) => void ): Promise { return await listen>('store://change', (event) => { - if (event.payload.path === this.path && event.payload.key === key) { + if (event.payload.resourceId === this.rid && event.payload.key === key) { cb(event.payload.value) } }) @@ -250,7 +259,7 @@ export class Store extends Resource implements IStore { cb: (key: string, value: T | null) => void ): Promise { return await listen>('store://change', (event) => { - if (event.payload.path === this.path) { + if (event.payload.resourceId === this.rid) { cb(event.payload.key, event.payload.value) } }) diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index dbc53d5f3f..40f72aff53 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -31,8 +31,10 @@ mod error; mod store; #[derive(Serialize, Clone)] +#[serde(rename_all = "camelCase")] struct ChangePayload<'a> { path: &'a Path, + resource_id: Option, key: &'a str, value: &'a JsonValue, } diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 15c5f4acbc..b2c4c23d55 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -12,7 +12,7 @@ use std::{ sync::{Arc, Mutex}, time::Duration, }; -use tauri::{AppHandle, Emitter, Manager, Resource, ResourceId, Runtime}; +use tauri::{path::BaseDirectory, AppHandle, Emitter, Manager, Resource, ResourceId, Runtime}; use tokio::{ select, sync::mpsc::{unbounded_channel, UnboundedSender}, @@ -59,10 +59,15 @@ impl StoreBuilder { /// }); /// ``` pub fn new, P: AsRef>(manager: &M, path: P) -> Self { + let app = manager.app_handle().clone(); + let path = app + .path() + .resolve(path, BaseDirectory::AppData) + .expect("failed to resolve app dir"); Self { - app: manager.app_handle().clone(), + app, // Since Store.path is only exposed to the user in emit calls we may as well simplify it here already. - path: dunce::simplified(path.as_ref()).to_path_buf(), + path: dunce::simplified(&path).to_path_buf(), defaults: None, serialize_fn: default_serialize, deserialize_fn: default_deserialize, @@ -255,17 +260,10 @@ impl StoreInner { /// Saves the store to disk at the store's `path`. pub fn save(&self) -> crate::Result<()> { - let app_dir = self - .app - .path() - .app_data_dir() - .expect("failed to resolve app dir"); - let store_path = app_dir.join(&self.path); - - create_dir_all(store_path.parent().expect("invalid store path"))?; + create_dir_all(self.path.parent().expect("invalid store path"))?; let bytes = (self.serialize_fn)(&self.cache).map_err(crate::Error::Serialize)?; - let mut f = File::create(&store_path)?; + let mut f = File::create(&self.path)?; f.write_all(&bytes)?; Ok(()) @@ -273,14 +271,7 @@ impl StoreInner { /// Update the store from the on-disk state pub fn load(&mut self) -> crate::Result<()> { - let app_dir = self - .app - .path() - .app_data_dir() - .expect("failed to resolve app dir"); - let store_path = app_dir.join(&self.path); - - let bytes = read(store_path)?; + let bytes = read(&self.path)?; self.cache .extend((self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?); @@ -374,10 +365,13 @@ impl StoreInner { } fn emit_change_event(&self, key: &str, value: &JsonValue) -> crate::Result<()> { + let collection = self.app.state::(); + let stores = collection.stores.lock().unwrap(); self.app.emit( "store://change", ChangePayload { path: &self.path, + resource_id: stores.get(&self.path).copied(), key, value, }, From a3bef843826f9b3496ede14f7810f0c995f9c5ce Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 16:22:07 +0800 Subject: [PATCH 16/70] more docs --- plugins/store/api-iife.js | 2 +- plugins/store/guest-js/index.ts | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index 9b27a2dc2c..40ed13651a 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,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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"}(r||(r={}));class u extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new u(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new u(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){return await s("plugin:store|get",{rid:this.rid,key:t})}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 load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.value)}))}}return t.LazyStore=class{constructor(t,e){this.path=t,this.options=e}get store(){return this._store||(this._store=c(this.path).then((async t=>t||await o(this.path,this.options)))),this._store}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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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"}(r||(r={}));class u extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new u(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new u(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){return await s("plugin:store|get",{rid:this.rid,key:t})}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 load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.value)}))}}return t.LazyStore=class{get store(){return this._store||(this._store=c(this.path).then((async t=>t||await 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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,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 69e4034852..aecf9e2f27 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -45,15 +45,9 @@ export async function getStore(path: string): Promise { /** * A lazy loaded key-value store persisted by the backend layer. - * - * Note that the options are not applied if someone else already created the store */ export class LazyStore implements IStore { private _store?: Promise - constructor( - private readonly path: string, - private readonly options?: StoreOptions - ) {} private get store(): Promise { if (!this._store) { @@ -64,6 +58,16 @@ export class LazyStore implements IStore { return this._store } + /** + * Note that the options are not applied if someone else already created the store + * @param path: Path to save the store in `app_data_dir` + * @param options: Store configuration options + */ + constructor( + private readonly path: string, + private readonly options?: StoreOptions + ) {} + /** * Init/load the store if it's not already */ From 718f5dc90debfa44cb698da3b05ef660e8b32534 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 16:46:06 +0800 Subject: [PATCH 17/70] Allow js to use pre-stored (de)serialize functions --- plugins/store/guest-js/index.ts | 8 +++++ plugins/store/src/error.rs | 6 ++++ plugins/store/src/lib.rs | 55 +++++++++++++++++++++++++++++---- plugins/store/src/store.rs | 4 +-- 4 files changed, 65 insertions(+), 8 deletions(-) diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index aecf9e2f27..cc85c78a9f 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -21,6 +21,14 @@ export type StoreOptions = { * Auto save on modification with debounce duration in milliseconds, it's 100ms by default, pass in `false` to disable it */ autoSave?: boolean | number + /** + * Name of a serialize function registered in the rust side plugin builder + */ + serializeFnName?: string + /** + * Name of a deserialize function registered in the rust side plugin builder + */ + deserializeFnName?: string } /** diff --git a/plugins/store/src/error.rs b/plugins/store/src/error.rs index 612edcfdc8..77526fbc6f 100644 --- a/plugins/store/src/error.rs +++ b/plugins/store/src/error.rs @@ -24,6 +24,12 @@ pub enum Error { /// Store already exists #[error("Store at \"{0}\" already exists")] AlreadyExists(PathBuf), + /// Serialize function not found + #[error("Serialize Function \"{0}\" not found")] + SerializeFunctionNotFound(String), + /// Deserialize function not found + #[error("Deserialize Function \"{0}\" not found")] + DeserializeFunctionNotFound(String), /// Some Tauri API failed #[error(transparent)] Tauri(#[from] tauri::Error), diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 40f72aff53..6e01061b24 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -21,10 +21,10 @@ use std::{ sync::{Arc, Mutex}, time::Duration, }; -pub use store::{Store, StoreBuilder, StoreInner}; +pub use store::{DeserializeFn, SerializeFn, Store, StoreBuilder, StoreInner}; use tauri::{ plugin::{self, TauriPlugin}, - AppHandle, Manager, ResourceId, RunEvent, Runtime, + AppHandle, Manager, ResourceId, RunEvent, Runtime, State, }; mod error; @@ -41,6 +41,8 @@ struct ChangePayload<'a> { pub struct StoreCollection { stores: Mutex>, + serialize_fns: HashMap, + deserialize_fns: HashMap, } #[derive(Serialize, Deserialize)] @@ -53,10 +55,14 @@ enum AutoSave { #[tauri::command] async fn create_store( app: AppHandle, + store_collection: State<'_, StoreCollection>, path: PathBuf, auto_save: Option, + serialize_fn_name: Option, + deserialize_fn_name: Option, ) -> Result { let mut builder = app.store_builder(path.clone()); + if let Some(auto_save) = auto_save { match auto_save { AutoSave::DebounceDuration(duration) => { @@ -68,15 +74,34 @@ async fn create_store( _ => {} } } + + if let Some(serialize_fn_name) = serialize_fn_name { + let serialize_fn = store_collection + .serialize_fns + .get(&serialize_fn_name) + .ok_or_else(|| crate::Error::SerializeFunctionNotFound(serialize_fn_name))?; + builder = builder.serialize(*serialize_fn); + } + + if let Some(deserialize_fn_name) = deserialize_fn_name { + let deserialize_fn = store_collection + .deserialize_fns + .get(&deserialize_fn_name) + .ok_or_else(|| crate::Error::DeserializeFunctionNotFound(deserialize_fn_name))?; + builder = builder.deserialize(*deserialize_fn); + } + let (_, rid) = builder.build_inner()?; Ok(rid) } #[tauri::command] -async fn get_store(app: AppHandle, path: PathBuf) -> Option { - let collection = app.state::(); - let stores = collection.stores.lock().unwrap(); - stores.get(&path).copied() +async fn get_store( + store_collection: State<'_, StoreCollection>, + path: PathBuf, +) -> Result> { + let stores = store_collection.stores.lock().unwrap(); + Ok(stores.get(&path).copied()) } #[tauri::command] @@ -202,12 +227,16 @@ impl> StoreExt for T { pub struct Builder { phantom_data: PhantomData, + serialize_fns: HashMap, + deserialize_fns: HashMap, } impl Default for Builder { fn default() -> Self { Self { phantom_data: Default::default(), + serialize_fns: Default::default(), + deserialize_fns: Default::default(), } } } @@ -217,6 +246,18 @@ impl Builder { Self::default() } + /// Register a serialize function to access it from the JavaScript side + pub fn register_serialize_fn(mut self, name: String, serialize_fn: SerializeFn) -> Self { + self.serialize_fns.insert(name, serialize_fn); + self + } + + /// Register a deserialize function to access it from the JavaScript side + pub fn register_deserialize_fn(mut self, name: String, deserialize_fn: DeserializeFn) -> Self { + self.deserialize_fns.insert(name, deserialize_fn); + self + } + /// Builds the plugin. /// /// # Examples @@ -250,6 +291,8 @@ impl Builder { .setup(move |app_handle, _api| { app_handle.manage(StoreCollection { stores: Mutex::new(HashMap::new()), + serialize_fns: self.serialize_fns, + deserialize_fns: self.deserialize_fns, }); Ok(()) }) diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index b2c4c23d55..aa5117bd55 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -19,9 +19,9 @@ use tokio::{ time::sleep, }; -type SerializeFn = +pub type SerializeFn = fn(&HashMap) -> Result, Box>; -pub(crate) type DeserializeFn = +pub type DeserializeFn = fn(&[u8]) -> Result, Box>; fn default_serialize( From 4f13a034017e2c744d29b528a7dca879c3f187cd Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 20:10:15 +0800 Subject: [PATCH 18/70] Fix js get and close store --- examples/api/src-tauri/capabilities/base.json | 6 +-- examples/api/src-tauri/src/lib.rs | 3 +- examples/api/src/views/Store.svelte | 51 +++++++++++++++--- plugins/store/api-iife.js | 2 +- plugins/store/build.rs | 1 + plugins/store/guest-js/index.ts | 5 ++ .../autogenerated/commands/close.toml | 13 +++++ .../autogenerated/commands/close_store.toml | 13 +++++ .../permissions/autogenerated/reference.md | 53 +++++++++++++++++++ plugins/store/permissions/default.toml | 1 + plugins/store/permissions/schemas/schema.json | 20 +++++++ plugins/store/src/lib.rs | 14 +++-- plugins/store/src/store.rs | 11 ++-- 13 files changed, 171 insertions(+), 22 deletions(-) create mode 100644 plugins/store/permissions/autogenerated/commands/close.toml create mode 100644 plugins/store/permissions/autogenerated/commands/close_store.toml diff --git a/examples/api/src-tauri/capabilities/base.json b/examples/api/src-tauri/capabilities/base.json index b76e898cb2..607828e93e 100644 --- a/examples/api/src-tauri/capabilities/base.json +++ b/examples/api/src-tauri/capabilities/base.json @@ -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" ] } diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index f3cacc4345..701f673182 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -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")] diff --git a/examples/api/src/views/Store.svelte b/examples/api/src/views/Store.svelte index 48288a5079..75583a53a8 100644 --- a/examples/api/src/views/Store.svelte +++ b/examples/api/src/views/Store.svelte @@ -7,15 +7,23 @@ let key; let value; - const store = new LazyStore("cache.json"); + let store = new LazyStore("cache.json"); let cache = {}; - onMount(async () => { - 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(); }); async function write(key, value) { @@ -23,11 +31,33 @@ await store.set(key, value); const v = await store.get(key); cache[key] = v; - cache = cache; } 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 now errors out"); + } catch (error) { + onMessage(error); + } + } + + function reopen() { + store = new LazyStore("cache.json"); + onMessage("We made a new `LazyStore` instance, operations will now work"); + }
@@ -42,7 +72,12 @@
- +
+ + + + +
diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index 40ed13651a..7246c5a2fa 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,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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"}(r||(r={}));class u extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new u(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new u(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){return await s("plugin:store|get",{rid:this.rid,key:t})}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 load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.value)}))}}return t.LazyStore=class{get store(){return this._store||(this._store=c(this.path).then((async t=>t||await 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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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"}(r||(r={}));class u extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new u(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new u(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){return await s("plugin:store|get",{rid:this.rid,key:t})}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 load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.value)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}return t.LazyStore=class{get store(){return this._store||(this._store=c(this.path).then((async t=>t||await 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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} diff --git a/plugins/store/build.rs b/plugins/store/build.rs index a144df296a..b22b0b86e8 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -5,6 +5,7 @@ const COMMANDS: &[&str] = &[ "create_store", "get_store", + "close_store", "set", "get", "has", diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index cc85c78a9f..5fa3e02f04 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -276,6 +276,11 @@ export class Store extends Resource implements IStore { } }) } + + async close(): Promise { + // The default close on `Resource` can only close resources in the webview's resource table + await invoke('plugin:store|close_store', { rid: this.rid }) + } } interface IStore { diff --git a/plugins/store/permissions/autogenerated/commands/close.toml b/plugins/store/permissions/autogenerated/commands/close.toml new file mode 100644 index 0000000000..fad12d151f --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/close.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-close" +description = "Enables the close command without any pre-configured scope." +commands.allow = ["close"] + +[[permission]] +identifier = "deny-close" +description = "Denies the close command without any pre-configured scope." +commands.deny = ["close"] diff --git a/plugins/store/permissions/autogenerated/commands/close_store.toml b/plugins/store/permissions/autogenerated/commands/close_store.toml new file mode 100644 index 0000000000..5a8e3a0cb0 --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/close_store.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-close-store" +description = "Enables the close_store command without any pre-configured scope." +commands.allow = ["close_store"] + +[[permission]] +identifier = "deny-close-store" +description = "Denies the close_store command without any pre-configured scope." +commands.deny = ["close_store"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 8d05169d33..d23e836c7c 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -11,6 +11,7 @@ All operations are enabled by default. - `allow-create-store` - `allow-get-store` +- `allow-close-store` - `allow-clear` - `allow-delete` - `allow-entries` @@ -62,6 +63,58 @@ Denies the clear command without any pre-configured scope. +`store:allow-close` + + + + +Enables the close command without any pre-configured scope. + + + + + + + +`store:deny-close` + + + + +Denies the close command without any pre-configured scope. + + + + + + + +`store:allow-close-store` + + + + +Enables the close_store command without any pre-configured scope. + + + + + + + +`store:deny-close-store` + + + + +Denies the close_store command without any pre-configured scope. + + + + + + + `store:allow-create-store` diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index 3a38e6e8b2..8c2b41c00a 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -13,6 +13,7 @@ All operations are enabled by default. permissions = [ "allow-create-store", "allow-get-store", + "allow-close-store", "allow-clear", "allow-delete", "allow-entries", diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index bfd50f9cb6..a567cbe4c7 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -304,6 +304,26 @@ "type": "string", "const": "deny-clear" }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "allow-close" + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "deny-close" + }, + { + "description": "Enables the close_store command without any pre-configured scope.", + "type": "string", + "const": "allow-close-store" + }, + { + "description": "Denies the close_store command without any pre-configured scope.", + "type": "string", + "const": "deny-close-store" + }, { "description": "Enables the create_store command without any pre-configured scope.", "type": "string", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 6e01061b24..ec58a00ba3 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -21,6 +21,7 @@ use std::{ sync::{Arc, Mutex}, time::Duration, }; +use store::resolve_store_path; pub use store::{DeserializeFn, SerializeFn, Store, StoreBuilder, StoreInner}; use tauri::{ plugin::{self, TauriPlugin}, @@ -96,12 +97,18 @@ async fn create_store( } #[tauri::command] -async fn get_store( +async fn get_store( + app: AppHandle, store_collection: State<'_, StoreCollection>, path: PathBuf, ) -> Result> { let stores = store_collection.stores.lock().unwrap(); - Ok(stores.get(&path).copied()) + Ok(stores.get(&resolve_store_path(app, path)).copied()) +} + +#[tauri::command] +async fn close_store(app: AppHandle, rid: ResourceId) -> Result<()> { + Ok(app.resources_table().close(rid)?) } #[tauri::command] @@ -275,6 +282,7 @@ impl Builder { .invoke_handler(tauri::generate_handler![ create_store, get_store, + close_store, set, get, has, @@ -286,7 +294,7 @@ impl Builder { length, entries, load, - save + save, ]) .setup(move |app_handle, _api| { app_handle.manage(StoreCollection { diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index aa5117bd55..5efbd173d7 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -36,6 +36,12 @@ fn default_deserialize( serde_json::from_slice(bytes).map_err(Into::into) } +pub(crate) fn resolve_store_path(app: AppHandle, path: impl AsRef) -> PathBuf { + app.path() + .resolve(path, BaseDirectory::AppData) + .expect("failed to resolve app dir") +} + /// Builds a [`Store`] pub struct StoreBuilder { app: AppHandle, @@ -60,10 +66,7 @@ impl StoreBuilder { /// ``` pub fn new, P: AsRef>(manager: &M, path: P) -> Self { let app = manager.app_handle().clone(); - let path = app - .path() - .resolve(path, BaseDirectory::AppData) - .expect("failed to resolve app dir"); + let path = resolve_store_path(app.clone(), path); Self { app, // Since Store.path is only exposed to the user in emit calls we may as well simplify it here already. From 73f365d3f0fb69a055bb66e5811ecd58b6b8edc5 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 20:35:19 +0800 Subject: [PATCH 19/70] Show case how to use pretty json --- examples/api/src-tauri/src/lib.rs | 12 +++++++++++- examples/api/src/views/Store.svelte | 2 +- plugins/store/src/lib.rs | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index 701f673182..26698cdd7b 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -37,7 +37,11 @@ pub fn run() { .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_shell::init()) - .plugin(tauri_plugin_store::Builder::default().build()) + .plugin( + tauri_plugin_store::Builder::default() + .register_serialize_fn("pretty-json".to_owned(), pretty_json) + .build(), + ) .setup(move |app| { #[cfg(desktop)] { @@ -159,3 +163,9 @@ pub fn run() { } }) } + +fn pretty_json( + cache: &std::collections::HashMap, +) -> Result, Box> { + Ok(serde_json::to_vec_pretty(&cache)?) +} diff --git a/examples/api/src/views/Store.svelte b/examples/api/src/views/Store.svelte index 75583a53a8..3d8c55b2f2 100644 --- a/examples/api/src/views/Store.svelte +++ b/examples/api/src/views/Store.svelte @@ -7,7 +7,7 @@ let key; let value; - let store = new LazyStore("cache.json"); + let store = new LazyStore("cache.json", { serializeFnName: "pretty-json" }); let cache = {}; async function refreshEntries() { diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index ec58a00ba3..0d97d49370 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -254,6 +254,23 @@ impl Builder { } /// Register a serialize function to access it from the JavaScript side + /// + /// # Examples + /// + /// ``` + /// fn pretty_json( + /// cache: &std::collections::HashMap, + /// ) -> Result, Box> { + /// Ok(serde_json::to_vec_pretty(&cache)?) + /// } + /// + /// tauri::Builder::default() + /// .plugin( + /// tauri_plugin_store::Builder::default() + /// .register_serialize_fn("pretty-json".to_owned(), pretty_json) + /// .build(), + /// ) + /// ``` pub fn register_serialize_fn(mut self, name: String, serialize_fn: SerializeFn) -> Self { self.serialize_fns.insert(name, serialize_fn); self From 27187224223a5b993cee5a7149aafa525df8ad0d Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 20:46:07 +0800 Subject: [PATCH 20/70] Update readme --- plugins/store/README.md | 49 ++++++----------------------------------- 1 file changed, 7 insertions(+), 42 deletions(-) diff --git a/plugins/store/README.md b/plugins/store/README.md index d781878d96..474f138b22 100644 --- a/plugins/store/README.md +++ b/plugins/store/README.md @@ -68,9 +68,9 @@ fn main() { Afterwards all the plugin's APIs are available through the JavaScript guest bindings: ```typescript -import { Store } from '@tauri-apps/plugin-store' +import { LazyStore } from '@tauri-apps/plugin-store' -const store = new Store('.settings.dat') +const store = new LazyStore('.settings.dat') await store.set('some-key', { value: 5 }) @@ -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 mode to the store are automatically saved by defaut You can manually save a store with: @@ -108,60 +105,28 @@ await store.load() 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_builder("app_data.bin").build()?; // 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() }) .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 From 7723f48f39393185110cdc57fc87fa8ebef8d479 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 20:49:33 +0800 Subject: [PATCH 21/70] Use store instead of `store_builder` in example --- plugins/store/README.md | 4 ++-- .../store/examples/AppSettingsManager/src-tauri/src/main.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/store/README.md b/plugins/store/README.md index 474f138b22..b94a228497 100644 --- a/plugins/store/README.md +++ b/plugins/store/README.md @@ -70,7 +70,7 @@ Afterwards all the plugin's APIs are available through the JavaScript guest bind ```typescript import { LazyStore } from '@tauri-apps/plugin-store' -const store = new LazyStore('.settings.dat') +const store = new LazyStore('settings.json') await store.set('some-key', { value: 5 }) @@ -113,7 +113,7 @@ fn main() { .plugin(tauri_plugin_store::Builder::default().build()) .setup(|app| { // This loads the store from disk - let store = app.store_builder("app_data.bin").build()?; + 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. diff --git a/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs b/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs index 95da5bee90..fbe250ae8d 100644 --- a/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs +++ b/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs @@ -17,7 +17,7 @@ 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").build().unwrap(); + let store = app.store("settings.json"); app.listen("store://change", |event| { dbg!(event); }); From 120f39fd9b0afdd2a31c9361d1886d442164b26b Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 22:17:40 +0800 Subject: [PATCH 22/70] Build --- .../autogenerated/commands/close.toml | 13 ---------- .../permissions/autogenerated/reference.md | 26 ------------------- plugins/store/permissions/schemas/schema.json | 10 ------- 3 files changed, 49 deletions(-) delete mode 100644 plugins/store/permissions/autogenerated/commands/close.toml diff --git a/plugins/store/permissions/autogenerated/commands/close.toml b/plugins/store/permissions/autogenerated/commands/close.toml deleted file mode 100644 index fad12d151f..0000000000 --- a/plugins/store/permissions/autogenerated/commands/close.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-close" -description = "Enables the close command without any pre-configured scope." -commands.allow = ["close"] - -[[permission]] -identifier = "deny-close" -description = "Denies the close command without any pre-configured scope." -commands.deny = ["close"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index d23e836c7c..ff131d5246 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -63,32 +63,6 @@ Denies the clear command without any pre-configured scope. -`store:allow-close` - - - - -Enables the close command without any pre-configured scope. - - - - - - - -`store:deny-close` - - - - -Denies the close command without any pre-configured scope. - - - - - - - `store:allow-close-store` diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index a567cbe4c7..4bd1cee885 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -304,16 +304,6 @@ "type": "string", "const": "deny-clear" }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "allow-close" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "deny-close" - }, { "description": "Enables the close_store command without any pre-configured scope.", "type": "string", From 50ab4c18e020ee92c307743d6d43f357c295a69a Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 22:21:59 +0800 Subject: [PATCH 23/70] Fix example --- examples/api/src/views/Store.svelte | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/api/src/views/Store.svelte b/examples/api/src/views/Store.svelte index 3d8c55b2f2..8261403830 100644 --- a/examples/api/src/views/Store.svelte +++ b/examples/api/src/views/Store.svelte @@ -7,9 +7,14 @@ let key; let value; - let store = new LazyStore("cache.json", { serializeFnName: "pretty-json" }); + let store; let cache = {}; + function newStore() { + store = new LazyStore("cache.json", { serializeFnName: "pretty-json" }); + } + newStore() + async function refreshEntries() { try { const values = await store.entries(); @@ -55,7 +60,7 @@ } function reopen() { - store = new LazyStore("cache.json"); + newStore() onMessage("We made a new `LazyStore` instance, operations will now work"); } From fc51b3d13280e1688645808e88c46e9223e19ec5 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 3 Oct 2024 22:36:51 +0800 Subject: [PATCH 24/70] More docs for StoreBuilder::build --- plugins/store/src/store.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 5efbd173d7..15a1d49aa9 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -213,6 +213,14 @@ impl StoreBuilder { /// Builds the [`Store`]. /// + /// This loads the store from disk and put the store in the app's resource table, + /// to remove it from the resource table, call [`Store::close_store`] + /// + /// # Errors + /// + /// If a store with this path is already in the resource table, + /// will return a [`crate::Error::AlreadyExists`] + /// /// # Examples /// ``` /// tauri::Builder::default() From 4037589000dc9fbcbbbb80efc8279f32c8b7b0de Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 4 Oct 2024 08:47:30 +0800 Subject: [PATCH 25/70] Add default (de)serialize fn --- plugins/store/src/lib.rs | 51 +++++++++++++++++++++++++++++++------- plugins/store/src/store.rs | 49 ++++++++++++++++++------------------ 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 0d97d49370..48c6c6e406 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -40,10 +40,13 @@ struct ChangePayload<'a> { value: &'a JsonValue, } -pub struct StoreCollection { +#[derive(Debug)] +pub struct StoreState { stores: Mutex>, serialize_fns: HashMap, deserialize_fns: HashMap, + default_serialize: SerializeFn, + default_deserialize: DeserializeFn, } #[derive(Serialize, Deserialize)] @@ -56,7 +59,7 @@ enum AutoSave { #[tauri::command] async fn create_store( app: AppHandle, - store_collection: State<'_, StoreCollection>, + store_state: State<'_, StoreState>, path: PathBuf, auto_save: Option, serialize_fn_name: Option, @@ -77,7 +80,7 @@ async fn create_store( } if let Some(serialize_fn_name) = serialize_fn_name { - let serialize_fn = store_collection + let serialize_fn = store_state .serialize_fns .get(&serialize_fn_name) .ok_or_else(|| crate::Error::SerializeFunctionNotFound(serialize_fn_name))?; @@ -85,7 +88,7 @@ async fn create_store( } if let Some(deserialize_fn_name) = deserialize_fn_name { - let deserialize_fn = store_collection + let deserialize_fn = store_state .deserialize_fns .get(&deserialize_fn_name) .ok_or_else(|| crate::Error::DeserializeFunctionNotFound(deserialize_fn_name))?; @@ -99,10 +102,10 @@ async fn create_store( #[tauri::command] async fn get_store( app: AppHandle, - store_collection: State<'_, StoreCollection>, + store_state: State<'_, StoreState>, path: PathBuf, ) -> Result> { - let stores = store_collection.stores.lock().unwrap(); + let stores = store_state.stores.lock().unwrap(); Ok(stores.get(&resolve_store_path(app, path)).copied()) } @@ -224,7 +227,7 @@ impl> StoreExt for T { } fn get_store(&self, path: impl AsRef) -> Option>> { - let collection = self.state::(); + let collection = self.state::(); let stores = collection.stores.lock().unwrap(); stores .get(path.as_ref()) @@ -232,10 +235,24 @@ impl> StoreExt for T { } } +fn default_serialize( + cache: &HashMap, +) -> std::result::Result, Box> { + Ok(serde_json::to_vec(&cache)?) +} + +fn default_deserialize( + bytes: &[u8], +) -> std::result::Result, Box> { + serde_json::from_slice(bytes).map_err(Into::into) +} + pub struct Builder { phantom_data: PhantomData, serialize_fns: HashMap, deserialize_fns: HashMap, + default_serialize: SerializeFn, + default_deserialize: DeserializeFn, } impl Default for Builder { @@ -244,6 +261,8 @@ impl Default for Builder { phantom_data: Default::default(), serialize_fns: Default::default(), deserialize_fns: Default::default(), + default_serialize, + default_deserialize, } } } @@ -282,6 +301,18 @@ impl Builder { self } + /// Use this serialize function for stores by default + pub fn default_serialize_fn(mut self, serialize_fn: SerializeFn) -> Self { + self.default_serialize = serialize_fn; + self + } + + /// Use this deserialize function for stores by default + pub fn default_deserialize_fn(mut self, deserialize_fn: DeserializeFn) -> Self { + self.default_deserialize = deserialize_fn; + self + } + /// Builds the plugin. /// /// # Examples @@ -314,16 +345,18 @@ impl Builder { save, ]) .setup(move |app_handle, _api| { - app_handle.manage(StoreCollection { + app_handle.manage(StoreState { stores: Mutex::new(HashMap::new()), serialize_fns: self.serialize_fns, deserialize_fns: self.deserialize_fns, + default_serialize: self.default_serialize, + default_deserialize: self.default_deserialize, }); Ok(()) }) .on_event(|app_handle, event| { if let RunEvent::Exit = event { - let collection = app_handle.state::(); + let collection = app_handle.state::(); let stores = collection.stores.lock().unwrap(); for (path, rid) in stores.iter() { if let Ok(store) = app_handle.resources_table().get::>(*rid) { diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 15a1d49aa9..b6e145a212 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{ChangePayload, StoreCollection}; +use crate::{ChangePayload, StoreState}; use serde_json::Value as JsonValue; use std::{ collections::HashMap, @@ -24,18 +24,6 @@ pub type SerializeFn = pub type DeserializeFn = fn(&[u8]) -> Result, Box>; -fn default_serialize( - cache: &HashMap, -) -> Result, Box> { - Ok(serde_json::to_vec(&cache)?) -} - -fn default_deserialize( - bytes: &[u8], -) -> Result, Box> { - serde_json::from_slice(bytes).map_err(Into::into) -} - pub(crate) fn resolve_store_path(app: AppHandle, path: impl AsRef) -> PathBuf { app.path() .resolve(path, BaseDirectory::AppData) @@ -50,6 +38,7 @@ pub struct StoreBuilder { serialize_fn: SerializeFn, deserialize_fn: DeserializeFn, auto_save: Option, + load_on_build: bool, } impl StoreBuilder { @@ -67,14 +56,18 @@ impl StoreBuilder { pub fn new, P: AsRef>(manager: &M, path: P) -> Self { let app = manager.app_handle().clone(); let path = resolve_store_path(app.clone(), path); + let state = app.state::(); + let serialize_fn = state.default_serialize; + let deserialize_fn = state.default_deserialize; Self { app, // Since Store.path is only exposed to the user in emit calls we may as well simplify it here already. path: dunce::simplified(&path).to_path_buf(), defaults: None, - serialize_fn: default_serialize, - deserialize_fn: default_deserialize, + serialize_fn, + deserialize_fn, auto_save: Some(Duration::from_millis(100)), + load_on_build: true, } } @@ -181,9 +174,15 @@ impl StoreBuilder { self } + /// Skip loading the store on build + pub fn skip_initial_load(mut self) -> Self { + self.load_on_build = false; + self + } + pub(crate) fn build_inner(mut self) -> crate::Result<(Arc>, ResourceId)> { - let collection = self.app.state::(); - let mut stores = collection.stores.lock().unwrap(); + let state = self.app.state::(); + let mut stores = state.stores.lock().unwrap(); if stores.contains_key(&self.path) { return Err(crate::Error::AlreadyExists(self.path)); @@ -196,7 +195,9 @@ impl StoreBuilder { self.serialize_fn, self.deserialize_fn, ); - let _ = store_inner.load(); + if self.load_on_build { + let _ = store_inner.load(); + } let store = Store { auto_save: self.auto_save, @@ -376,8 +377,8 @@ impl StoreInner { } fn emit_change_event(&self, key: &str, value: &JsonValue) -> crate::Result<()> { - let collection = self.app.state::(); - let stores = collection.stores.lock().unwrap(); + let state = self.app.state::(); + let stores = state.stores.lock().unwrap(); self.app.emit( "store://change", ChangePayload { @@ -409,8 +410,8 @@ pub struct Store { impl Resource for Store { fn close(self: Arc) { let store = self.store.lock().unwrap(); - let collection = store.app.state::(); - let mut stores = collection.stores.lock().unwrap(); + let state = store.app.state::(); + let mut stores = state.stores.lock().unwrap(); stores.remove(&store.path); } } @@ -508,8 +509,8 @@ impl Store { pub fn close_store(self) { let store = self.store.lock().unwrap(); let app = store.app.clone(); - let collection = app.state::(); - let stores = collection.stores.lock().unwrap(); + let state = app.state::(); + let stores = state.stores.lock().unwrap(); if let Some(rid) = stores.get(&store.path).copied() { drop(store); drop(stores); From 1ef9f6a487982895f98130756b503dd9ece57107 Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 4 Oct 2024 09:40:33 +0800 Subject: [PATCH 26/70] Use pretty json by default --- examples/api/src-tauri/src/lib.rs | 12 +----------- examples/api/src/views/Store.svelte | 15 +++++++-------- plugins/store/src/lib.rs | 25 +++++++++++++++++++++---- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index 26698cdd7b..701f673182 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -37,11 +37,7 @@ pub fn run() { .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_shell::init()) - .plugin( - tauri_plugin_store::Builder::default() - .register_serialize_fn("pretty-json".to_owned(), pretty_json) - .build(), - ) + .plugin(tauri_plugin_store::Builder::default().build()) .setup(move |app| { #[cfg(desktop)] { @@ -163,9 +159,3 @@ pub fn run() { } }) } - -fn pretty_json( - cache: &std::collections::HashMap, -) -> Result, Box> { - Ok(serde_json::to_vec_pretty(&cache)?) -} diff --git a/examples/api/src/views/Store.svelte b/examples/api/src/views/Store.svelte index 8261403830..7407e3b0fb 100644 --- a/examples/api/src/views/Store.svelte +++ b/examples/api/src/views/Store.svelte @@ -7,14 +7,9 @@ let key; let value; - let store; + let store = new LazyStore("cache.json"); let cache = {}; - function newStore() { - store = new LazyStore("cache.json", { serializeFnName: "pretty-json" }); - } - newStore() - async function refreshEntries() { try { const values = await store.entries(); @@ -33,7 +28,11 @@ async function write(key, value) { try { - await store.set(key, value); + if (value) { + await store.set(key, value); + } else { + await store.delete(key); + } const v = await store.get(key); cache[key] = v; } catch (error) { @@ -60,7 +59,7 @@ } function reopen() { - newStore() + store = new LazyStore("cache.json"); onMessage("We made a new `LazyStore` instance, operations will now work"); } diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 48c6c6e406..d075d01cef 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -238,7 +238,7 @@ impl> StoreExt for T { fn default_serialize( cache: &HashMap, ) -> std::result::Result, Box> { - Ok(serde_json::to_vec(&cache)?) + Ok(serde_json::to_vec_pretty(&cache)?) } fn default_deserialize( @@ -277,16 +277,16 @@ impl Builder { /// # Examples /// /// ``` - /// fn pretty_json( + /// fn no_pretty_json( /// cache: &std::collections::HashMap, /// ) -> Result, Box> { - /// Ok(serde_json::to_vec_pretty(&cache)?) + /// Ok(serde_json::to_vec(&cache)?) /// } /// /// tauri::Builder::default() /// .plugin( /// tauri_plugin_store::Builder::default() - /// .register_serialize_fn("pretty-json".to_owned(), pretty_json) + /// .register_serialize_fn("no-pretty-json".to_owned(), no_pretty_json) /// .build(), /// ) /// ``` @@ -302,6 +302,23 @@ impl Builder { } /// Use this serialize function for stores by default + /// + /// # Examples + /// + /// ``` + /// fn no_pretty_json( + /// cache: &std::collections::HashMap, + /// ) -> Result, Box> { + /// Ok(serde_json::to_vec(&cache)?) + /// } + /// + /// tauri::Builder::default() + /// .plugin( + /// tauri_plugin_store::Builder::default() + /// .default_serialize_fn(no_pretty_json) + /// .build(), + /// ) + /// ``` pub fn default_serialize_fn(mut self, serialize_fn: SerializeFn) -> Self { self.default_serialize = serialize_fn; self From 831105d7a851fced490b0509eb54538b2140fd2b Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 4 Oct 2024 10:11:13 +0800 Subject: [PATCH 27/70] Use `undefined` for empty value in get --- examples/api/src/views/Store.svelte | 7 ++++++- plugins/store/api-iife.js | 2 +- plugins/store/guest-js/index.ts | 11 ++++++----- plugins/store/src/lib.rs | 6 ++++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/examples/api/src/views/Store.svelte b/examples/api/src/views/Store.svelte index 7407e3b0fb..f33002784e 100644 --- a/examples/api/src/views/Store.svelte +++ b/examples/api/src/views/Store.svelte @@ -34,7 +34,12 @@ await store.delete(key); } const v = await store.get(key); - cache[key] = v; + if (v === undefined) { + delete cache[key]; + cache = cache; + } else { + cache[key] = v; + } } catch (error) { onMessage(error); } diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index 7246c5a2fa..ad943d3227 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,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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"}(r||(r={}));class u extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new u(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new u(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){return await s("plugin:store|get",{rid:this.rid,key:t})}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 load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.value)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}return t.LazyStore=class{get store(){return this._store||(this._store=c(this.path).then((async t=>t||await 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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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"}(r||(r={}));class u extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new u(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new u(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,r]=await s("plugin:store|get",{rid:this.rid,key:t});return r?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 load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.value)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}return t.LazyStore=class{get store(){return this._store||(this._store=c(this.path).then((async t=>t||await 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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,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 5fa3e02f04..0b6452a0a5 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -87,7 +87,7 @@ export class LazyStore implements IStore { return (await this.store).set(key, value) } - async get(key: string): Promise { + async get(key: string): Promise { return (await this.store).get(key) } @@ -203,11 +203,12 @@ export class Store extends Resource implements IStore { }) } - async get(key: string): Promise { - return await invoke('plugin:store|get', { + async get(key: string): Promise { + const [value, exists] = await invoke<[T, boolean]>('plugin:store|get', { rid: this.rid, key }) + return exists ? value : undefined } async has(key: string): Promise { @@ -294,12 +295,12 @@ interface IStore { set(key: string, value: unknown): Promise /** - * Returns the value for the given `key` or `null` if the key does not exist. + * Returns the value for the given `key` or `undefined` if the key does not exist. * * @param key * @returns */ - get(key: string): Promise + get(key: string): Promise /** * Returns `true` if the given `key` exists in the store. diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index d075d01cef..5826bf50ca 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -131,9 +131,11 @@ async fn get( app: AppHandle, rid: ResourceId, key: String, -) -> Result> { +) -> Result<(Option, bool)> { let store = app.resources_table().get::>(rid)?; - Ok(store.get(key)) + let value = store.get(key); + let exists = value.is_some(); + Ok((value, exists)) } #[tauri::command] From 25d677d647b5e9dd2fe354de03756869271ab252 Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 4 Oct 2024 10:21:33 +0800 Subject: [PATCH 28/70] Change files --- .changes/store-plugin-rework-js-feat.md | 7 +++++++ .changes/store-plugin-rework.md | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 .changes/store-plugin-rework-js-feat.md create mode 100644 .changes/store-plugin-rework.md diff --git a/.changes/store-plugin-rework-js-feat.md b/.changes/store-plugin-rework-js-feat.md new file mode 100644 index 0000000000..8eb619880a --- /dev/null +++ b/.changes/store-plugin-rework-js-feat.md @@ -0,0 +1,7 @@ +--- +"store-js": minor:feat +--- + +- Add `getStore` +- Add an option to use pre-stored (de)serialize functions +- Add back lazy store `LazyStore` diff --git a/.changes/store-plugin-rework.md b/.changes/store-plugin-rework.md new file mode 100644 index 0000000000..949df008ac --- /dev/null +++ b/.changes/store-plugin-rework.md @@ -0,0 +1,24 @@ +--- +"store": minor:breaking +--- + +**Breaking change**: Renamed `StoreCollection` to `StoreState` + +Changes: + +- Disallow calling `create_store` when there're still active stores with the same path alive +- Save and cancel pending auto save on drop +- Use absolute path as store's key, fix #984 + +New features: + +- Add `getStore`/`get_store` share stores across js and rust side +- Add default (de)serialize functions settings +- Allow js to use pre-stored (de)serialize functions +- Add back lazy store (implemented in js) + +Default changes: + +- Share store to resource table by default +- Enable auto save with 100ms debounce time by default +- Use pretty json by default, close #1690 \ No newline at end of file From 7faf8c31e02d88d3f34b8bf81a780b1b321e3d07 Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 4 Oct 2024 17:09:41 +0800 Subject: [PATCH 29/70] Differentiate json null from no value for events --- plugins/store/api-iife.js | 2 +- plugins/store/guest-js/index.ts | 24 +++++++++++++++--------- plugins/store/src/lib.rs | 3 ++- plugins/store/src/store.rs | 15 ++++++++------- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index ad943d3227..627d28cbb8 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,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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"}(r||(r={}));class u extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new u(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new u(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,r]=await s("plugin:store|get",{rid:this.rid,key:t});return r?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 load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.value)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.value)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}return t.LazyStore=class{get store(){return this._store||(this._store=c(this.path).then((async t=>t||await 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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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 createStore(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new u(a)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new u(e):void 0}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 load(){await s("plugin:store|load",{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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}return t.LazyStore=class{get store(){return this._store||(this._store=c(this.path).then((async t=>t||await 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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,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 0b6452a0a5..8ffe1bf7e5 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -10,7 +10,8 @@ interface ChangePayload { path: string resourceId?: number key: string - value: T | null + value: T + exists: boolean } /** @@ -133,13 +134,13 @@ export class LazyStore implements IStore { async onKeyChange( key: string, - cb: (value: T | null) => void + cb: (value: T | undefined) => void ): Promise { return (await this.store).onKeyChange(key, cb) } async onChange( - cb: (key: string, value: T | null) => void + cb: (key: string, value: T | undefined) => void ): Promise { return (await this.store).onChange(cb) } @@ -259,21 +260,24 @@ export class Store extends Resource implements IStore { async onKeyChange( key: string, - cb: (value: T | null) => void + cb: (value: T | undefined) => void ): Promise { return await listen>('store://change', (event) => { if (event.payload.resourceId === this.rid && event.payload.key === key) { - cb(event.payload.value) + cb(event.payload.exists ? event.payload.value : undefined) } }) } async onChange( - cb: (key: string, value: T | null) => void + cb: (key: string, value: T | undefined) => void ): Promise { return await listen>('store://change', (event) => { if (event.payload.resourceId === this.rid) { - cb(event.payload.key, event.payload.value) + cb( + event.payload.key, + event.payload.exists ? event.payload.value : undefined + ) } }) } @@ -388,7 +392,7 @@ interface IStore { */ onKeyChange( key: string, - cb: (value: T | null) => void + cb: (value: T | undefined) => void ): Promise /** @@ -398,7 +402,9 @@ interface IStore { * * @since 2.0.0 */ - onChange(cb: (key: string, value: T | null) => void): Promise + onChange( + cb: (key: string, value: T | undefined) => void + ): Promise /** * Close the store and cleans up this resource from memory. diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 5826bf50ca..aecd4362c4 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -37,7 +37,8 @@ struct ChangePayload<'a> { path: &'a Path, resource_id: Option, key: &'a str, - value: &'a JsonValue, + value: Option<&'a JsonValue>, + exists: bool, } #[derive(Debug)] diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index b6e145a212..ae67fb899e 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -296,7 +296,7 @@ impl StoreInner { let key = key.into(); let value = value.into(); self.cache.insert(key.clone(), value.clone()); - let _ = self.emit_change_event(&key, &value); + let _ = self.emit_change_event(&key, Some(&value)); } /// Returns a reference to the value corresponding to the key. @@ -313,7 +313,7 @@ impl StoreInner { pub fn delete(&mut self, key: impl AsRef) -> bool { let flag = self.cache.remove(key.as_ref()).is_some(); if flag { - let _ = self.emit_change_event(key.as_ref(), &JsonValue::Null); + let _ = self.emit_change_event(key.as_ref(), None); } flag } @@ -325,7 +325,7 @@ impl StoreInner { let keys: Vec = self.cache.keys().cloned().collect(); self.cache.clear(); for key in &keys { - let _ = self.emit_change_event(key, &JsonValue::Null); + let _ = self.emit_change_event(key, None); } } @@ -336,13 +336,12 @@ impl StoreInner { if let Some(defaults) = &self.defaults { for (key, value) in &self.cache { if defaults.get(key) != Some(value) { - let _ = - self.emit_change_event(key, defaults.get(key).unwrap_or(&JsonValue::Null)); + let _ = self.emit_change_event(key, defaults.get(key)); } } for (key, value) in defaults { if !self.cache.contains_key(key) { - let _ = self.emit_change_event(key, value); + let _ = self.emit_change_event(key, Some(value)); } } self.cache.clone_from(defaults); @@ -376,9 +375,10 @@ impl StoreInner { self.cache.is_empty() } - fn emit_change_event(&self, key: &str, value: &JsonValue) -> crate::Result<()> { + fn emit_change_event(&self, key: &str, value: Option<&JsonValue>) -> crate::Result<()> { let state = self.app.state::(); let stores = state.stores.lock().unwrap(); + let exists = value.is_some(); self.app.emit( "store://change", ChangePayload { @@ -386,6 +386,7 @@ impl StoreInner { resource_id: stores.get(&self.path).copied(), key, value, + exists, }, )?; Ok(()) From dc5be009b74b19a356dcdf8679684389ce6c68d4 Mon Sep 17 00:00:00 2001 From: Tony Date: Sat, 5 Oct 2024 16:21:27 +0800 Subject: [PATCH 30/70] Add create or existing --- plugins/store/api-iife.js | 2 +- plugins/store/build.rs | 1 + plugins/store/guest-js/index.ts | 37 +++++++++- .../commands/create_or_existing_store.toml | 13 ++++ .../permissions/autogenerated/reference.md | 26 +++++++ plugins/store/permissions/default.toml | 1 + plugins/store/permissions/schemas/schema.json | 10 +++ plugins/store/src/lib.rs | 56 ++++++++++++--- plugins/store/src/store.rs | 68 ++++++++++++++++--- 9 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 plugins/store/permissions/autogenerated/commands/create_or_existing_store.toml diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index 627d28cbb8..af3d90ab78 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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}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 createStore(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new u(a)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new u(e):void 0}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 load(){await s("plugin:store|load",{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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}return t.LazyStore=class{get store(){return this._store||(this._store=c(this.path).then((async t=>t||await 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 load(){await(await this.store).load()}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.createStore=o,t.getStore=c,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.createOrExistingStore(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"}(r||(r={}));class c extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new c(r)}static async createOrExistingStore(t,e){const r=await s("plugin:store|create_or_existing_store",{path:t,...e});return new c(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new c(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,r]=await s("plugin:store|get",{rid:this.rid,key:t});return r?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 load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.exists?r.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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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 load(){await(await this.store).load()}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=c,t.createOrExistingStore=o,t.createStore=async function(t,e){return await c.createStore(t,e)},t.getStore=async function(t){return await c.getStore(t)},t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} diff --git a/plugins/store/build.rs b/plugins/store/build.rs index b22b0b86e8..e2530539c8 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -4,6 +4,7 @@ const COMMANDS: &[&str] = &[ "create_store", + "create_or_existing_store", "get_store", "close_store", "set", diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 8ffe1bf7e5..19b6df9554 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -45,6 +45,19 @@ export async function createStore( return await Store.createStore(path, options) } +/** + * Create a new Store or get the existing store with the path + * + * @param path: Path to save the store in `app_data_dir` + * @param options: Store configuration options + */ +export async function createOrExistingStore( + path: string, + options?: StoreOptions +): Promise { + return await Store.createOrExistingStore(path, options) +} + /** * @param path: Path of the store in the rust side */ @@ -60,9 +73,7 @@ export class LazyStore implements IStore { private get store(): Promise { if (!this._store) { - this._store = getStore(this.path).then( - async (store) => store || (await createStore(this.path, this.options)) - ) + this._store = createOrExistingStore(this.path, this.options) } return this._store } @@ -183,6 +194,26 @@ export class Store extends Resource implements IStore { ) } + /** + * Create a new Store or get the existing store with the path + * + * @param path: Path to save the store in `app_data_dir` + * @param options: Store configuration options + */ + static async createOrExistingStore( + path: string, + options?: StoreOptions + ): Promise { + const rid = await invoke('plugin:store|create_or_existing_store', { + path, + ...options + }) + return new Store( + rid + // path + ) + } + /** * @param path: Path of the store in the rust side */ diff --git a/plugins/store/permissions/autogenerated/commands/create_or_existing_store.toml b/plugins/store/permissions/autogenerated/commands/create_or_existing_store.toml new file mode 100644 index 0000000000..c4555c91cd --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/create_or_existing_store.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-create-or-existing-store" +description = "Enables the create_or_existing_store command without any pre-configured scope." +commands.allow = ["create_or_existing_store"] + +[[permission]] +identifier = "deny-create-or-existing-store" +description = "Denies the create_or_existing_store command without any pre-configured scope." +commands.deny = ["create_or_existing_store"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index ff131d5246..6146de3103 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -89,6 +89,32 @@ Denies the close_store command without any pre-configured scope. +`store:allow-create-or-existing-store` + + + + +Enables the create_or_existing_store command without any pre-configured scope. + + + + + + + +`store:deny-create-or-existing-store` + + + + +Denies the create_or_existing_store command without any pre-configured scope. + + + + + + + `store:allow-create-store` diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index 8c2b41c00a..fa7264f987 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -12,6 +12,7 @@ All operations are enabled by default. """ permissions = [ "allow-create-store", + "allow-create-or-existing-store", "allow-get-store", "allow-close-store", "allow-clear", diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index 4bd1cee885..277190472d 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -314,6 +314,16 @@ "type": "string", "const": "deny-close-store" }, + { + "description": "Enables the create_or_existing_store command without any pre-configured scope.", + "type": "string", + "const": "allow-create-or-existing-store" + }, + { + "description": "Denies the create_or_existing_store command without any pre-configured scope.", + "type": "string", + "const": "deny-create-or-existing-store" + }, { "description": "Enables the create_store command without any pre-configured scope.", "type": "string", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index aecd4362c4..1edfa56919 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -57,17 +57,15 @@ enum AutoSave { Bool(bool), } -#[tauri::command] -async fn create_store( +fn builder( app: AppHandle, store_state: State<'_, StoreState>, path: PathBuf, auto_save: Option, serialize_fn_name: Option, deserialize_fn_name: Option, -) -> Result { - let mut builder = app.store_builder(path.clone()); - +) -> Result> { + let mut builder = app.store_builder(path); if let Some(auto_save) = auto_save { match auto_save { AutoSave::DebounceDuration(duration) => { @@ -95,11 +93,51 @@ async fn create_store( .ok_or_else(|| crate::Error::DeserializeFunctionNotFound(deserialize_fn_name))?; builder = builder.deserialize(*deserialize_fn); } + Ok(builder) +} +#[tauri::command] +async fn create_store( + app: AppHandle, + store_state: State<'_, StoreState>, + path: PathBuf, + auto_save: Option, + serialize_fn_name: Option, + deserialize_fn_name: Option, +) -> Result { + let builder = builder( + app, + store_state, + path, + auto_save, + serialize_fn_name, + deserialize_fn_name, + )?; let (_, rid) = builder.build_inner()?; Ok(rid) } +#[tauri::command] +async fn create_or_existing_store( + app: AppHandle, + store_state: State<'_, StoreState>, + path: PathBuf, + auto_save: Option, + serialize_fn_name: Option, + deserialize_fn_name: Option, +) -> Result { + let builder = builder( + app, + store_state, + path, + auto_save, + serialize_fn_name, + deserialize_fn_name, + )?; + let (_, rid) = builder.build_or_existing_inner(); + Ok(rid) +} + #[tauri::command] async fn get_store( app: AppHandle, @@ -107,7 +145,7 @@ async fn get_store( path: PathBuf, ) -> Result> { let stores = store_state.stores.lock().unwrap(); - Ok(stores.get(&resolve_store_path(app, path)).copied()) + Ok(stores.get(&resolve_store_path(&app, path)).copied()) } #[tauri::command] @@ -217,8 +255,7 @@ pub trait StoreExt { impl> StoreExt for T { fn store(&self, path: impl AsRef) -> Arc> { - self.get_store(&path) - .unwrap_or_else(|| self.create_store(&path).unwrap()) + StoreBuilder::new(self.app_handle(), path).build_or_existing() } fn create_store(&self, path: impl AsRef) -> Result>> { @@ -233,7 +270,7 @@ impl> StoreExt for T { let collection = self.state::(); let stores = collection.stores.lock().unwrap(); stores - .get(path.as_ref()) + .get(&resolve_store_path(self.app_handle(), path.as_ref())) .and_then(|rid| self.resources_table().get(*rid).ok()) } } @@ -351,6 +388,7 @@ impl Builder { create_store, get_store, close_store, + create_or_existing_store, set, get, has, diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index ae67fb899e..cc11f283f9 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -24,10 +24,16 @@ pub type SerializeFn = pub type DeserializeFn = fn(&[u8]) -> Result, Box>; -pub(crate) fn resolve_store_path(app: AppHandle, path: impl AsRef) -> PathBuf { - app.path() - .resolve(path, BaseDirectory::AppData) - .expect("failed to resolve app dir") +pub(crate) fn resolve_store_path( + app: &AppHandle, + path: impl AsRef, +) -> PathBuf { + dunce::simplified( + &app.path() + .resolve(path, BaseDirectory::AppData) + .expect("failed to resolve app dir"), + ) + .to_path_buf() } /// Builds a [`Store`] @@ -55,14 +61,13 @@ impl StoreBuilder { /// ``` pub fn new, P: AsRef>(manager: &M, path: P) -> Self { let app = manager.app_handle().clone(); - let path = resolve_store_path(app.clone(), path); + let path = resolve_store_path(&app, path); let state = app.state::(); let serialize_fn = state.default_serialize; let deserialize_fn = state.default_deserialize; Self { app, - // Since Store.path is only exposed to the user in emit calls we may as well simplify it here already. - path: dunce::simplified(&path).to_path_buf(), + path, defaults: None, serialize_fn, deserialize_fn, @@ -212,7 +217,38 @@ impl StoreBuilder { Ok((store, rid)) } - /// Builds the [`Store`]. + pub(crate) fn build_or_existing_inner(mut self) -> (Arc>, ResourceId) { + let state = self.app.state::(); + let mut stores = state.stores.lock().unwrap(); + + if let Some(rid) = stores.get(&self.path) { + (self.app.resources_table().get(*rid).unwrap(), *rid) + } else { + let mut store_inner = StoreInner::new( + self.app.clone(), + self.path.clone(), + self.defaults.take(), + self.serialize_fn, + self.deserialize_fn, + ); + if self.load_on_build { + let _ = store_inner.load(); + } + + let store = Store { + auto_save: self.auto_save, + auto_save_debounce_sender: Arc::new(Mutex::new(None)), + store: Arc::new(Mutex::new(store_inner)), + }; + + let store = Arc::new(store); + let rid = self.app.resources_table().add_arc(store.clone()); + stores.insert(self.path, rid); + (store, rid) + } + } + + /// Builds the [`Store`], also see [`build_or_existing`](Self::build_or_existing). /// /// This loads the store from disk and put the store in the app's resource table, /// to remove it from the resource table, call [`Store::close_store`] @@ -235,6 +271,22 @@ impl StoreBuilder { let (store, _) = self.build_inner()?; Ok(store) } + + /// Get the existing store with the same path or builds a new [`Store`], also see [`build`](Self::build). + /// + /// # Examples + /// ``` + /// tauri::Builder::default() + /// .plugin(tauri_plugin_store::Builder::default().build()) + /// .setup(|app| { + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").build_or_existing(); + /// Ok(()) + /// }); + /// ``` + pub fn build_or_existing(self) -> Arc> { + let (store, _) = self.build_or_existing_inner(); + store + } } pub(crate) enum AutoSaveMessage { From 92196ee92731d35100a3e55484605163989dd771 Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 8 Oct 2024 10:18:43 +0800 Subject: [PATCH 31/70] Build --- plugins/store/permissions/autogenerated/reference.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 6146de3103..1e0726a9f3 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -10,6 +10,7 @@ All operations are enabled by default. - `allow-create-store` +- `allow-create-or-existing-store` - `allow-get-store` - `allow-close-store` - `allow-clear` From a00cb152244e4b4a44d9e4bb99273c2a17b44b16 Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 8 Oct 2024 10:22:58 +0800 Subject: [PATCH 32/70] Rename inner store's inset method to set --- plugins/store/src/store.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index cc11f283f9..820759617a 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -344,7 +344,7 @@ impl StoreInner { } /// Inserts a key-value pair into the store. - pub fn insert(&mut self, key: impl Into, value: impl Into) { + pub fn set(&mut self, key: impl Into, value: impl Into) { let key = key.into(); let value = value.into(); self.cache.insert(key.clone(), value.clone()); @@ -479,7 +479,7 @@ impl Store { /// Inserts a key-value pair into the store. pub fn set(&self, key: impl Into, value: impl Into) { - self.store.lock().unwrap().insert(key.into(), value.into()); + self.store.lock().unwrap().set(key.into(), value.into()); let _ = self.trigger_auto_save(); } From c7094473ebd421e543210cd24340e541f680ca7b Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 8 Oct 2024 10:31:28 +0800 Subject: [PATCH 33/70] Update readme --- plugins/store/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/store/README.md b/plugins/store/README.md index e84c5285c9..535f673b6a 100644 --- a/plugins/store/README.md +++ b/plugins/store/README.md @@ -117,7 +117,7 @@ fn main() { // 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")); + store.set("a".to_string(), json!("b")); }) .run(tauri::generate_context!()) .expect("error while running tauri application"); From 230e6bbdf3646b8ee6ef9f718da9b84c6044a975 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 10 Oct 2024 13:59:07 +0800 Subject: [PATCH 34/70] Apply suggestions from code review --- examples/api/src/views/Store.svelte | 2 +- plugins/store/README.md | 2 +- plugins/store/guest-js/index.ts | 22 ++++------------------ 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/examples/api/src/views/Store.svelte b/examples/api/src/views/Store.svelte index f33002784e..6248b0099b 100644 --- a/examples/api/src/views/Store.svelte +++ b/examples/api/src/views/Store.svelte @@ -57,7 +57,7 @@ async function close() { try { await store.close(); - onMessage("Store is now closed, any new operations will now errors out"); + onMessage("Store is now closed, any new operations will error out"); } catch (error) { onMessage(error); } diff --git a/plugins/store/README.md b/plugins/store/README.md index 535f673b6a..811b00b786 100644 --- a/plugins/store/README.md +++ b/plugins/store/README.md @@ -85,7 +85,7 @@ if (val) { ### Persisting Values -Modifications mode to the store are automatically saved by defaut +Modifications made to the store are automatically saved by default You can manually save a store with: diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 19b6df9554..292c599009 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -167,10 +167,7 @@ export class LazyStore implements IStore { * A key-value store persisted by the backend layer. */ export class Store extends Resource implements IStore { - private constructor( - rid: number - // private readonly path: string - ) { + private constructor(rid: number) { super(rid) } @@ -188,10 +185,7 @@ export class Store extends Resource implements IStore { path, ...options }) - return new Store( - rid - // path - ) + return new Store(rid) } /** @@ -208,10 +202,7 @@ export class Store extends Resource implements IStore { path, ...options }) - return new Store( - rid - // path - ) + return new Store(rid) } /** @@ -219,12 +210,7 @@ export class Store extends Resource implements IStore { */ static async getStore(path: string): Promise { const rid = await invoke('plugin:store|get_store', { path }) - return rid - ? new Store( - rid - // path - ) - : undefined + return rid ? new Store(rid) : undefined } async set(key: string, value: unknown): Promise { From 3413d2a0f7276969575e8b68ec9d845ea28dfda8 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 10 Oct 2024 14:05:54 +0800 Subject: [PATCH 35/70] Use close instead --- plugins/store/src/store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 820759617a..b9122f99ab 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -567,7 +567,7 @@ impl Store { if let Some(rid) = stores.get(&store.path).copied() { drop(store); drop(stores); - let _ = app.resources_table().take::>(rid); + let _ = app.resources_table().close(rid); } } From c3748b39fee5582488cef4d7082afb784c9798b0 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 10 Oct 2024 15:29:25 +0800 Subject: [PATCH 36/70] Update breaking changes --- .changes/store-plugin-rework.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.changes/store-plugin-rework.md b/.changes/store-plugin-rework.md index 949df008ac..6a771690c6 100644 --- a/.changes/store-plugin-rework.md +++ b/.changes/store-plugin-rework.md @@ -2,13 +2,19 @@ "store": minor:breaking --- -**Breaking change**: Renamed `StoreCollection` to `StoreState` +**Breaking changes**: -Changes: +- Renamed `StoreCollection` to `StoreState` +- `StoreBuilder::build` now returns a `Result` +- `StoreExt::store` now returns `Arc` instead of `Store` + +Other Changes: -- Disallow calling `create_store` when there're still active stores with the same path alive - 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: @@ -16,9 +22,3 @@ New features: - Add default (de)serialize functions settings - Allow js to use pre-stored (de)serialize functions - Add back lazy store (implemented in js) - -Default changes: - -- Share store to resource table by default -- Enable auto save with 100ms debounce time by default -- Use pretty json by default, close #1690 \ No newline at end of file From d88fcb5c8a6340b626cb4ceba2c81ffc634b24ae Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 10 Oct 2024 15:53:32 +0800 Subject: [PATCH 37/70] Return result in resolve_store_path --- .changes/store-plugin-rework.md | 2 +- .../AppSettingsManager/src-tauri/src/main.rs | 2 +- plugins/store/src/lib.rs | 10 ++-- plugins/store/src/store.rs | 52 +++++-------------- 4 files changed, 20 insertions(+), 46 deletions(-) diff --git a/.changes/store-plugin-rework.md b/.changes/store-plugin-rework.md index 6a771690c6..2b6d3618d5 100644 --- a/.changes/store-plugin-rework.md +++ b/.changes/store-plugin-rework.md @@ -6,7 +6,7 @@ - Renamed `StoreCollection` to `StoreState` - `StoreBuilder::build` now returns a `Result` -- `StoreExt::store` now returns `Arc` instead of `Store` +- `StoreExt::store` now returns `Result>` Other Changes: diff --git a/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs b/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs index fbe250ae8d..f20db4fc23 100644 --- a/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs +++ b/plugins/store/examples/AppSettingsManager/src-tauri/src/main.rs @@ -17,7 +17,7 @@ fn main() { .plugin(tauri_plugin_store::Builder::new().build()) .setup(|app| { // Init store and load it from disk - let store = app.store("settings.json"); + let store = app.store("settings.json")?; app.listen("store://change", |event| { dbg!(event); }); diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 1edfa56919..15b98d38fb 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -134,7 +134,7 @@ async fn create_or_existing_store( serialize_fn_name, deserialize_fn_name, )?; - let (_, rid) = builder.build_or_existing_inner(); + let (_, rid) = builder.build_or_existing_inner()?; Ok(rid) } @@ -145,7 +145,7 @@ async fn get_store( path: PathBuf, ) -> Result> { let stores = store_state.stores.lock().unwrap(); - Ok(stores.get(&resolve_store_path(&app, path)).copied()) + Ok(stores.get(&resolve_store_path(&app, path)?).copied()) } #[tauri::command] @@ -244,7 +244,7 @@ async fn save(app: AppHandle, rid: ResourceId) -> Result<()> { pub trait StoreExt { /// Create a store or get an existing store with default settings at path - fn store(&self, path: impl AsRef) -> Arc>; + fn store(&self, path: impl AsRef) -> Result>>; /// Create a store with default settings fn create_store(&self, path: impl AsRef) -> Result>>; /// Get a store builder @@ -254,7 +254,7 @@ pub trait StoreExt { } impl> StoreExt for T { - fn store(&self, path: impl AsRef) -> Arc> { + fn store(&self, path: impl AsRef) -> Result>> { StoreBuilder::new(self.app_handle(), path).build_or_existing() } @@ -270,7 +270,7 @@ impl> StoreExt for T { let collection = self.state::(); let stores = collection.stores.lock().unwrap(); stores - .get(&resolve_store_path(self.app_handle(), path.as_ref())) + .get(&resolve_store_path(self.app_handle(), path.as_ref()).ok()?) .and_then(|rid| self.resources_table().get(*rid).ok()) } } diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index b9122f99ab..ccdb848711 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -27,13 +27,8 @@ pub type DeserializeFn = pub(crate) fn resolve_store_path( app: &AppHandle, path: impl AsRef, -) -> PathBuf { - dunce::simplified( - &app.path() - .resolve(path, BaseDirectory::AppData) - .expect("failed to resolve app dir"), - ) - .to_path_buf() +) -> crate::Result { + Ok(dunce::simplified(&app.path().resolve(path, BaseDirectory::AppData)?).to_path_buf()) } /// Builds a [`Store`] @@ -61,13 +56,12 @@ impl StoreBuilder { /// ``` pub fn new, P: AsRef>(manager: &M, path: P) -> Self { let app = manager.app_handle().clone(); - let path = resolve_store_path(&app, path); let state = app.state::(); let serialize_fn = state.default_serialize; let deserialize_fn = state.default_deserialize; Self { app, - path, + path: path.as_ref().to_path_buf(), defaults: None, serialize_fn, deserialize_fn, @@ -217,35 +211,15 @@ impl StoreBuilder { Ok((store, rid)) } - pub(crate) fn build_or_existing_inner(mut self) -> (Arc>, ResourceId) { - let state = self.app.state::(); - let mut stores = state.stores.lock().unwrap(); - - if let Some(rid) = stores.get(&self.path) { - (self.app.resources_table().get(*rid).unwrap(), *rid) - } else { - let mut store_inner = StoreInner::new( - self.app.clone(), - self.path.clone(), - self.defaults.take(), - self.serialize_fn, - self.deserialize_fn, - ); - if self.load_on_build { - let _ = store_inner.load(); + pub(crate) fn build_or_existing_inner(self) -> crate::Result<(Arc>, ResourceId)> { + { + let state = self.app.state::(); + let stores = state.stores.lock().unwrap(); + if let Some(rid) = stores.get(&self.path) { + return Ok((self.app.resources_table().get(*rid).unwrap(), *rid)); } - - let store = Store { - auto_save: self.auto_save, - auto_save_debounce_sender: Arc::new(Mutex::new(None)), - store: Arc::new(Mutex::new(store_inner)), - }; - - let store = Arc::new(store); - let rid = self.app.resources_table().add_arc(store.clone()); - stores.insert(self.path, rid); - (store, rid) } + self.build_inner() } /// Builds the [`Store`], also see [`build_or_existing`](Self::build_or_existing). @@ -283,9 +257,9 @@ impl StoreBuilder { /// Ok(()) /// }); /// ``` - pub fn build_or_existing(self) -> Arc> { - let (store, _) = self.build_or_existing_inner(); - store + pub fn build_or_existing(self) -> crate::Result>> { + let (store, _) = self.build_or_existing_inner()?; + Ok(store) } } From 39dec5d5b4d8f9831f85295b86a4a12557216074 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 10 Oct 2024 16:12:57 +0800 Subject: [PATCH 38/70] Change to close_resource and take &self --- plugins/store/src/store.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index ccdb848711..f988a1117f 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -530,10 +530,8 @@ impl Store { self.store.lock().unwrap().save() } - /// Removes the store from the resource table, - /// this doesn't remove other references of the same store held by you, - /// and the store is only truely closed once all reference held by you are also dropped - pub fn close_store(self) { + /// Removes the store from the resource table + pub fn close_resource(&self) { let store = self.store.lock().unwrap(); let app = store.app.clone(); let state = app.state::(); From 3f470a7a67312b3785bbddb5c8ddaca0abcd7422 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 10 Oct 2024 17:15:13 +0800 Subject: [PATCH 39/70] Clean up --- plugins/store/src/store.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index f988a1117f..a7dcc8342e 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -6,8 +6,7 @@ use crate::{ChangePayload, StoreState}; use serde_json::Value as JsonValue; use std::{ collections::HashMap, - fs::{create_dir_all, read, File}, - io::Write, + fs, path::{Path, PathBuf}, sync::{Arc, Mutex}, time::Duration, @@ -298,18 +297,17 @@ impl StoreInner { /// Saves the store to disk at the store's `path`. pub fn save(&self) -> crate::Result<()> { - create_dir_all(self.path.parent().expect("invalid store path"))?; + fs::create_dir_all(self.path.parent().expect("invalid store path"))?; let bytes = (self.serialize_fn)(&self.cache).map_err(crate::Error::Serialize)?; - let mut f = File::create(&self.path)?; - f.write_all(&bytes)?; + fs::write(&self.path, bytes)?; Ok(()) } /// Update the store from the on-disk state pub fn load(&mut self) -> crate::Result<()> { - let bytes = read(&self.path)?; + let bytes = fs::read(&self.path)?; self.cache .extend((self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?); From 5296ffa3de4b68f3ba2f621b5da26fb708963051 Mon Sep 17 00:00:00 2001 From: Tony <68118705+Legend-Master@users.noreply.github.com> Date: Fri, 11 Oct 2024 12:20:32 +0800 Subject: [PATCH 40/70] Apply suggestions from code review Co-authored-by: Amr Bashir --- .changes/store-plugin-rework-js-feat.md | 4 ++-- .changes/store-plugin-rework.md | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.changes/store-plugin-rework-js-feat.md b/.changes/store-plugin-rework-js-feat.md index 8eb619880a..3306af24f8 100644 --- a/.changes/store-plugin-rework-js-feat.md +++ b/.changes/store-plugin-rework-js-feat.md @@ -3,5 +3,5 @@ --- - Add `getStore` -- Add an option to use pre-stored (de)serialize functions -- Add back lazy store `LazyStore` +- Add an option to use pre-stored (de)serialize functions (registered on rust) +- Add `LazyStore` diff --git a/.changes/store-plugin-rework.md b/.changes/store-plugin-rework.md index 2b6d3618d5..0d4776b02a 100644 --- a/.changes/store-plugin-rework.md +++ b/.changes/store-plugin-rework.md @@ -2,13 +2,13 @@ "store": minor:breaking --- -**Breaking changes**: +### Breaking changes: - Renamed `StoreCollection` to `StoreState` - `StoreBuilder::build` now returns a `Result` - `StoreExt::store` now returns `Result>` -Other Changes: +### Enhancements: - Save and cancel pending auto save on drop - Use absolute path as store's key, fix #984 @@ -16,9 +16,9 @@ Other Changes: - Enable auto save with 100ms debounce time by default - Use pretty json by default, close #1690 -New features: +### New features: -- Add `getStore`/`get_store` share stores across js and rust side +- Add `get_store` to get shared stores across js and rust side - Add default (de)serialize functions settings - Allow js to use pre-stored (de)serialize functions - Add back lazy store (implemented in js) From a020ae38e249fe1e395bf400b67e80fea46f6036 Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 11 Oct 2024 13:24:47 +0800 Subject: [PATCH 41/70] Remove unused pub(crate) --- plugins/store/src/store.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index a7dcc8342e..c6a113db81 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -262,19 +262,19 @@ impl StoreBuilder { } } -pub(crate) enum AutoSaveMessage { +enum AutoSaveMessage { Reset, Cancel, } #[derive(Clone)] pub struct StoreInner { - pub(crate) app: AppHandle, - pub(crate) path: PathBuf, - pub(crate) cache: HashMap, - pub(crate) defaults: Option>, - pub(crate) serialize_fn: SerializeFn, - pub(crate) deserialize_fn: DeserializeFn, + app: AppHandle, + path: PathBuf, + cache: HashMap, + defaults: Option>, + serialize_fn: SerializeFn, + deserialize_fn: DeserializeFn, } impl StoreInner { From 7e33a5eba2d41cbd31e2c27c6ef73d0ad42a2366 Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 11 Oct 2024 13:29:57 +0800 Subject: [PATCH 42/70] Update change file --- .changes/store-plugin-rework.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.changes/store-plugin-rework.md b/.changes/store-plugin-rework.md index 0d4776b02a..0d9584afb1 100644 --- a/.changes/store-plugin-rework.md +++ b/.changes/store-plugin-rework.md @@ -19,6 +19,5 @@ ### New features: - Add `get_store` to get shared stores across js and rust side -- Add default (de)serialize functions settings -- Allow js to use pre-stored (de)serialize functions -- Add back lazy store (implemented in js) +- 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` From 1498eb5404bc3df265742a723cb0341db3cfcdcc Mon Sep 17 00:00:00 2001 From: Tony Date: Mon, 14 Oct 2024 09:58:52 +0800 Subject: [PATCH 43/70] Expose resolve_store_path --- plugins/store/src/lib.rs | 3 +-- plugins/store/src/store.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 15b98d38fb..dbc3509367 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -21,8 +21,7 @@ use std::{ sync::{Arc, Mutex}, time::Duration, }; -use store::resolve_store_path; -pub use store::{DeserializeFn, SerializeFn, Store, StoreBuilder, StoreInner}; +pub use store::{resolve_store_path, DeserializeFn, SerializeFn, Store, StoreBuilder, StoreInner}; use tauri::{ plugin::{self, TauriPlugin}, AppHandle, Manager, ResourceId, RunEvent, Runtime, State, diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index c6a113db81..4e62a75f49 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -23,7 +23,7 @@ pub type SerializeFn = pub type DeserializeFn = fn(&[u8]) -> Result, Box>; -pub(crate) fn resolve_store_path( +pub fn resolve_store_path( app: &AppHandle, path: impl AsRef, ) -> crate::Result { From 02d077ea3bc9615b624396d11aa13cdcda9b961e Mon Sep 17 00:00:00 2001 From: Tony Date: Mon, 14 Oct 2024 15:32:47 +0800 Subject: [PATCH 44/70] Remove with_store --- plugins/store/src/store.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 4e62a75f49..e800c99fd3 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -442,12 +442,12 @@ impl Resource for Store { } impl Store { - /// Do something with the inner store, - /// useful for batching some work if you need higher performance - pub fn with_store(&self, f: impl FnOnce(&mut StoreInner) -> T) -> T { - let mut store = self.store.lock().unwrap(); - f(&mut store) - } + // /// Do something with the inner store, + // /// useful for batching some work if you need higher performance + // pub fn with_store(&self, f: impl FnOnce(&mut StoreInner) -> T) -> T { + // let mut store = self.store.lock().unwrap(); + // f(&mut store) + // } /// Inserts a key-value pair into the store. pub fn set(&self, key: impl Into, value: impl Into) { From 99321ddbfac3bcd4156aab769213cfda91d9dad5 Mon Sep 17 00:00:00 2001 From: Tony Date: Mon, 14 Oct 2024 20:34:43 +0800 Subject: [PATCH 45/70] Remove StoreInner from pub and expose is_empty --- plugins/store/src/lib.rs | 2 +- plugins/store/src/store.rs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index dbc3509367..5eecf8d738 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -21,7 +21,7 @@ use std::{ sync::{Arc, Mutex}, time::Duration, }; -pub use store::{resolve_store_path, DeserializeFn, SerializeFn, Store, StoreBuilder, StoreInner}; +pub use store::{resolve_store_path, DeserializeFn, SerializeFn, Store, StoreBuilder}; use tauri::{ plugin::{self, TauriPlugin}, AppHandle, Manager, ResourceId, RunEvent, Runtime, State, diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index e800c99fd3..30e131eef2 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -268,7 +268,7 @@ enum AutoSaveMessage { } #[derive(Clone)] -pub struct StoreInner { +struct StoreInner { app: AppHandle, path: PathBuf, cache: HashMap, @@ -515,6 +515,11 @@ impl Store { self.store.lock().unwrap().len() } + /// Returns true if the store contains no elements. + pub fn is_empty(&self) -> bool { + self.store.lock().unwrap().is_empty() + } + /// Update the store from the on-disk state pub fn load(&self) -> crate::Result<()> { self.store.lock().unwrap().load() From 1c401f7cb00d78245195332efd76cbdf5d42f074 Mon Sep 17 00:00:00 2001 From: Tony Date: Mon, 14 Oct 2024 22:35:54 +0800 Subject: [PATCH 46/70] Fix wrong jsdoc param --- plugins/store/guest-js/index.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 292c599009..ca29fc8f2f 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -33,8 +33,8 @@ export type StoreOptions = { } /** - * @param path: Path to save the store in `app_data_dir` - * @param options: Store configuration options + * @param path Path to save the store in `app_data_dir` + * @param options Store configuration options * * @throws If a store at that path already exists */ @@ -48,8 +48,8 @@ export async function createStore( /** * Create a new Store or get the existing store with the path * - * @param path: Path to save the store in `app_data_dir` - * @param options: Store configuration options + * @param path Path to save the store in `app_data_dir` + * @param options Store configuration options */ export async function createOrExistingStore( path: string, @@ -59,7 +59,7 @@ export async function createOrExistingStore( } /** - * @param path: Path of the store in the rust side + * @param path Path of the store in the rust side */ export async function getStore(path: string): Promise { return await Store.getStore(path) @@ -80,8 +80,8 @@ export class LazyStore implements IStore { /** * Note that the options are not applied if someone else already created the store - * @param path: Path to save the store in `app_data_dir` - * @param options: Store configuration options + * @param path Path to save the store in `app_data_dir` + * @param options Store configuration options */ constructor( private readonly path: string, @@ -172,8 +172,8 @@ export class Store extends Resource implements IStore { } /** - * @param path: Path to save the store in `app_data_dir` - * @param options: Store configuration options + * @param path Path to save the store in `app_data_dir` + * @param options Store configuration options * * @throws If a store at that path already exists */ @@ -191,8 +191,8 @@ export class Store extends Resource implements IStore { /** * Create a new Store or get the existing store with the path * - * @param path: Path to save the store in `app_data_dir` - * @param options: Store configuration options + * @param path Path to save the store in `app_data_dir` + * @param options Store configuration options */ static async createOrExistingStore( path: string, @@ -206,7 +206,7 @@ export class Store extends Resource implements IStore { } /** - * @param path: Path of the store in the rust side + * @param path Path of the store in the rust side */ static async getStore(path: string): Promise { const rid = await invoke('plugin:store|get_store', { path }) From 7aca25520941d9e32c6c2fa8ae1f090bfccbb425 Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 15 Oct 2024 09:34:52 +0800 Subject: [PATCH 47/70] Update readme --- plugins/store/README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plugins/store/README.md b/plugins/store/README.md index 811b00b786..5b60fcd2df 100644 --- a/plugins/store/README.md +++ b/plugins/store/README.md @@ -68,9 +68,9 @@ fn main() { Afterwards all the plugin's APIs are available through the JavaScript guest bindings: ```typescript -import { LazyStore } from '@tauri-apps/plugin-store' +import { createStore } from '@tauri-apps/plugin-store' -const store = new LazyStore('settings.json') +const store = await createStore('settings.json') await store.set('some-key', { value: 5 }) @@ -100,6 +100,16 @@ 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: From 083c165543f3be5cea5d127d916d3e118e782808 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Oct 2024 07:31:54 -0300 Subject: [PATCH 48/70] rename createOrExistingStore to createOrLoad --- plugins/store/api-iife.js | 2 +- plugins/store/build.rs | 2 +- plugins/store/guest-js/index.ts | 6 +++--- .../commands/create_or_existing_store.toml | 13 ------------- .../autogenerated/commands/create_or_load.toml | 13 +++++++++++++ .../store/permissions/autogenerated/reference.md | 8 ++++---- plugins/store/permissions/schemas/schema.json | 8 ++++---- plugins/store/src/lib.rs | 4 ++-- 8 files changed, 28 insertions(+), 28 deletions(-) delete mode 100644 plugins/store/permissions/autogenerated/commands/create_or_existing_store.toml create mode 100644 plugins/store/permissions/autogenerated/commands/create_or_load.toml diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index af3d90ab78..20a3ce650a 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,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.createOrExistingStore(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"}(r||(r={}));class c extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new c(r)}static async createOrExistingStore(t,e){const r=await s("plugin:store|create_or_existing_store",{path:t,...e});return new c(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new c(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,r]=await s("plugin:store|get",{rid:this.rid,key:t});return r?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 load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.exists?r.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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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 load(){await(await this.store).load()}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=c,t.createOrExistingStore=o,t.createStore=async function(t,e){return await c.createStore(t,e)},t.getStore=async function(t){return await c.getStore(t)},t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.createOrExistingStore(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"}(r||(r={}));class c extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new c(r)}static async createOrLoad(t,e){const r=await s("plugin:store|create_or_load",{path:t,...e});return new c(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new c(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,r]=await s("plugin:store|get",{rid:this.rid,key:t});return r?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 load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.exists?r.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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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 load(){await(await this.store).load()}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=c,t.createOrExistingStore=o,t.createStore=async function(t,e){return await c.createStore(t,e)},t.getStore=async function(t){return await c.getStore(t)},t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} diff --git a/plugins/store/build.rs b/plugins/store/build.rs index e2530539c8..75b14e222d 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -4,7 +4,7 @@ const COMMANDS: &[&str] = &[ "create_store", - "create_or_existing_store", + "create_or_load", "get_store", "close_store", "set", diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index ca29fc8f2f..3f71f7f68a 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -89,7 +89,7 @@ export class LazyStore implements IStore { ) {} /** - * Init/load the store if it's not already + * Init/load the store if it's not loaded already */ async init(): Promise { await this.store @@ -194,11 +194,11 @@ export class Store extends Resource implements IStore { * @param path Path to save the store in `app_data_dir` * @param options Store configuration options */ - static async createOrExistingStore( + static async createOrLoad( path: string, options?: StoreOptions ): Promise { - const rid = await invoke('plugin:store|create_or_existing_store', { + const rid = await invoke('plugin:store|create_or_load', { path, ...options }) diff --git a/plugins/store/permissions/autogenerated/commands/create_or_existing_store.toml b/plugins/store/permissions/autogenerated/commands/create_or_existing_store.toml deleted file mode 100644 index c4555c91cd..0000000000 --- a/plugins/store/permissions/autogenerated/commands/create_or_existing_store.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-create-or-existing-store" -description = "Enables the create_or_existing_store command without any pre-configured scope." -commands.allow = ["create_or_existing_store"] - -[[permission]] -identifier = "deny-create-or-existing-store" -description = "Denies the create_or_existing_store command without any pre-configured scope." -commands.deny = ["create_or_existing_store"] diff --git a/plugins/store/permissions/autogenerated/commands/create_or_load.toml b/plugins/store/permissions/autogenerated/commands/create_or_load.toml new file mode 100644 index 0000000000..8e821d7b07 --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/create_or_load.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-create-or-load" +description = "Enables the create_or_load command without any pre-configured scope." +commands.allow = ["create_or_load"] + +[[permission]] +identifier = "deny-create-or-load" +description = "Denies the create_or_load command without any pre-configured scope." +commands.deny = ["create_or_load"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 1e0726a9f3..1306dbac67 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -90,12 +90,12 @@ Denies the close_store command without any pre-configured scope. -`store:allow-create-or-existing-store` +`store:allow-create-or-load` -Enables the create_or_existing_store command without any pre-configured scope. +Enables the create_or_load command without any pre-configured scope. @@ -103,12 +103,12 @@ Enables the create_or_existing_store command without any pre-configured scope. -`store:deny-create-or-existing-store` +`store:deny-create-or-load` -Denies the create_or_existing_store command without any pre-configured scope. +Denies the create_or_load command without any pre-configured scope. diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index 277190472d..89eeb0c0de 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -315,14 +315,14 @@ "const": "deny-close-store" }, { - "description": "Enables the create_or_existing_store command without any pre-configured scope.", + "description": "Enables the create_or_load command without any pre-configured scope.", "type": "string", - "const": "allow-create-or-existing-store" + "const": "allow-create-or-load" }, { - "description": "Denies the create_or_existing_store command without any pre-configured scope.", + "description": "Denies the create_or_load command without any pre-configured scope.", "type": "string", - "const": "deny-create-or-existing-store" + "const": "deny-create-or-load" }, { "description": "Enables the create_store command without any pre-configured scope.", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 5eecf8d738..53530c7ca6 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -117,7 +117,7 @@ async fn create_store( } #[tauri::command] -async fn create_or_existing_store( +async fn create_or_load( app: AppHandle, store_state: State<'_, StoreState>, path: PathBuf, @@ -385,9 +385,9 @@ impl Builder { plugin::Builder::new("store") .invoke_handler(tauri::generate_handler![ create_store, + create_or_load, get_store, close_store, - create_or_existing_store, set, get, has, From ff9bddd95c6f4aba5b2f6b973d64e42935bb6e55 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Oct 2024 08:06:27 -0300 Subject: [PATCH 49/70] make api consistent with the JS implementation, add examples --- plugins/store/api-iife.js | 2 +- plugins/store/guest-js/index.ts | 71 ++++++++++++++++++------- plugins/store/src/lib.rs | 94 +++++++++++++++++++++++++++++---- plugins/store/src/store.rs | 55 ++++++++----------- 4 files changed, 159 insertions(+), 63 deletions(-) diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index 20a3ce650a..66e5ffd107 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,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.createOrExistingStore(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"}(r||(r={}));class c extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new c(r)}static async createOrLoad(t,e){const r=await s("plugin:store|create_or_load",{path:t,...e});return new c(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new c(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,r]=await s("plugin:store|get",{rid:this.rid,key:t});return r?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 load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.exists?r.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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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 load(){await(await this.store).load()}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=c,t.createOrExistingStore=o,t.createStore=async function(t,e){return await c.createStore(t,e)},t.getStore=async function(t){return await c.getStore(t)},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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.createOrLoad(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 c extends i{constructor(t){super(t)}static async create(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new c(a)}static async createOrLoad(t,e){const a=await s("plugin:store|create_or_load",{path:t,...e});return new c(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new c(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 load(){await s("plugin:store|load",{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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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 load(){await(await this.store).load()}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=c,t.create=async function(t,e){return await c.create(t,e)},t.createOrLoad=o,t.getStore=async function(t){return await c.get(t)},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 3f71f7f68a..1f2c0cffcb 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -38,31 +38,31 @@ export type StoreOptions = { * * @throws If a store at that path already exists */ -export async function createStore( +export async function create( path: string, options?: StoreOptions ): Promise { - return await Store.createStore(path, options) + return await Store.create(path, options) } /** - * Create a new Store or get the existing store with the path + * Create a new Store or load the existing store with the path * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options */ -export async function createOrExistingStore( +export async function createOrLoad( path: string, options?: StoreOptions ): Promise { - return await Store.createOrExistingStore(path, options) + return await Store.createOrLoad(path, options) } /** - * @param path Path of the store in the rust side + * @param path Path of the store */ -export async function getStore(path: string): Promise { - return await Store.getStore(path) +export async function getStore(path: string): Promise { + return await Store.get(path) } /** @@ -73,7 +73,7 @@ export class LazyStore implements IStore { private get store(): Promise { if (!this._store) { - this._store = createOrExistingStore(this.path, this.options) + this._store = createOrLoad(this.path, this.options) } return this._store } @@ -172,15 +172,24 @@ export class Store extends Resource implements IStore { } /** + * Create a new store. + * + * If the store already exists, its data will be overwritten. + * + * If the store is already loaded you must use {@link getStore} instead. + * + * @example + * ```typescript + * import { Store } from '@tauri-apps/api/store'; + * const store = await Store.create('store.json'); + * ``` + * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options * * @throws If a store at that path already exists */ - static async createStore( - path: string, - options?: StoreOptions - ): Promise { + static async create(path: string, options?: StoreOptions): Promise { const rid = await invoke('plugin:store|create_store', { path, ...options @@ -189,7 +198,18 @@ export class Store extends Resource implements IStore { } /** - * Create a new Store or get the existing store with the path + * Create a new Store or load the existing store with the path. + * + * If the store is already loaded you must use {@link getStore} instead. + * + * @example + * ```typescript + * import { Store } from '@tauri-apps/api/store'; + * let store = await Store.get('store.json'); + * if (!store) { + * store = await Store.createOrLoad('store.json'); + * } + * ``` * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options @@ -206,11 +226,26 @@ export class Store extends Resource implements IStore { } /** - * @param path Path of the store in the rust side + * Gets an already loaded store. + * + * If the store is not loaded, returns `null`. In this case, + * you must either {@link Store.create | create} it or {@link Store.createOrLoad createOrLoad} it. + * + * @example + * ```typescript + * import { Store } from '@tauri-apps/api/store'; + * let store = await Store.get('store.json'); + * if (!store) { + * store = await Store.createOrLoad('store.json'); + * } + * ``` + * + * @param path Path of the store. */ - static async getStore(path: string): Promise { - const rid = await invoke('plugin:store|get_store', { path }) - return rid ? new Store(rid) : undefined + static async get(path: string): Promise { + return await invoke('plugin:store|get_store', { path }).then( + (rid) => (rid ? new Store(rid) : null) + ) } async set(key: string, value: unknown): Promise { diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 53530c7ca6..f9cf5bc0ea 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -112,7 +112,7 @@ async fn create_store( serialize_fn_name, deserialize_fn_name, )?; - let (_, rid) = builder.build_inner()?; + let (_, rid) = builder.create_inner()?; Ok(rid) } @@ -133,7 +133,7 @@ async fn create_or_load( serialize_fn_name, deserialize_fn_name, )?; - let (_, rid) = builder.build_or_existing_inner()?; + let (_, rid) = builder.create_or_load_inner()?; Ok(rid) } @@ -242,23 +242,95 @@ async fn save(app: AppHandle, rid: ResourceId) -> Result<()> { } pub trait StoreExt { - /// Create a store or get an existing store with default settings at path + /// Create a store or load an existing store with default settings at the given path. + /// + /// If the store is already loaded, its instance is automatically returned. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_store::StoreExt; + /// + /// tauri::Builder::default() + /// .plugin(tauri_plugin_store::Builder::default().build()) + /// .setup(|app| { + /// let store = app.store("my-store")?; + /// Ok(()) + /// }); + /// ``` fn store(&self, path: impl AsRef) -> Result>>; - /// Create a store with default settings + /// Create a store with default settings. + /// + /// If the store is already loaded you must check with [`Self::get_store`] or prefer [`Self::store`] + /// as it will return `Err(Error::AlreadyExists)`. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_store::StoreExt; + /// + /// tauri::Builder::default() + /// .plugin(tauri_plugin_store::Builder::default().build()) + /// .setup(|app| { + /// let store = app.create_store("my-store")?; + /// Ok(()) + /// }); + /// ``` fn create_store(&self, path: impl AsRef) -> Result>>; - /// Get a store builder + /// Get a store builder. + /// + /// The builder can be used to configure the store. + /// To use the default settings see [`Self::store`]. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_store::StoreExt; + /// use std::time::Duration; + /// + /// tauri::Builder::default() + /// .plugin(tauri_plugin_store::Builder::default().build()) + /// .setup(|app| { + /// let store = app.store_builder("users.json").auto_save(Duration::from_secs(1)).create_or_load()?; + /// Ok(()) + /// }); + /// ``` fn store_builder(&self, path: impl AsRef) -> StoreBuilder; - /// Get an existing store + /// Get a handle of an already loaded store. + /// + /// If the store is not loaded or does not exist, it returns `None`. + /// In this case, you should initialize it with [`Self::store`]. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_store::StoreExt; + /// + /// tauri::Builder::default() + /// .plugin(tauri_plugin_store::Builder::default().build()) + /// .setup(|app| { + /// let store = if let Some(s) = app.get_store("store.json") { + /// s + /// } else { + /// app.store("store.json")? + /// }; + /// Ok(()) + /// }); + /// ``` fn get_store(&self, path: impl AsRef) -> Option>>; } impl> StoreExt for T { fn store(&self, path: impl AsRef) -> Result>> { - StoreBuilder::new(self.app_handle(), path).build_or_existing() + let path = path.as_ref(); + if let Some(store) = self.get_store(path) { + return Ok(store); + } + StoreBuilder::new(self.app_handle(), path).create_or_load() } fn create_store(&self, path: impl AsRef) -> Result>> { - StoreBuilder::new(self.app_handle(), path).build() + StoreBuilder::new(self.app_handle(), path).create() } fn store_builder(&self, path: impl AsRef) -> StoreBuilder { @@ -327,7 +399,7 @@ impl Builder { /// tauri_plugin_store::Builder::default() /// .register_serialize_fn("no-pretty-json".to_owned(), no_pretty_json) /// .build(), - /// ) + /// ); /// ``` pub fn register_serialize_fn(mut self, name: String, serialize_fn: SerializeFn) -> Self { self.serialize_fns.insert(name, serialize_fn); @@ -356,7 +428,7 @@ impl Builder { /// tauri_plugin_store::Builder::default() /// .default_serialize_fn(no_pretty_json) /// .build(), - /// ) + /// ); /// ``` pub fn default_serialize_fn(mut self, serialize_fn: SerializeFn) -> Self { self.default_serialize = serialize_fn; @@ -377,7 +449,7 @@ impl Builder { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin").build()?; + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin").create_or_load()?; /// Ok(()) /// }); /// ``` diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 30e131eef2..1d8d2dd1e7 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -38,7 +38,6 @@ pub struct StoreBuilder { serialize_fn: SerializeFn, deserialize_fn: DeserializeFn, auto_save: Option, - load_on_build: bool, } impl StoreBuilder { @@ -65,7 +64,6 @@ impl StoreBuilder { serialize_fn, deserialize_fn, auto_save: Some(Duration::from_millis(100)), - load_on_build: true, } } @@ -81,7 +79,7 @@ impl StoreBuilder { /// /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin") /// .defaults(defaults) - /// .build()?; + /// .create_or_load()?; /// Ok(()) /// }); /// ``` @@ -99,7 +97,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin") /// .default("foo".to_string(), "bar") - /// .build()?; + /// .create_or_load()?; /// Ok(()) /// }); /// ``` @@ -121,7 +119,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .serialize(|cache| serde_json::to_vec(&cache).map_err(Into::into)) - /// .build()?; + /// .create_or_load()?; /// Ok(()) /// }); /// ``` @@ -139,7 +137,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .deserialize(|bytes| serde_json::from_slice(&bytes).map_err(Into::into)) - /// .build()?; + /// .create_or_load()?; /// Ok(()) /// }); /// ``` @@ -157,7 +155,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .auto_save(std::time::Duration::from_millis(100)) - /// .build()?; + /// .create_or_load()?; /// Ok(()) /// }); /// ``` @@ -172,13 +170,7 @@ impl StoreBuilder { self } - /// Skip loading the store on build - pub fn skip_initial_load(mut self) -> Self { - self.load_on_build = false; - self - } - - pub(crate) fn build_inner(mut self) -> crate::Result<(Arc>, ResourceId)> { + pub(crate) fn build_inner(mut self, load: bool) -> crate::Result<(Arc>, ResourceId)> { let state = self.app.state::(); let mut stores = state.stores.lock().unwrap(); @@ -193,7 +185,7 @@ impl StoreBuilder { self.serialize_fn, self.deserialize_fn, ); - if self.load_on_build { + if load { let _ = store_inner.load(); } @@ -210,17 +202,6 @@ impl StoreBuilder { Ok((store, rid)) } - pub(crate) fn build_or_existing_inner(self) -> crate::Result<(Arc>, ResourceId)> { - { - let state = self.app.state::(); - let stores = state.stores.lock().unwrap(); - if let Some(rid) = stores.get(&self.path) { - return Ok((self.app.resources_table().get(*rid).unwrap(), *rid)); - } - } - self.build_inner() - } - /// Builds the [`Store`], also see [`build_or_existing`](Self::build_or_existing). /// /// This loads the store from disk and put the store in the app's resource table, @@ -236,30 +217,38 @@ impl StoreBuilder { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").build()?; + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").create_or_load()?; /// Ok(()) /// }); /// ``` - pub fn build(self) -> crate::Result>> { - let (store, _) = self.build_inner()?; + pub fn create(self) -> crate::Result>> { + let (store, _) = self.create_inner()?; Ok(store) } - /// Get the existing store with the same path or builds a new [`Store`], also see [`build`](Self::build). + pub(crate) fn create_inner(self) -> crate::Result<(Arc>, ResourceId)> { + self.build_inner(false) + } + + /// Get the existing store with the same path or creates a new [`Store`], also see [`create`](Self::create). /// /// # Examples /// ``` /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").build_or_existing(); + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").create_or_load(); /// Ok(()) /// }); /// ``` - pub fn build_or_existing(self) -> crate::Result>> { - let (store, _) = self.build_or_existing_inner()?; + pub fn create_or_load(self) -> crate::Result>> { + let (store, _) = self.create_or_load_inner()?; Ok(store) } + + pub(crate) fn create_or_load_inner(self) -> crate::Result<(Arc>, ResourceId)> { + self.build_inner(true) + } } enum AutoSaveMessage { From afbb5aae05986e7a6ef6f19fdc4e1bd2174d230f Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Oct 2024 08:08:52 -0300 Subject: [PATCH 50/70] fmt --- plugins/store/build.rs | 2 +- .../store/permissions/autogenerated/reference.md | 14 +++++++------- plugins/store/permissions/default.toml | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/plugins/store/build.rs b/plugins/store/build.rs index 75b14e222d..174ee42848 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -15,8 +15,8 @@ const COMMANDS: &[&str] = &[ "reset", "keys", "values", - "length", "entries", + "length", "load", "save", ]; diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 1306dbac67..51e5604f00 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -10,21 +10,21 @@ All operations are enabled by default. - `allow-create-store` -- `allow-create-or-existing-store` +- `allow-create-or-load` - `allow-get-store` - `allow-close-store` -- `allow-clear` -- `allow-delete` -- `allow-entries` +- `allow-set` - `allow-get` - `allow-has` +- `allow-delete` +- `allow-clear` +- `allow-reset` - `allow-keys` +- `allow-values` +- `allow-entries` - `allow-length` - `allow-load` -- `allow-reset` - `allow-save` -- `allow-set` -- `allow-values` ## Permission Table diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index fa7264f987..48b3a09082 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -12,19 +12,19 @@ All operations are enabled by default. """ permissions = [ "allow-create-store", - "allow-create-or-existing-store", + "allow-create-or-load", "allow-get-store", "allow-close-store", - "allow-clear", - "allow-delete", - "allow-entries", + "allow-set", "allow-get", "allow-has", + "allow-delete", + "allow-clear", + "allow-reset", "allow-keys", + "allow-values", + "allow-entries", "allow-length", "allow-load", - "allow-reset", "allow-save", - "allow-set", - "allow-values", ] From 028d6661ca75b06b85b51e8e37ad6189d277a83a Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Oct 2024 09:52:20 -0300 Subject: [PATCH 51/70] reintroduce "get existing store" behavior for create_or_load --- plugins/store/guest-js/index.ts | 6 +++++- plugins/store/src/lib.rs | 10 ++++++---- plugins/store/src/store.rs | 13 ++++++++++++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 1f2c0cffcb..ae14ddc946 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -46,7 +46,11 @@ export async function create( } /** - * Create a new Store or load the existing store with the path + * Create a new Store or load the existing store with the path. + * + * If the store at the given path is already loaded, + * its instance is returned regardless of the options object. + * If the settings to not match an error is returned. * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index f9cf5bc0ea..9251fa0044 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -299,7 +299,9 @@ pub trait StoreExt { /// Get a handle of an already loaded store. /// /// If the store is not loaded or does not exist, it returns `None`. - /// In this case, you should initialize it with [`Self::store`]. + /// + /// Note that using this function can cause race conditions if you fallback to creating or loading the store, + /// so you should consider using [`Self::store`] if you are not sure if the store is loaded or not. /// /// # Examples /// @@ -312,6 +314,9 @@ pub trait StoreExt { /// let store = if let Some(s) = app.get_store("store.json") { /// s /// } else { + /// // this is not thread safe; if another thread is doing the same load/create, + /// // there will be a race condition; in this case we could remove the get_store + /// // and only run app.store() as it will return the existing store if it has been loaded /// app.store("store.json")? /// }; /// Ok(()) @@ -323,9 +328,6 @@ pub trait StoreExt { impl> StoreExt for T { fn store(&self, path: impl AsRef) -> Result>> { let path = path.as_ref(); - if let Some(store) = self.get_store(path) { - return Ok(store); - } StoreBuilder::new(self.app_handle(), path).create_or_load() } diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 1d8d2dd1e7..8091f13274 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -230,7 +230,11 @@ impl StoreBuilder { self.build_inner(false) } - /// Get the existing store with the same path or creates a new [`Store`], also see [`create`](Self::create). + /// Get the existing store with the same path or creates a new [`Store`]. + /// + /// If a store with the same path has already been loaded its instance is returned. + /// + /// See [`create`](Self::create) if you want to force create a new store in the path. /// /// # Examples /// ``` @@ -247,6 +251,13 @@ impl StoreBuilder { } pub(crate) fn create_or_load_inner(self) -> crate::Result<(Arc>, ResourceId)> { + { + let state = self.app.state::(); + let stores = state.stores.lock().unwrap(); + if let Some(rid) = stores.get(&self.path) { + return Ok((self.app.resources_table().get(*rid).unwrap(), *rid)); + } + } self.build_inner(true) } } From 8a8bdcac23bd77a4b2cabe0acbf45ae7fdff8a30 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Oct 2024 10:22:55 -0300 Subject: [PATCH 52/70] rename createOrLoad to newOrExisting --- plugins/store/api-iife.js | 2 +- plugins/store/build.rs | 2 +- plugins/store/guest-js/index.ts | 16 +++--- .../commands/create_or_load.toml | 13 ----- .../commands/new_or_existing.toml | 13 +++++ .../permissions/autogenerated/reference.md | 54 +++++++++---------- plugins/store/permissions/default.toml | 2 +- plugins/store/permissions/schemas/schema.json | 20 +++---- plugins/store/src/lib.rs | 12 ++--- plugins/store/src/store.rs | 20 +++---- 10 files changed, 77 insertions(+), 77 deletions(-) delete mode 100644 plugins/store/permissions/autogenerated/commands/create_or_load.toml create mode 100644 plugins/store/permissions/autogenerated/commands/new_or_existing.toml diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index 66e5ffd107..0179964057 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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.createOrLoad(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 c extends i{constructor(t){super(t)}static async create(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new c(a)}static async createOrLoad(t,e){const a=await s("plugin:store|create_or_load",{path:t,...e});return new c(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new c(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 load(){await s("plugin:store|load",{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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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 load(){await(await this.store).load()}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=c,t.create=async function(t,e){return await c.create(t,e)},t.createOrLoad=o,t.getStore=async function(t){return await c.get(t)},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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.newOrExisting(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 c extends i{constructor(t){super(t)}static async create(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new c(a)}static async newOrExisting(t,e){const a=await s("plugin:store|new_or_existing",{path:t,...e});return new c(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new c(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 load(){await s("plugin:store|load",{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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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 load(){await(await this.store).load()}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=c,t.create=async function(t,e){return await c.create(t,e)},t.getStore=async function(t){return await c.get(t)},t.newOrExisting=o,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} diff --git a/plugins/store/build.rs b/plugins/store/build.rs index 174ee42848..9024df232a 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -4,7 +4,7 @@ const COMMANDS: &[&str] = &[ "create_store", - "create_or_load", + "new_or_existing", "get_store", "close_store", "set", diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index ae14ddc946..6a3227da28 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -55,11 +55,11 @@ export async function create( * @param path Path to save the store in `app_data_dir` * @param options Store configuration options */ -export async function createOrLoad( +export async function newOrExisting( path: string, options?: StoreOptions ): Promise { - return await Store.createOrLoad(path, options) + return await Store.newOrExisting(path, options) } /** @@ -77,7 +77,7 @@ export class LazyStore implements IStore { private get store(): Promise { if (!this._store) { - this._store = createOrLoad(this.path, this.options) + this._store = newOrExisting(this.path, this.options) } return this._store } @@ -211,18 +211,18 @@ export class Store extends Resource implements IStore { * import { Store } from '@tauri-apps/api/store'; * let store = await Store.get('store.json'); * if (!store) { - * store = await Store.createOrLoad('store.json'); + * store = await Store.newOrExisting('store.json'); * } * ``` * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options */ - static async createOrLoad( + static async newOrExisting( path: string, options?: StoreOptions ): Promise { - const rid = await invoke('plugin:store|create_or_load', { + const rid = await invoke('plugin:store|new_or_existing', { path, ...options }) @@ -233,14 +233,14 @@ export class Store extends Resource implements IStore { * Gets an already loaded store. * * If the store is not loaded, returns `null`. In this case, - * you must either {@link Store.create | create} it or {@link Store.createOrLoad createOrLoad} it. + * you must either {@link Store.create | create} it or {@link Store.newOrExisting newOrExisting} it. * * @example * ```typescript * import { Store } from '@tauri-apps/api/store'; * let store = await Store.get('store.json'); * if (!store) { - * store = await Store.createOrLoad('store.json'); + * store = await Store.newOrExisting('store.json'); * } * ``` * diff --git a/plugins/store/permissions/autogenerated/commands/create_or_load.toml b/plugins/store/permissions/autogenerated/commands/create_or_load.toml deleted file mode 100644 index 8e821d7b07..0000000000 --- a/plugins/store/permissions/autogenerated/commands/create_or_load.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-create-or-load" -description = "Enables the create_or_load command without any pre-configured scope." -commands.allow = ["create_or_load"] - -[[permission]] -identifier = "deny-create-or-load" -description = "Denies the create_or_load command without any pre-configured scope." -commands.deny = ["create_or_load"] diff --git a/plugins/store/permissions/autogenerated/commands/new_or_existing.toml b/plugins/store/permissions/autogenerated/commands/new_or_existing.toml new file mode 100644 index 0000000000..c430ecfb75 --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/new_or_existing.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-new-or-existing" +description = "Enables the new_or_existing command without any pre-configured scope." +commands.allow = ["new_or_existing"] + +[[permission]] +identifier = "deny-new-or-existing" +description = "Denies the new_or_existing command without any pre-configured scope." +commands.deny = ["new_or_existing"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 51e5604f00..804a77a023 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -10,7 +10,7 @@ All operations are enabled by default. - `allow-create-store` -- `allow-create-or-load` +- `allow-new-or-existing` - `allow-get-store` - `allow-close-store` - `allow-set` @@ -90,32 +90,6 @@ Denies the close_store command without any pre-configured scope. -`store:allow-create-or-load` - - - - -Enables the create_or_load command without any pre-configured scope. - - - - - - - -`store:deny-create-or-load` - - - - -Denies the create_or_load command without any pre-configured scope. - - - - - - - `store:allow-create-store` @@ -350,6 +324,32 @@ Denies the load command without any pre-configured scope. +`store:allow-new-or-existing` + + + + +Enables the new_or_existing command without any pre-configured scope. + + + + + + + +`store:deny-new-or-existing` + + + + +Denies the new_or_existing command without any pre-configured scope. + + + + + + + `store:allow-reset` diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index 48b3a09082..6aa688ae52 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -12,7 +12,7 @@ All operations are enabled by default. """ permissions = [ "allow-create-store", - "allow-create-or-load", + "allow-new-or-existing", "allow-get-store", "allow-close-store", "allow-set", diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index 89eeb0c0de..01e465dbbd 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -314,16 +314,6 @@ "type": "string", "const": "deny-close-store" }, - { - "description": "Enables the create_or_load command without any pre-configured scope.", - "type": "string", - "const": "allow-create-or-load" - }, - { - "description": "Denies the create_or_load command without any pre-configured scope.", - "type": "string", - "const": "deny-create-or-load" - }, { "description": "Enables the create_store command without any pre-configured scope.", "type": "string", @@ -414,6 +404,16 @@ "type": "string", "const": "deny-load" }, + { + "description": "Enables the new_or_existing command without any pre-configured scope.", + "type": "string", + "const": "allow-new-or-existing" + }, + { + "description": "Denies the new_or_existing command without any pre-configured scope.", + "type": "string", + "const": "deny-new-or-existing" + }, { "description": "Enables the reset command without any pre-configured scope.", "type": "string", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 9251fa0044..ae71b3c9dd 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -117,7 +117,7 @@ async fn create_store( } #[tauri::command] -async fn create_or_load( +async fn new_or_existing( app: AppHandle, store_state: State<'_, StoreState>, path: PathBuf, @@ -133,7 +133,7 @@ async fn create_or_load( serialize_fn_name, deserialize_fn_name, )?; - let (_, rid) = builder.create_or_load_inner()?; + let (_, rid) = builder.new_or_existing_inner()?; Ok(rid) } @@ -291,7 +291,7 @@ pub trait StoreExt { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = app.store_builder("users.json").auto_save(Duration::from_secs(1)).create_or_load()?; + /// let store = app.store_builder("users.json").auto_save(Duration::from_secs(1)).new_or_existing()?; /// Ok(()) /// }); /// ``` @@ -328,7 +328,7 @@ pub trait StoreExt { impl> StoreExt for T { fn store(&self, path: impl AsRef) -> Result>> { let path = path.as_ref(); - StoreBuilder::new(self.app_handle(), path).create_or_load() + StoreBuilder::new(self.app_handle(), path).new_or_existing() } fn create_store(&self, path: impl AsRef) -> Result>> { @@ -451,7 +451,7 @@ impl Builder { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin").create_or_load()?; + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin").new_or_existing()?; /// Ok(()) /// }); /// ``` @@ -459,7 +459,7 @@ impl Builder { plugin::Builder::new("store") .invoke_handler(tauri::generate_handler![ create_store, - create_or_load, + new_or_existing, get_store, close_store, set, diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 8091f13274..42240cec97 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -79,7 +79,7 @@ impl StoreBuilder { /// /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin") /// .defaults(defaults) - /// .create_or_load()?; + /// .new_or_existing()?; /// Ok(()) /// }); /// ``` @@ -97,7 +97,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin") /// .default("foo".to_string(), "bar") - /// .create_or_load()?; + /// .new_or_existing()?; /// Ok(()) /// }); /// ``` @@ -119,7 +119,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .serialize(|cache| serde_json::to_vec(&cache).map_err(Into::into)) - /// .create_or_load()?; + /// .new_or_existing()?; /// Ok(()) /// }); /// ``` @@ -137,7 +137,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .deserialize(|bytes| serde_json::from_slice(&bytes).map_err(Into::into)) - /// .create_or_load()?; + /// .new_or_existing()?; /// Ok(()) /// }); /// ``` @@ -155,7 +155,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .auto_save(std::time::Duration::from_millis(100)) - /// .create_or_load()?; + /// .new_or_existing()?; /// Ok(()) /// }); /// ``` @@ -217,7 +217,7 @@ impl StoreBuilder { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").create_or_load()?; + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").new_or_existing()?; /// Ok(()) /// }); /// ``` @@ -241,16 +241,16 @@ impl StoreBuilder { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").create_or_load(); + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").new_or_existing(); /// Ok(()) /// }); /// ``` - pub fn create_or_load(self) -> crate::Result>> { - let (store, _) = self.create_or_load_inner()?; + pub fn new_or_existing(self) -> crate::Result>> { + let (store, _) = self.new_or_existing_inner()?; Ok(store) } - pub(crate) fn create_or_load_inner(self) -> crate::Result<(Arc>, ResourceId)> { + pub(crate) fn new_or_existing_inner(self) -> crate::Result<(Arc>, ResourceId)> { { let state = self.app.state::(); let stores = state.stores.lock().unwrap(); From a7af6612f2983f48a86709bd0cd0d82716947fd8 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 15 Oct 2024 10:28:58 -0300 Subject: [PATCH 53/70] keep store lock throughout whole new_or_existing_inner --- plugins/store/src/lib.rs | 4 ++-- plugins/store/src/store.rs | 27 ++++++++++++++------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index ae71b3c9dd..e44e2d3e08 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -42,7 +42,7 @@ struct ChangePayload<'a> { #[derive(Debug)] pub struct StoreState { - stores: Mutex>, + stores: Arc>>, serialize_fns: HashMap, deserialize_fns: HashMap, default_serialize: SerializeFn, @@ -477,7 +477,7 @@ impl Builder { ]) .setup(move |app_handle, _api| { app_handle.manage(StoreState { - stores: Mutex::new(HashMap::new()), + stores: Arc::new(Mutex::new(HashMap::new())), serialize_fns: self.serialize_fns, deserialize_fns: self.deserialize_fns, default_serialize: self.default_serialize, diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 42240cec97..409a13e4b0 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -8,7 +8,7 @@ use std::{ collections::HashMap, fs, path::{Path, PathBuf}, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, MutexGuard}, time::Duration, }; use tauri::{path::BaseDirectory, AppHandle, Emitter, Manager, Resource, ResourceId, Runtime}; @@ -170,10 +170,11 @@ impl StoreBuilder { self } - pub(crate) fn build_inner(mut self, load: bool) -> crate::Result<(Arc>, ResourceId)> { - let state = self.app.state::(); - let mut stores = state.stores.lock().unwrap(); - + pub(crate) fn build_inner( + mut self, + load: bool, + mut stores: MutexGuard<'_, HashMap>, + ) -> crate::Result<(Arc>, ResourceId)> { if stores.contains_key(&self.path) { return Err(crate::Error::AlreadyExists(self.path)); } @@ -227,7 +228,8 @@ impl StoreBuilder { } pub(crate) fn create_inner(self) -> crate::Result<(Arc>, ResourceId)> { - self.build_inner(false) + let stores = self.app.state::().stores.clone(); + self.build_inner(false, stores.lock().unwrap()) } /// Get the existing store with the same path or creates a new [`Store`]. @@ -251,14 +253,13 @@ impl StoreBuilder { } pub(crate) fn new_or_existing_inner(self) -> crate::Result<(Arc>, ResourceId)> { - { - let state = self.app.state::(); - let stores = state.stores.lock().unwrap(); - if let Some(rid) = stores.get(&self.path) { - return Ok((self.app.resources_table().get(*rid).unwrap(), *rid)); - } + let stores = self.app.state::().stores.clone(); + let stores_ = stores.lock().unwrap(); + if let Some(rid) = stores_.get(&self.path) { + return Ok((self.app.resources_table().get(*rid).unwrap(), *rid)); } - self.build_inner(true) + + self.build_inner(true, stores_) } } From fbc7149d60137afed54a918a99895d8a2442ea22 Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 15 Oct 2024 22:47:57 +0800 Subject: [PATCH 54/70] Remove load --- plugins/store/src/store.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 409a13e4b0..4d2db22b94 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -172,7 +172,6 @@ impl StoreBuilder { pub(crate) fn build_inner( mut self, - load: bool, mut stores: MutexGuard<'_, HashMap>, ) -> crate::Result<(Arc>, ResourceId)> { if stores.contains_key(&self.path) { @@ -229,7 +228,7 @@ impl StoreBuilder { pub(crate) fn create_inner(self) -> crate::Result<(Arc>, ResourceId)> { let stores = self.app.state::().stores.clone(); - self.build_inner(false, stores.lock().unwrap()) + self.build_inner(stores.lock().unwrap()) } /// Get the existing store with the same path or creates a new [`Store`]. @@ -259,7 +258,7 @@ impl StoreBuilder { return Ok((self.app.resources_table().get(*rid).unwrap(), *rid)); } - self.build_inner(true, stores_) + self.build_inner(stores_) } } From 5f2de5e4ea5edd1632c12058c3542287cd80066a Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 15 Oct 2024 22:48:35 +0800 Subject: [PATCH 55/70] Missed if load --- plugins/store/src/store.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 4d2db22b94..1e4a944f5a 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -185,9 +185,7 @@ impl StoreBuilder { self.serialize_fn, self.deserialize_fn, ); - if load { - let _ = store_inner.load(); - } + let _ = store_inner.load(); let store = Store { auto_save: self.auto_save, From 985059a1f0cf0dd1c49f5e5fde4f4ab1c4b341f0 Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 15 Oct 2024 22:52:11 +0800 Subject: [PATCH 56/70] Don't make StoreState public --- plugins/store/README.md | 2 +- plugins/store/src/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/store/README.md b/plugins/store/README.md index 5b60fcd2df..f6773776d9 100644 --- a/plugins/store/README.md +++ b/plugins/store/README.md @@ -123,7 +123,7 @@ fn main() { .plugin(tauri_plugin_store::Builder::default().build()) .setup(|app| { // This loads the store from disk - let store = app.store("app_data.json"); + 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. diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index e44e2d3e08..b1f06d3de3 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -41,7 +41,7 @@ struct ChangePayload<'a> { } #[derive(Debug)] -pub struct StoreState { +struct StoreState { stores: Arc>>, serialize_fns: HashMap, deserialize_fns: HashMap, @@ -327,7 +327,6 @@ pub trait StoreExt { impl> StoreExt for T { fn store(&self, path: impl AsRef) -> Result>> { - let path = path.as_ref(); StoreBuilder::new(self.app_handle(), path).new_or_existing() } From 44016a480f040655b7236efd6107d440a45fe0bd Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 15 Oct 2024 23:06:43 +0800 Subject: [PATCH 57/70] Remove R: Runtime from Builder --- plugins/store/src/lib.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index b1f06d3de3..ccf2e81d21 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -16,7 +16,6 @@ use serde::{Deserialize, Serialize}; pub use serde_json::Value as JsonValue; use std::{ collections::HashMap, - marker::PhantomData, path::{Path, PathBuf}, sync::{Arc, Mutex}, time::Duration, @@ -359,18 +358,16 @@ fn default_deserialize( serde_json::from_slice(bytes).map_err(Into::into) } -pub struct Builder { - phantom_data: PhantomData, +pub struct Builder { serialize_fns: HashMap, deserialize_fns: HashMap, default_serialize: SerializeFn, default_deserialize: DeserializeFn, } -impl Default for Builder { +impl Default for Builder { fn default() -> Self { Self { - phantom_data: Default::default(), serialize_fns: Default::default(), deserialize_fns: Default::default(), default_serialize, @@ -379,7 +376,7 @@ impl Default for Builder { } } -impl Builder { +impl Builder { pub fn new() -> Self { Self::default() } @@ -454,7 +451,7 @@ impl Builder { /// Ok(()) /// }); /// ``` - pub fn build(self) -> TauriPlugin { + pub fn build(self) -> TauriPlugin { plugin::Builder::new("store") .invoke_handler(tauri::generate_handler![ create_store, From 983bd8221252a8c96f3878e9c8b307043edffef6 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Oct 2024 15:24:58 -0300 Subject: [PATCH 58/70] rename newOrExisting to load, load to reload --- plugins/store/api-iife.js | 2 +- plugins/store/build.rs | 4 +-- plugins/store/guest-js/index.ts | 25 +++++++++---------- .../commands/new_or_existing.toml | 13 ---------- .../autogenerated/commands/reload.toml | 13 ++++++++++ .../permissions/autogenerated/reference.md | 10 ++++---- plugins/store/permissions/default.toml | 2 +- plugins/store/permissions/schemas/schema.json | 8 +++--- plugins/store/src/lib.rs | 18 ++++++------- plugins/store/src/store.rs | 22 ++++++++-------- 10 files changed, 58 insertions(+), 59 deletions(-) delete mode 100644 plugins/store/permissions/autogenerated/commands/new_or_existing.toml create mode 100644 plugins/store/permissions/autogenerated/commands/reload.toml diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index 0179964057..cb4f16e330 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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.newOrExisting(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 c extends i{constructor(t){super(t)}static async create(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new c(a)}static async newOrExisting(t,e){const a=await s("plugin:store|new_or_existing",{path:t,...e});return new c(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new c(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 load(){await s("plugin:store|load",{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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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 load(){await(await this.store).load()}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=c,t.create=async function(t,e){return await c.create(t,e)},t.getStore=async function(t){return await c.get(t)},t.newOrExisting=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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.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 c extends i{constructor(t){super(t)}static async create(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new c(a)}static async load(t,e){const a=await s("plugin:store|load",{path:t,...e});return new c(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new c(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 load(){await s("plugin:store|load",{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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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 load(){await(await this.store).load()}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=c,t.create=async function(t,e){return await c.create(t,e)},t.getStore=async function(t){return await c.get(t)},t.load=o,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} diff --git a/plugins/store/build.rs b/plugins/store/build.rs index 9024df232a..52175aa041 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -4,7 +4,7 @@ const COMMANDS: &[&str] = &[ "create_store", - "new_or_existing", + "load", "get_store", "close_store", "set", @@ -17,7 +17,7 @@ const COMMANDS: &[&str] = &[ "values", "entries", "length", - "load", + "reload", "save", ]; diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 6a3227da28..dfa34ae98e 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -55,11 +55,11 @@ export async function create( * @param path Path to save the store in `app_data_dir` * @param options Store configuration options */ -export async function newOrExisting( +export async function load( path: string, options?: StoreOptions ): Promise { - return await Store.newOrExisting(path, options) + return await Store.load(path, options) } /** @@ -77,7 +77,7 @@ export class LazyStore implements IStore { private get store(): Promise { if (!this._store) { - this._store = newOrExisting(this.path, this.options) + this._store = load(this.path, this.options) } return this._store } @@ -180,6 +180,8 @@ export class Store extends Resource implements IStore { * * If the store already exists, its data will be overwritten. * + * To load the store if it already exists you must use {@link load} instead. + * * If the store is already loaded you must use {@link getStore} instead. * * @example @@ -211,18 +213,15 @@ export class Store extends Resource implements IStore { * import { Store } from '@tauri-apps/api/store'; * let store = await Store.get('store.json'); * if (!store) { - * store = await Store.newOrExisting('store.json'); + * store = await Store.load('store.json'); * } * ``` * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options */ - static async newOrExisting( - path: string, - options?: StoreOptions - ): Promise { - const rid = await invoke('plugin:store|new_or_existing', { + static async load(path: string, options?: StoreOptions): Promise { + const rid = await invoke('plugin:store|load', { path, ...options }) @@ -233,14 +232,14 @@ export class Store extends Resource implements IStore { * Gets an already loaded store. * * If the store is not loaded, returns `null`. In this case, - * you must either {@link Store.create | create} it or {@link Store.newOrExisting newOrExisting} it. + * you must either {@link Store.create | create} it or {@link Store.load load} it. * * @example * ```typescript * import { Store } from '@tauri-apps/api/store'; * let store = await Store.get('store.json'); * if (!store) { - * store = await Store.newOrExisting('store.json'); + * store = await Store.load('store.json'); * } * ``` * @@ -306,8 +305,8 @@ export class Store extends Resource implements IStore { return await invoke('plugin:store|length', { rid: this.rid }) } - async load(): Promise { - await invoke('plugin:store|load', { rid: this.rid }) + async reload(): Promise { + await invoke('plugin:store|reload', { rid: this.rid }) } async save(): Promise { diff --git a/plugins/store/permissions/autogenerated/commands/new_or_existing.toml b/plugins/store/permissions/autogenerated/commands/new_or_existing.toml deleted file mode 100644 index c430ecfb75..0000000000 --- a/plugins/store/permissions/autogenerated/commands/new_or_existing.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-new-or-existing" -description = "Enables the new_or_existing command without any pre-configured scope." -commands.allow = ["new_or_existing"] - -[[permission]] -identifier = "deny-new-or-existing" -description = "Denies the new_or_existing command without any pre-configured scope." -commands.deny = ["new_or_existing"] diff --git a/plugins/store/permissions/autogenerated/commands/reload.toml b/plugins/store/permissions/autogenerated/commands/reload.toml new file mode 100644 index 0000000000..92e252531c --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/reload.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-reload" +description = "Enables the reload command without any pre-configured scope." +commands.allow = ["reload"] + +[[permission]] +identifier = "deny-reload" +description = "Denies the reload command without any pre-configured scope." +commands.deny = ["reload"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 804a77a023..3b36c3ed9f 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -10,7 +10,7 @@ All operations are enabled by default. - `allow-create-store` -- `allow-new-or-existing` +- `allow-load` - `allow-get-store` - `allow-close-store` - `allow-set` @@ -324,12 +324,12 @@ Denies the load command without any pre-configured scope. -`store:allow-new-or-existing` +`store:allow-reload` -Enables the new_or_existing command without any pre-configured scope. +Enables the reload command without any pre-configured scope. @@ -337,12 +337,12 @@ Enables the new_or_existing command without any pre-configured scope. -`store:deny-new-or-existing` +`store:deny-reload` -Denies the new_or_existing command without any pre-configured scope. +Denies the reload command without any pre-configured scope. diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index 6aa688ae52..09161d589a 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -12,7 +12,7 @@ All operations are enabled by default. """ permissions = [ "allow-create-store", - "allow-new-or-existing", + "allow-load", "allow-get-store", "allow-close-store", "allow-set", diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index 01e465dbbd..2400aac563 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -405,14 +405,14 @@ "const": "deny-load" }, { - "description": "Enables the new_or_existing command without any pre-configured scope.", + "description": "Enables the reload command without any pre-configured scope.", "type": "string", - "const": "allow-new-or-existing" + "const": "allow-reload" }, { - "description": "Denies the new_or_existing command without any pre-configured scope.", + "description": "Denies the reload command without any pre-configured scope.", "type": "string", - "const": "deny-new-or-existing" + "const": "deny-reload" }, { "description": "Enables the reset command without any pre-configured scope.", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index ccf2e81d21..1cb95cadb7 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -116,7 +116,7 @@ async fn create_store( } #[tauri::command] -async fn new_or_existing( +async fn load( app: AppHandle, store_state: State<'_, StoreState>, path: PathBuf, @@ -132,7 +132,7 @@ async fn new_or_existing( serialize_fn_name, deserialize_fn_name, )?; - let (_, rid) = builder.new_or_existing_inner()?; + let (_, rid) = builder.load_inner()?; Ok(rid) } @@ -229,9 +229,9 @@ async fn length(app: AppHandle, rid: ResourceId) -> Result } #[tauri::command] -async fn load(app: AppHandle, rid: ResourceId) -> Result<()> { +async fn reload(app: AppHandle, rid: ResourceId) -> Result<()> { let store = app.resources_table().get::>(rid)?; - store.load() + store.reload() } #[tauri::command] @@ -290,7 +290,7 @@ pub trait StoreExt { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = app.store_builder("users.json").auto_save(Duration::from_secs(1)).new_or_existing()?; + /// let store = app.store_builder("users.json").auto_save(Duration::from_secs(1)).load()?; /// Ok(()) /// }); /// ``` @@ -326,7 +326,7 @@ pub trait StoreExt { impl> StoreExt for T { fn store(&self, path: impl AsRef) -> Result>> { - StoreBuilder::new(self.app_handle(), path).new_or_existing() + StoreBuilder::new(self.app_handle(), path).load() } fn create_store(&self, path: impl AsRef) -> Result>> { @@ -447,7 +447,7 @@ impl Builder { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin").new_or_existing()?; + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin").load()?; /// Ok(()) /// }); /// ``` @@ -455,7 +455,7 @@ impl Builder { plugin::Builder::new("store") .invoke_handler(tauri::generate_handler![ create_store, - new_or_existing, + load, get_store, close_store, set, @@ -468,7 +468,7 @@ impl Builder { values, length, entries, - load, + reload, save, ]) .setup(move |app_handle, _api| { diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 1e4a944f5a..2750bc01e3 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -79,7 +79,7 @@ impl StoreBuilder { /// /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin") /// .defaults(defaults) - /// .new_or_existing()?; + /// .load()?; /// Ok(()) /// }); /// ``` @@ -97,7 +97,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin") /// .default("foo".to_string(), "bar") - /// .new_or_existing()?; + /// .load()?; /// Ok(()) /// }); /// ``` @@ -119,7 +119,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .serialize(|cache| serde_json::to_vec(&cache).map_err(Into::into)) - /// .new_or_existing()?; + /// .load()?; /// Ok(()) /// }); /// ``` @@ -137,7 +137,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .deserialize(|bytes| serde_json::from_slice(&bytes).map_err(Into::into)) - /// .new_or_existing()?; + /// .load()?; /// Ok(()) /// }); /// ``` @@ -155,7 +155,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .auto_save(std::time::Duration::from_millis(100)) - /// .new_or_existing()?; + /// .load()?; /// Ok(()) /// }); /// ``` @@ -215,7 +215,7 @@ impl StoreBuilder { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").new_or_existing()?; + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").load()?; /// Ok(()) /// }); /// ``` @@ -240,16 +240,16 @@ impl StoreBuilder { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").new_or_existing(); + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").load(); /// Ok(()) /// }); /// ``` - pub fn new_or_existing(self) -> crate::Result>> { - let (store, _) = self.new_or_existing_inner()?; + pub fn load(self) -> crate::Result>> { + let (store, _) = self.load_inner()?; Ok(store) } - pub(crate) fn new_or_existing_inner(self) -> crate::Result<(Arc>, ResourceId)> { + pub(crate) fn load_inner(self) -> crate::Result<(Arc>, ResourceId)> { let stores = self.app.state::().stores.clone(); let stores_ = stores.lock().unwrap(); if let Some(rid) = stores_.get(&self.path) { @@ -519,7 +519,7 @@ impl Store { } /// Update the store from the on-disk state - pub fn load(&self) -> crate::Result<()> { + pub fn reload(&self) -> crate::Result<()> { self.store.lock().unwrap().load() } From d7bff640985036fedcc7aebc24171b0e6e5c49bf Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Oct 2024 15:30:47 -0300 Subject: [PATCH 59/70] update docs --- plugins/store/guest-js/index.ts | 57 +++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index dfa34ae98e..07bfa1626b 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -33,10 +33,24 @@ export type StoreOptions = { } /** + * Create a new store. + * + * If the store already exists, its data will be overwritten. + * + * To load the store if it already exists you must use {@link load} instead. + * + * If the store is already loaded you must use {@link getStore} instead. + * + * @example + * ```typescript + * import { Store } from '@tauri-apps/api/store'; + * const store = await Store.create('store.json'); + * ``` + * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options * - * @throws If a store at that path already exists + * @throws If a store at that path is already loaded */ export async function create( path: string, @@ -48,12 +62,21 @@ export async function create( /** * Create a new Store or load the existing store with the path. * - * If the store at the given path is already loaded, - * its instance is returned regardless of the options object. - * If the settings to not match an error is returned. + * If the store is already loaded you must use {@link getStore} instead. + * + * @example + * ```typescript + * import { Store } from '@tauri-apps/api/store'; + * let store = await Store.get('store.json'); + * if (!store) { + * store = await Store.load('store.json'); + * } + * ``` * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options + * + * @throws If a store at that path is already loaded */ export async function load( path: string, @@ -63,7 +86,24 @@ export async function load( } /** - * @param path Path of the store + * Gets an already loaded store. + * + * If the store is not loaded, returns `null`. In this case, + * you must either {@link Store.create | create} it or {@link Store.load load} it. + * + * This function is more useful when you already know the store is loaded + * and just need to access its instance. Prefer {@link Store.load} otherwise. + * + * @example + * ```typescript + * import { getStore } from '@tauri-apps/api/store'; + * let store = await getStore('store.json'); + * if (!store) { + * store = await Store.load('store.json'); + * } + * ``` + * + * @param path Path of the store. */ export async function getStore(path: string): Promise { return await Store.get(path) @@ -193,7 +233,7 @@ export class Store extends Resource implements IStore { * @param path Path to save the store in `app_data_dir` * @param options Store configuration options * - * @throws If a store at that path already exists + * @throws If a store at that path is already loaded */ static async create(path: string, options?: StoreOptions): Promise { const rid = await invoke('plugin:store|create_store', { @@ -219,6 +259,8 @@ export class Store extends Resource implements IStore { * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options + * + * @throws If a store at that path is already loaded */ static async load(path: string, options?: StoreOptions): Promise { const rid = await invoke('plugin:store|load', { @@ -234,6 +276,9 @@ export class Store extends Resource implements IStore { * If the store is not loaded, returns `null`. In this case, * you must either {@link Store.create | create} it or {@link Store.load load} it. * + * This function is more useful when you already know the store is loaded + * and just need to access its instance. Prefer {@link Store.load} otherwise. + * * @example * ```typescript * import { Store } from '@tauri-apps/api/store'; From ac912703d0f8e4b84c4d16f117cd58a291687759 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Oct 2024 15:31:28 -0300 Subject: [PATCH 60/70] rename missing reload fn --- plugins/store/api-iife.js | 2 +- plugins/store/guest-js/index.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index cb4f16e330..cc64d0b782 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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.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 c extends i{constructor(t){super(t)}static async create(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new c(a)}static async load(t,e){const a=await s("plugin:store|load",{path:t,...e});return new c(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new c(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 load(){await s("plugin:store|load",{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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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 load(){await(await this.store).load()}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=c,t.create=async function(t,e){return await c.create(t,e)},t.getStore=async function(t){return await c.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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.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 c extends i{constructor(t){super(t)}static async create(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new c(a)}static async load(t,e){const a=await s("plugin:store|load",{path:t,...e});return new c(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new c(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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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=c,t.create=async function(t,e){return await c.create(t,e)},t.getStore=async function(t){return await c.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 07bfa1626b..28f64f1216 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -179,8 +179,8 @@ export class LazyStore implements IStore { return (await this.store).length() } - async load(): Promise { - await (await this.store).load() + async reload(): Promise { + await (await this.store).reload() } async save(): Promise { @@ -474,7 +474,7 @@ interface IStore { * Note: This method does not emit change events. * @returns */ - load(): Promise + reload(): Promise /** * Saves the store to disk at the store's `path`. From 150e030c1265ca672e03f36fe269d301ee6005c5 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Oct 2024 15:38:24 -0300 Subject: [PATCH 61/70] rename builder fn to build() --- plugins/store/src/lib.rs | 32 +++----------- plugins/store/src/store.rs | 87 +++++++++++++------------------------- 2 files changed, 35 insertions(+), 84 deletions(-) diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 1cb95cadb7..cabd6b6a57 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -111,7 +111,7 @@ async fn create_store( serialize_fn_name, deserialize_fn_name, )?; - let (_, rid) = builder.create_inner()?; + let (_, rid) = builder.create_new().build_inner()?; Ok(rid) } @@ -132,7 +132,7 @@ async fn load( serialize_fn_name, deserialize_fn_name, )?; - let (_, rid) = builder.load_inner()?; + let (_, rid) = builder.build_inner()?; Ok(rid) } @@ -258,24 +258,6 @@ pub trait StoreExt { /// }); /// ``` fn store(&self, path: impl AsRef) -> Result>>; - /// Create a store with default settings. - /// - /// If the store is already loaded you must check with [`Self::get_store`] or prefer [`Self::store`] - /// as it will return `Err(Error::AlreadyExists)`. - /// - /// # Examples - /// - /// ``` - /// use tauri_plugin_store::StoreExt; - /// - /// tauri::Builder::default() - /// .plugin(tauri_plugin_store::Builder::default().build()) - /// .setup(|app| { - /// let store = app.create_store("my-store")?; - /// Ok(()) - /// }); - /// ``` - fn create_store(&self, path: impl AsRef) -> Result>>; /// Get a store builder. /// /// The builder can be used to configure the store. @@ -290,7 +272,7 @@ pub trait StoreExt { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = app.store_builder("users.json").auto_save(Duration::from_secs(1)).load()?; + /// let store = app.store_builder("users.json").auto_save(Duration::from_secs(1)).build()?; /// Ok(()) /// }); /// ``` @@ -326,11 +308,7 @@ pub trait StoreExt { impl> StoreExt for T { fn store(&self, path: impl AsRef) -> Result>> { - StoreBuilder::new(self.app_handle(), path).load() - } - - fn create_store(&self, path: impl AsRef) -> Result>> { - StoreBuilder::new(self.app_handle(), path).create() + StoreBuilder::new(self.app_handle(), path).build() } fn store_builder(&self, path: impl AsRef) -> StoreBuilder { @@ -447,7 +425,7 @@ impl Builder { /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin").load()?; + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin").build()?; /// Ok(()) /// }); /// ``` diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 2750bc01e3..9111aa2daf 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -8,7 +8,7 @@ use std::{ collections::HashMap, fs, path::{Path, PathBuf}, - sync::{Arc, Mutex, MutexGuard}, + sync::{Arc, Mutex}, time::Duration, }; use tauri::{path::BaseDirectory, AppHandle, Emitter, Manager, Resource, ResourceId, Runtime}; @@ -38,6 +38,7 @@ pub struct StoreBuilder { serialize_fn: SerializeFn, deserialize_fn: DeserializeFn, auto_save: Option, + create_new: bool, } impl StoreBuilder { @@ -64,6 +65,7 @@ impl StoreBuilder { serialize_fn, deserialize_fn, auto_save: Some(Duration::from_millis(100)), + create_new: false, } } @@ -79,7 +81,7 @@ impl StoreBuilder { /// /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin") /// .defaults(defaults) - /// .load()?; + /// .build()?; /// Ok(()) /// }); /// ``` @@ -97,7 +99,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin") /// .default("foo".to_string(), "bar") - /// .load()?; + /// .build()?; /// Ok(()) /// }); /// ``` @@ -119,7 +121,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .serialize(|cache| serde_json::to_vec(&cache).map_err(Into::into)) - /// .load()?; + /// .build()?; /// Ok(()) /// }); /// ``` @@ -137,7 +139,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .deserialize(|bytes| serde_json::from_slice(&bytes).map_err(Into::into)) - /// .load()?; + /// .build()?; /// Ok(()) /// }); /// ``` @@ -155,7 +157,7 @@ impl StoreBuilder { /// .setup(|app| { /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json") /// .auto_save(std::time::Duration::from_millis(100)) - /// .load()?; + /// .build()?; /// Ok(()) /// }); /// ``` @@ -164,16 +166,25 @@ impl StoreBuilder { self } - /// Disable auto save on modified with a debounce duration + /// Disable auto save on modified with a debounce duration. pub fn disable_auto_save(mut self) -> Self { self.auto_save = None; self } - pub(crate) fn build_inner( - mut self, - mut stores: MutexGuard<'_, HashMap>, - ) -> crate::Result<(Arc>, ResourceId)> { + /// Force create a new store even if it already exists. + pub fn create_new(mut self) -> Self { + self.create_new = 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(); + if let Some(rid) = stores.get(&self.path) { + return Ok((self.app.resources_table().get(*rid).unwrap(), *rid)); + } + if stores.contains_key(&self.path) { return Err(crate::Error::AlreadyExists(self.path)); } @@ -185,7 +196,10 @@ impl StoreBuilder { self.serialize_fn, self.deserialize_fn, ); - let _ = store_inner.load(); + + if !self.create_new { + let _ = store_inner.load(); + } let store = Store { auto_save: self.auto_save, @@ -200,64 +214,23 @@ impl StoreBuilder { Ok((store, rid)) } - /// Builds the [`Store`], also see [`build_or_existing`](Self::build_or_existing). - /// - /// This loads the store from disk and put the store in the app's resource table, - /// to remove it from the resource table, call [`Store::close_store`] - /// - /// # Errors - /// - /// If a store with this path is already in the resource table, - /// will return a [`crate::Error::AlreadyExists`] - /// - /// # Examples - /// ``` - /// tauri::Builder::default() - /// .plugin(tauri_plugin_store::Builder::default().build()) - /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").load()?; - /// Ok(()) - /// }); - /// ``` - pub fn create(self) -> crate::Result>> { - let (store, _) = self.create_inner()?; - Ok(store) - } - - pub(crate) fn create_inner(self) -> crate::Result<(Arc>, ResourceId)> { - let stores = self.app.state::().stores.clone(); - self.build_inner(stores.lock().unwrap()) - } - - /// Get the existing store with the same path or creates a new [`Store`]. + /// Load the existing store with the same path or creates a new [`Store`]. /// /// If a store with the same path has already been loaded its instance is returned. /// - /// See [`create`](Self::create) if you want to force create a new store in the path. - /// /// # Examples /// ``` /// tauri::Builder::default() /// .plugin(tauri_plugin_store::Builder::default().build()) /// .setup(|app| { - /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").load(); + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").build(); /// Ok(()) /// }); /// ``` - pub fn load(self) -> crate::Result>> { - let (store, _) = self.load_inner()?; + pub fn build(self) -> crate::Result>> { + let (store, _) = self.build_inner()?; Ok(store) } - - pub(crate) fn load_inner(self) -> crate::Result<(Arc>, ResourceId)> { - let stores = self.app.state::().stores.clone(); - let stores_ = stores.lock().unwrap(); - if let Some(rid) = stores_.get(&self.path) { - return Ok((self.app.resources_table().get(*rid).unwrap(), *rid)); - } - - self.build_inner(stores_) - } } enum AutoSaveMessage { From 3288e4bd741d09a626f4cb913819384ea9d1e3a3 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 16 Oct 2024 15:51:24 -0300 Subject: [PATCH 62/70] fix default permission --- plugins/store/permissions/default.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index 09161d589a..04fdc29fb0 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -25,6 +25,6 @@ permissions = [ "allow-values", "allow-entries", "allow-length", - "allow-load", + "allow-reload", "allow-save", ] From 99cafa6d73f53a86577004b87d547828cfc778e0 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 17 Oct 2024 11:31:38 +0800 Subject: [PATCH 63/70] Fix description and create_new logic --- plugins/store/guest-js/index.ts | 8 ------- .../permissions/autogenerated/reference.md | 2 +- plugins/store/src/error.rs | 7 +++---- plugins/store/src/store.rs | 21 +++++++++++++------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 28f64f1216..e9c0a77c1e 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -49,8 +49,6 @@ export type StoreOptions = { * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options - * - * @throws If a store at that path is already loaded */ export async function create( path: string, @@ -75,8 +73,6 @@ export async function create( * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options - * - * @throws If a store at that path is already loaded */ export async function load( path: string, @@ -232,8 +228,6 @@ export class Store extends Resource implements IStore { * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options - * - * @throws If a store at that path is already loaded */ static async create(path: string, options?: StoreOptions): Promise { const rid = await invoke('plugin:store|create_store', { @@ -259,8 +253,6 @@ export class Store extends Resource implements IStore { * * @param path Path to save the store in `app_data_dir` * @param options Store configuration options - * - * @throws If a store at that path is already loaded */ static async load(path: string, options?: StoreOptions): Promise { const rid = await invoke('plugin:store|load', { diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 3b36c3ed9f..4260d3f882 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -23,7 +23,7 @@ All operations are enabled by default. - `allow-values` - `allow-entries` - `allow-length` -- `allow-load` +- `allow-reload` - `allow-save` ## Permission Table diff --git a/plugins/store/src/error.rs b/plugins/store/src/error.rs index 77526fbc6f..ef5ee59383 100644 --- a/plugins/store/src/error.rs +++ b/plugins/store/src/error.rs @@ -3,7 +3,6 @@ // SPDX-License-Identifier: MIT use serde::{Serialize, Serializer}; -use std::path::PathBuf; pub type Result = std::result::Result; @@ -21,9 +20,9 @@ pub enum Error { /// IO error. #[error(transparent)] Io(#[from] std::io::Error), - /// Store already exists - #[error("Store at \"{0}\" already exists")] - AlreadyExists(PathBuf), + // /// Store already exists + // #[error("Store at \"{0}\" already exists")] + // AlreadyExists(PathBuf), /// Serialize function not found #[error("Serialize Function \"{0}\" not found")] SerializeFunctionNotFound(String), diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 9111aa2daf..72af4a022e 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -172,7 +172,7 @@ impl StoreBuilder { self } - /// Force create a new store even if it already exists. + /// Force create a new store with default values even if it already exists. pub fn create_new(mut self) -> Self { self.create_new = true; self @@ -181,14 +181,23 @@ impl StoreBuilder { pub(crate) fn build_inner(mut self) -> crate::Result<(Arc>, ResourceId)> { let stores = self.app.state::().stores.clone(); let mut stores = stores.lock().unwrap(); - if let Some(rid) = stores.get(&self.path) { - return Ok((self.app.resources_table().get(*rid).unwrap(), *rid)); - } - if stores.contains_key(&self.path) { - return Err(crate::Error::AlreadyExists(self.path)); + self.path = resolve_store_path(&self.app, self.path)?; + + if self.create_new { + if let Some(rid) = stores.remove(&self.path) { + let _ = self.app.resources_table().take::>(rid); + } + } else { + if let Some(rid) = stores.get(&self.path) { + return Ok((self.app.resources_table().get(*rid).unwrap(), *rid)); + } } + // if stores.contains_key(&self.path) { + // return Err(crate::Error::AlreadyExists(self.path)); + // } + let mut store_inner = StoreInner::new( self.app.clone(), self.path.clone(), From bf5a19120f8429f12a7e8fe9f0f3eca363828a0d Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 17 Oct 2024 11:34:08 +0800 Subject: [PATCH 64/70] Clippy --- plugins/store/src/store.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 72af4a022e..1dc5e1d21d 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -188,10 +188,8 @@ impl StoreBuilder { if let Some(rid) = stores.remove(&self.path) { let _ = self.app.resources_table().take::>(rid); } - } else { - if let Some(rid) = stores.get(&self.path) { - return Ok((self.app.resources_table().get(*rid).unwrap(), *rid)); - } + } else if let Some(rid) = stores.get(&self.path) { + return Ok((self.app.resources_table().get(*rid).unwrap(), *rid)); } // if stores.contains_key(&self.path) { From 975129695ede477b6fe52cf715abf3bd672fe7a4 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 17 Oct 2024 11:58:40 +0800 Subject: [PATCH 65/70] Update docs --- plugins/store/README.md | 4 ++-- plugins/store/guest-js/index.ts | 15 +++------------ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/plugins/store/README.md b/plugins/store/README.md index f6773776d9..7ab63fd451 100644 --- a/plugins/store/README.md +++ b/plugins/store/README.md @@ -68,9 +68,9 @@ fn main() { Afterwards all the plugin's APIs are available through the JavaScript guest bindings: ```typescript -import { createStore } from '@tauri-apps/plugin-store' +import { Store } from '@tauri-apps/plugin-store' -const store = await createStore('settings.json') +const store = await Store.load('settings.json') await store.set('some-key', { value: 5 }) diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index e9c0a77c1e..0260f5e48c 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -65,10 +65,7 @@ export async function create( * @example * ```typescript * import { Store } from '@tauri-apps/api/store'; - * let store = await Store.get('store.json'); - * if (!store) { - * store = await Store.load('store.json'); - * } + * const store = await Store.load('store.json'); * ``` * * @param path Path to save the store in `app_data_dir` @@ -93,10 +90,7 @@ export async function load( * @example * ```typescript * import { getStore } from '@tauri-apps/api/store'; - * let store = await getStore('store.json'); - * if (!store) { - * store = await Store.load('store.json'); - * } + * const store = await getStore('store.json'); * ``` * * @param path Path of the store. @@ -245,10 +239,7 @@ export class Store extends Resource implements IStore { * @example * ```typescript * import { Store } from '@tauri-apps/api/store'; - * let store = await Store.get('store.json'); - * if (!store) { - * store = await Store.load('store.json'); - * } + * const store = await Store.load('store.json'); * ``` * * @param path Path to save the store in `app_data_dir` From 5181d2b241e1171329c30c8150a993da0a25afcb Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 17 Oct 2024 15:20:28 +0800 Subject: [PATCH 66/70] Update docs --- plugins/store/guest-js/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 0260f5e48c..772966bbcc 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -60,8 +60,6 @@ export async function create( /** * Create a new Store or load the existing store with the path. * - * If the store is already loaded you must use {@link getStore} instead. - * * @example * ```typescript * import { Store } from '@tauri-apps/api/store'; @@ -234,8 +232,6 @@ export class Store extends Resource implements IStore { /** * Create a new Store or load the existing store with the path. * - * If the store is already loaded you must use {@link getStore} instead. - * * @example * ```typescript * import { Store } from '@tauri-apps/api/store'; From 09cce55b4067777c7c9759b35b77b5f9641ca380 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 17 Oct 2024 07:24:17 -0300 Subject: [PATCH 67/70] remove create_store command --- plugins/store/api-iife.js | 2 +- plugins/store/build.rs | 1 - plugins/store/guest-js/index.ts | 61 ++----------------- .../autogenerated/commands/create_store.toml | 13 ---- .../permissions/autogenerated/reference.md | 27 -------- plugins/store/permissions/default.toml | 1 - plugins/store/permissions/schemas/schema.json | 10 --- plugins/store/src/lib.rs | 30 +++------ 8 files changed, 15 insertions(+), 130 deletions(-) delete mode 100644 plugins/store/permissions/autogenerated/commands/create_store.toml diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index cc64d0b782..d7af2bfe1e 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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.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 c extends i{constructor(t){super(t)}static async create(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new c(a)}static async load(t,e){const a=await s("plugin:store|load",{path:t,...e});return new c(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new c(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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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=c,t.create=async function(t,e){return await c.create(t,e)},t.getStore=async function(t){return await c.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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.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 c extends i{constructor(t){super(t)}static async load(t,e){const a=await s("plugin:store|load",{path:t,...e});return new c(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new c(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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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=c,t.getStore=async function(t){return await c.get(t)},t.load=o,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} diff --git a/plugins/store/build.rs b/plugins/store/build.rs index 52175aa041..747d977442 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -3,7 +3,6 @@ // SPDX-License-Identifier: MIT const COMMANDS: &[&str] = &[ - "create_store", "load", "get_store", "close_store", diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 772966bbcc..b8641db486 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -30,31 +30,10 @@ export type StoreOptions = { * Name of a deserialize function registered in the rust side plugin builder */ deserializeFnName?: string -} - -/** - * Create a new store. - * - * If the store already exists, its data will be overwritten. - * - * To load the store if it already exists you must use {@link load} instead. - * - * If the store is already loaded you must use {@link getStore} instead. - * - * @example - * ```typescript - * import { Store } from '@tauri-apps/api/store'; - * const store = await Store.create('store.json'); - * ``` - * - * @param path Path to save the store in `app_data_dir` - * @param options Store configuration options - */ -export async function create( - path: string, - options?: StoreOptions -): Promise { - return await Store.create(path, options) + /** + * Force create a new store with default values even if it already exists. + */ + createNew?: boolean } /** @@ -79,8 +58,7 @@ export async function load( /** * Gets an already loaded store. * - * If the store is not loaded, returns `null`. In this case, - * you must either {@link Store.create | create} it or {@link Store.load load} it. + * If the store is not loaded, returns `null`. In this case you must {@link Store.load load} it. * * This function is more useful when you already know the store is loaded * and just need to access its instance. Prefer {@link Store.load} otherwise. @@ -203,32 +181,6 @@ export class Store extends Resource implements IStore { super(rid) } - /** - * Create a new store. - * - * If the store already exists, its data will be overwritten. - * - * To load the store if it already exists you must use {@link load} instead. - * - * If the store is already loaded you must use {@link getStore} instead. - * - * @example - * ```typescript - * import { Store } from '@tauri-apps/api/store'; - * const store = await Store.create('store.json'); - * ``` - * - * @param path Path to save the store in `app_data_dir` - * @param options Store configuration options - */ - static async create(path: string, options?: StoreOptions): Promise { - const rid = await invoke('plugin:store|create_store', { - path, - ...options - }) - return new Store(rid) - } - /** * Create a new Store or load the existing store with the path. * @@ -252,8 +204,7 @@ export class Store extends Resource implements IStore { /** * Gets an already loaded store. * - * If the store is not loaded, returns `null`. In this case, - * you must either {@link Store.create | create} it or {@link Store.load load} it. + * If the store is not loaded, returns `null`. In this case you must {@link Store.load load} it. * * This function is more useful when you already know the store is loaded * and just need to access its instance. Prefer {@link Store.load} otherwise. diff --git a/plugins/store/permissions/autogenerated/commands/create_store.toml b/plugins/store/permissions/autogenerated/commands/create_store.toml deleted file mode 100644 index cde71c24f4..0000000000 --- a/plugins/store/permissions/autogenerated/commands/create_store.toml +++ /dev/null @@ -1,13 +0,0 @@ -# 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"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 4260d3f882..2bd12e84b3 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -9,7 +9,6 @@ All operations are enabled by default. -- `allow-create-store` - `allow-load` - `allow-get-store` - `allow-close-store` @@ -90,32 +89,6 @@ Denies the close_store command without any pre-configured scope. -`store:allow-create-store` - - - - -Enables the create_store command without any pre-configured scope. - - - - - - - -`store:deny-create-store` - - - - -Denies the create_store command without any pre-configured scope. - - - - - - - `store:allow-delete` diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index 04fdc29fb0..297686f36e 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -11,7 +11,6 @@ All operations are enabled by default. """ permissions = [ - "allow-create-store", "allow-load", "allow-get-store", "allow-close-store", diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index 2400aac563..b8962c1236 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -314,16 +314,6 @@ "type": "string", "const": "deny-close-store" }, - { - "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", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index cabd6b6a57..76e94d97d9 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -62,6 +62,7 @@ fn builder( auto_save: Option, serialize_fn_name: Option, deserialize_fn_name: Option, + create_new: bool, ) -> Result> { let mut builder = app.store_builder(path); if let Some(auto_save) = auto_save { @@ -91,28 +92,12 @@ fn builder( .ok_or_else(|| crate::Error::DeserializeFunctionNotFound(deserialize_fn_name))?; builder = builder.deserialize(*deserialize_fn); } - Ok(builder) -} -#[tauri::command] -async fn create_store( - app: AppHandle, - store_state: State<'_, StoreState>, - path: PathBuf, - auto_save: Option, - serialize_fn_name: Option, - deserialize_fn_name: Option, -) -> Result { - let builder = builder( - app, - store_state, - path, - auto_save, - serialize_fn_name, - deserialize_fn_name, - )?; - let (_, rid) = builder.create_new().build_inner()?; - Ok(rid) + if create_new { + builder = builder.create_new(); + } + + Ok(builder) } #[tauri::command] @@ -123,6 +108,7 @@ async fn load( auto_save: Option, serialize_fn_name: Option, deserialize_fn_name: Option, + create_new: Option, ) -> Result { let builder = builder( app, @@ -131,6 +117,7 @@ async fn load( auto_save, serialize_fn_name, deserialize_fn_name, + create_new.unwrap_or_default(), )?; let (_, rid) = builder.build_inner()?; Ok(rid) @@ -432,7 +419,6 @@ impl Builder { pub fn build(self) -> TauriPlugin { plugin::Builder::new("store") .invoke_handler(tauri::generate_handler![ - create_store, load, get_store, close_store, From 4a29fc8990fed7caec9896b0390a30130ed1813c Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 17 Oct 2024 07:26:02 -0300 Subject: [PATCH 68/70] remove close_store command since we extend from Resource --- plugins/store/api-iife.js | 2 +- plugins/store/build.rs | 1 - plugins/store/guest-js/index.ts | 5 ---- .../autogenerated/commands/close_store.toml | 13 --------- .../permissions/autogenerated/reference.md | 27 ------------------- plugins/store/permissions/default.toml | 1 - plugins/store/permissions/schemas/schema.json | 10 ------- plugins/store/src/lib.rs | 22 ++------------- 8 files changed, 3 insertions(+), 78 deletions(-) delete mode 100644 plugins/store/permissions/autogenerated/commands/close_store.toml diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index d7af2bfe1e..fc04ff00d0 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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.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 c extends i{constructor(t){super(t)}static async load(t,e){const a=await s("plugin:store|load",{path:t,...e});return new c(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new c(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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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=c,t.getStore=async function(t){return await c.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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){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__})} diff --git a/plugins/store/build.rs b/plugins/store/build.rs index 747d977442..2e88d59ac6 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -5,7 +5,6 @@ const COMMANDS: &[&str] = &[ "load", "get_store", - "close_store", "set", "get", "has", diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index b8641db486..1df89fd529 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -311,11 +311,6 @@ export class Store extends Resource implements IStore { } }) } - - async close(): Promise { - // The default close on `Resource` can only close resources in the webview's resource table - await invoke('plugin:store|close_store', { rid: this.rid }) - } } interface IStore { diff --git a/plugins/store/permissions/autogenerated/commands/close_store.toml b/plugins/store/permissions/autogenerated/commands/close_store.toml deleted file mode 100644 index 5a8e3a0cb0..0000000000 --- a/plugins/store/permissions/autogenerated/commands/close_store.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-close-store" -description = "Enables the close_store command without any pre-configured scope." -commands.allow = ["close_store"] - -[[permission]] -identifier = "deny-close-store" -description = "Denies the close_store command without any pre-configured scope." -commands.deny = ["close_store"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 2bd12e84b3..5640415da2 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -11,7 +11,6 @@ All operations are enabled by default. - `allow-load` - `allow-get-store` -- `allow-close-store` - `allow-set` - `allow-get` - `allow-has` @@ -63,32 +62,6 @@ Denies the clear command without any pre-configured scope. -`store:allow-close-store` - - - - -Enables the close_store command without any pre-configured scope. - - - - - - - -`store:deny-close-store` - - - - -Denies the close_store command without any pre-configured scope. - - - - - - - `store:allow-delete` diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index 297686f36e..3a3e4b3af2 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -13,7 +13,6 @@ All operations are enabled by default. permissions = [ "allow-load", "allow-get-store", - "allow-close-store", "allow-set", "allow-get", "allow-has", diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index b8962c1236..4237bc624c 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -304,16 +304,6 @@ "type": "string", "const": "deny-clear" }, - { - "description": "Enables the close_store command without any pre-configured scope.", - "type": "string", - "const": "allow-close-store" - }, - { - "description": "Denies the close_store command without any pre-configured scope.", - "type": "string", - "const": "deny-close-store" - }, { "description": "Enables the delete command without any pre-configured scope.", "type": "string", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 76e94d97d9..310e80ecda 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -133,11 +133,6 @@ async fn get_store( Ok(stores.get(&resolve_store_path(&app, path)?).copied()) } -#[tauri::command] -async fn close_store(app: AppHandle, rid: ResourceId) -> Result<()> { - Ok(app.resources_table().close(rid)?) -} - #[tauri::command] async fn set( app: AppHandle, @@ -419,21 +414,8 @@ impl Builder { pub fn build(self) -> TauriPlugin { plugin::Builder::new("store") .invoke_handler(tauri::generate_handler![ - load, - get_store, - close_store, - set, - get, - has, - delete, - clear, - reset, - keys, - values, - length, - entries, - reload, - save, + load, get_store, set, get, has, delete, clear, reset, keys, values, length, + entries, reload, save, ]) .setup(move |app_handle, _api| { app_handle.manage(StoreState { From 70a1830e7d0f60e9f9b45e91b30ef5eb278e4f7d Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 17 Oct 2024 07:38:18 -0300 Subject: [PATCH 69/70] Revert "remove close_store command since we extend from Resource" This reverts commit 4a29fc8990fed7caec9896b0390a30130ed1813c. --- plugins/store/api-iife.js | 2 +- plugins/store/build.rs | 1 + plugins/store/guest-js/index.ts | 5 ++++ .../autogenerated/commands/close_store.toml | 13 +++++++++ .../permissions/autogenerated/reference.md | 27 +++++++++++++++++++ plugins/store/permissions/default.toml | 1 + plugins/store/permissions/schemas/schema.json | 10 +++++++ plugins/store/src/lib.rs | 22 +++++++++++++-- 8 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 plugins/store/permissions/autogenerated/commands/close_store.toml diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index fc04ff00d0..d7af2bfe1e 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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.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 c extends i{constructor(t){super(t)}static async load(t,e){const a=await s("plugin:store|load",{path:t,...e});return new c(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new c(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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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=c,t.getStore=async function(t){return await c.get(t)},t.load=o,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} diff --git a/plugins/store/build.rs b/plugins/store/build.rs index 2e88d59ac6..747d977442 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -5,6 +5,7 @@ const COMMANDS: &[&str] = &[ "load", "get_store", + "close_store", "set", "get", "has", diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 1df89fd529..b8641db486 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -311,6 +311,11 @@ export class Store extends Resource implements IStore { } }) } + + async close(): Promise { + // The default close on `Resource` can only close resources in the webview's resource table + await invoke('plugin:store|close_store', { rid: this.rid }) + } } interface IStore { diff --git a/plugins/store/permissions/autogenerated/commands/close_store.toml b/plugins/store/permissions/autogenerated/commands/close_store.toml new file mode 100644 index 0000000000..5a8e3a0cb0 --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/close_store.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-close-store" +description = "Enables the close_store command without any pre-configured scope." +commands.allow = ["close_store"] + +[[permission]] +identifier = "deny-close-store" +description = "Denies the close_store command without any pre-configured scope." +commands.deny = ["close_store"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 5640415da2..2bd12e84b3 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -11,6 +11,7 @@ All operations are enabled by default. - `allow-load` - `allow-get-store` +- `allow-close-store` - `allow-set` - `allow-get` - `allow-has` @@ -62,6 +63,32 @@ Denies the clear command without any pre-configured scope. +`store:allow-close-store` + + + + +Enables the close_store command without any pre-configured scope. + + + + + + + +`store:deny-close-store` + + + + +Denies the close_store command without any pre-configured scope. + + + + + + + `store:allow-delete` diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index 3a3e4b3af2..297686f36e 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -13,6 +13,7 @@ All operations are enabled by default. permissions = [ "allow-load", "allow-get-store", + "allow-close-store", "allow-set", "allow-get", "allow-has", diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index 4237bc624c..b8962c1236 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -304,6 +304,16 @@ "type": "string", "const": "deny-clear" }, + { + "description": "Enables the close_store command without any pre-configured scope.", + "type": "string", + "const": "allow-close-store" + }, + { + "description": "Denies the close_store command without any pre-configured scope.", + "type": "string", + "const": "deny-close-store" + }, { "description": "Enables the delete command without any pre-configured scope.", "type": "string", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 310e80ecda..76e94d97d9 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -133,6 +133,11 @@ async fn get_store( Ok(stores.get(&resolve_store_path(&app, path)?).copied()) } +#[tauri::command] +async fn close_store(app: AppHandle, rid: ResourceId) -> Result<()> { + Ok(app.resources_table().close(rid)?) +} + #[tauri::command] async fn set( app: AppHandle, @@ -414,8 +419,21 @@ impl Builder { pub fn build(self) -> TauriPlugin { plugin::Builder::new("store") .invoke_handler(tauri::generate_handler![ - load, get_store, set, get, has, delete, clear, reset, keys, values, length, - entries, reload, save, + load, + get_store, + close_store, + set, + get, + has, + delete, + clear, + reset, + keys, + values, + length, + entries, + reload, + save, ]) .setup(move |app_handle, _api| { app_handle.manage(StoreState { From 8e71393946d4086395e4db1979777e4bec9cfcb7 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 17 Oct 2024 07:59:55 -0300 Subject: [PATCH 70/70] Reapply "remove close_store command since we extend from Resource" This reverts commit 70a1830e7d0f60e9f9b45e91b30ef5eb278e4f7d. --- plugins/store/api-iife.js | 2 +- plugins/store/build.rs | 1 - plugins/store/guest-js/index.ts | 5 ---- .../autogenerated/commands/close_store.toml | 13 --------- .../permissions/autogenerated/reference.md | 27 ------------------- plugins/store/permissions/default.toml | 1 - plugins/store/permissions/schemas/schema.json | 10 ------- plugins/store/src/lib.rs | 22 ++------------- 8 files changed, 3 insertions(+), 78 deletions(-) delete mode 100644 plugins/store/permissions/autogenerated/commands/close_store.toml diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index d7af2bfe1e..fc04ff00d0 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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.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 c extends i{constructor(t){super(t)}static async load(t,e){const a=await s("plugin:store|load",{path:t,...e});return new c(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new c(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)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}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=c,t.getStore=async function(t){return await c.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("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");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,r,s){if("function"==typeof e?t!==e||!s:!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){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__})} diff --git a/plugins/store/build.rs b/plugins/store/build.rs index 747d977442..2e88d59ac6 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -5,7 +5,6 @@ const COMMANDS: &[&str] = &[ "load", "get_store", - "close_store", "set", "get", "has", diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index b8641db486..1df89fd529 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -311,11 +311,6 @@ export class Store extends Resource implements IStore { } }) } - - async close(): Promise { - // The default close on `Resource` can only close resources in the webview's resource table - await invoke('plugin:store|close_store', { rid: this.rid }) - } } interface IStore { diff --git a/plugins/store/permissions/autogenerated/commands/close_store.toml b/plugins/store/permissions/autogenerated/commands/close_store.toml deleted file mode 100644 index 5a8e3a0cb0..0000000000 --- a/plugins/store/permissions/autogenerated/commands/close_store.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-close-store" -description = "Enables the close_store command without any pre-configured scope." -commands.allow = ["close_store"] - -[[permission]] -identifier = "deny-close-store" -description = "Denies the close_store command without any pre-configured scope." -commands.deny = ["close_store"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index 2bd12e84b3..5640415da2 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -11,7 +11,6 @@ All operations are enabled by default. - `allow-load` - `allow-get-store` -- `allow-close-store` - `allow-set` - `allow-get` - `allow-has` @@ -63,32 +62,6 @@ Denies the clear command without any pre-configured scope. -`store:allow-close-store` - - - - -Enables the close_store command without any pre-configured scope. - - - - - - - -`store:deny-close-store` - - - - -Denies the close_store command without any pre-configured scope. - - - - - - - `store:allow-delete` diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index 297686f36e..3a3e4b3af2 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -13,7 +13,6 @@ All operations are enabled by default. permissions = [ "allow-load", "allow-get-store", - "allow-close-store", "allow-set", "allow-get", "allow-has", diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index b8962c1236..4237bc624c 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -304,16 +304,6 @@ "type": "string", "const": "deny-clear" }, - { - "description": "Enables the close_store command without any pre-configured scope.", - "type": "string", - "const": "allow-close-store" - }, - { - "description": "Denies the close_store command without any pre-configured scope.", - "type": "string", - "const": "deny-close-store" - }, { "description": "Enables the delete command without any pre-configured scope.", "type": "string", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 76e94d97d9..310e80ecda 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -133,11 +133,6 @@ async fn get_store( Ok(stores.get(&resolve_store_path(&app, path)?).copied()) } -#[tauri::command] -async fn close_store(app: AppHandle, rid: ResourceId) -> Result<()> { - Ok(app.resources_table().close(rid)?) -} - #[tauri::command] async fn set( app: AppHandle, @@ -419,21 +414,8 @@ impl Builder { pub fn build(self) -> TauriPlugin { plugin::Builder::new("store") .invoke_handler(tauri::generate_handler![ - load, - get_store, - close_store, - set, - get, - has, - delete, - clear, - reset, - keys, - values, - length, - entries, - reload, - save, + load, get_store, set, get, has, delete, clear, reset, keys, values, length, + entries, reload, save, ]) .setup(move |app_handle, _api| { app_handle.manage(StoreState {