From 0558a1f6c869ae0afdb0181dfa1ea31be8cf4893 Mon Sep 17 00:00:00 2001 From: FabianLars Date: Wed, 3 May 2023 07:03:53 +0000 Subject: [PATCH] Rename dev branch to v1 and next branch to v2 Committed via a GitHub action: https://github.com/tauri-apps/plugins-workspace/actions/runs/4869194441 Co-authored-by: FabianLars --- .gitignore | 1 + Cargo.toml | 17 +++ LICENSE.spdx | 20 +++ LICENSE_APACHE-2.0 | 177 ++++++++++++++++++++++ LICENSE_MIT | 21 +++ README.md | 114 ++++++++++++++ banner.png | Bin 0 -> 40819 bytes dist-js/index.d.ts | 105 +++++++++++++ dist-js/index.min.js | 182 ++++++++++++++++++++++ dist-js/index.min.js.map | 1 + dist-js/index.mjs | 177 ++++++++++++++++++++++ dist-js/index.mjs.map | 1 + guest-js/index.ts | 203 +++++++++++++++++++++++++ package.json | 33 ++++ rollup.config.mjs | 11 ++ src/error.rs | 37 +++++ src/lib.rs | 320 +++++++++++++++++++++++++++++++++++++++ src/store.rs | 319 ++++++++++++++++++++++++++++++++++++++ tsconfig.json | 1 + 19 files changed, 1740 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE.spdx create mode 100644 LICENSE_APACHE-2.0 create mode 100644 LICENSE_MIT create mode 100644 README.md create mode 100644 banner.png create mode 100644 dist-js/index.d.ts create mode 100644 dist-js/index.min.js create mode 100644 dist-js/index.min.js.map create mode 100644 dist-js/index.mjs create mode 100644 dist-js/index.mjs.map create mode 100644 guest-js/index.ts create mode 100644 package.json create mode 100644 rollup.config.mjs create mode 100644 src/error.rs create mode 100644 src/lib.rs create mode 100644 src/store.rs create mode 120000 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..626dbeb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tauri-plugin-store" +version = "0.0.0" +description = "Simple, persistent key-value store." +authors.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde.workspace = true +serde_json.workspace = true +tauri.workspace = true +log.workspace = true +thiserror.workspace = true \ No newline at end of file diff --git a/LICENSE.spdx b/LICENSE.spdx new file mode 100644 index 0000000..cdd0df5 --- /dev/null +++ b/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/LICENSE_APACHE-2.0 b/LICENSE_APACHE-2.0 new file mode 100644 index 0000000..4947287 --- /dev/null +++ b/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/LICENSE_MIT b/LICENSE_MIT new file mode 100644 index 0000000..4d75472 --- /dev/null +++ b/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4aef4a9 --- /dev/null +++ b/README.md @@ -0,0 +1,114 @@ +![plugin-store](banner.png) + +Simple, persistent key-value store. + +## Install + +_This plugin requires a Rust version of at least **1.64**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use. + +```sh +pnpm add https://github.com/tauri-apps/tauri-plugin-store +# or +npm add https://github.com/tauri-apps/tauri-plugin-store +# or +yarn add https://github.com/tauri-apps/tauri-plugin-store +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/main.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_store::Builder::default().build()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { Store } from "tauri-plugin-store-api"; + +const store = new Store(".settings.dat"); + +await store.set("some-key", { value: 5 }); + +const val = await store.get("some-key"); +assert(val, { value: 5 }); + +await store.save(); // this manually saves the store, otherwise the store is only saved when your app is closed +``` + +### Persisting values + +Values added to the store are not persisted between application loads unless: + +1. The application is closed gracefully (plugin automatically saves) +2. The store is manually saved (using `store.save()`) + +## Usage from Rust + +You can also access Stores from Rust, you can create new stores: + +```rust +use tauri_plugin_store::StoreBuilder; +use serde_json::json; + +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_store::Builder::default().build()) + .setup(|app| { + let mut store = StoreBuilder::new(app.handle(), "path/to/store.bin".parse()?).build(); + + store.insert("a".to_string(), json!("b")) // note that values must be serd_json::Value to be compatible with JS + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +As you may have noticed, the Store crated above isn't accessible to the frontend. To interoperate with stores created by JS use the exported `with_store` method: + +```rust +use tauri::Wry; +use tauri_plugin_store::with_store; + +let stores = app.state::>(); +let path = PathBuf::from("path/to/the/storefile"); + +with_store(app_handle, stores, path, |store| store.insert("a".to_string(), json!("b"))) +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/banner.png b/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..0bffdbcfb6a2e66f043c66facb6b1c71c1e337bd GIT binary patch literal 40819 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 AivR!s literal 0 HcmV?d00001 diff --git a/dist-js/index.d.ts b/dist-js/index.d.ts new file mode 100644 index 0000000..732e986 --- /dev/null +++ b/dist-js/index.d.ts @@ -0,0 +1,105 @@ +import { UnlistenFn } from "@tauri-apps/api/event"; +/** + * A key-value store persisted by the backend layer. + */ +export declare class Store { + path: string; + constructor(path: string); + /** + * 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` 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. + * @returns + */ + clear(): Promise; + /** + * Resets the store to it's `default` value. + * + * If no default value has been set, this method behaves identical to `clear`. + * @returns + */ + reset(): Promise; + /** + * Returns a list of all key in the store. + * + * @returns + */ + keys(): Promise; + /** + * Returns a list of all values in the store. + * + * @returns + */ + values(): Promise; + /** + * Returns a list of all entries in the store. + * + * @returns + */ + entries(): Promise>; + /** + * Returns the number of key-value pairs in the store. + * + * @returns + */ + length(): Promise; + /** + * Attempts to load the on-disk state at the stores `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 + */ + load(): Promise; + /** + * Saves the store to disk at the stores `path`. + * + * As the store is only persisted to disk before the apps exit, changes might be lost in a crash. + * This method lets you persist the store to disk whenever you deem necessary. + * @returns + */ + save(): Promise; + /** + * Listen to changes on a store key. + * @param key + * @param cb + * @returns A promise resolving to a function to unlisten to the event. + */ + onKeyChange(key: string, cb: (value: T | null) => void): Promise; + /** + * Listen to changes on the store. + * @param cb + * @returns A promise resolving to a function to unlisten to the event. + */ + onChange(cb: (key: string, value: T | null) => void): Promise; +} diff --git a/dist-js/index.min.js b/dist-js/index.min.js new file mode 100644 index 0000000..b8fa0b7 --- /dev/null +++ b/dist-js/index.min.js @@ -0,0 +1,182 @@ +var d=Object.defineProperty;var e=(c,a)=>{for(var b in a)d(c,b,{get:a[b],enumerable:!0});}; + +var f={};e(f,{convertFileSrc:()=>w,invoke:()=>c$1,transformCallback:()=>s$1});function u$1(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function s$1(e,r=!1){let n=u$1(),t=`_${n}`;return Object.defineProperty(window,t,{value:o=>(r&&Reflect.deleteProperty(window,t),e==null?void 0:e(o)),writable:!1,configurable:!0}),n}async function c$1(e,r={}){return new Promise((n,t)=>{let o=s$1(i=>{n(i),Reflect.deleteProperty(window,`_${a}`);},!0),a=s$1(i=>{t(i),Reflect.deleteProperty(window,`_${o}`);},!0);window.__TAURI_IPC__({cmd:e,callback:o,error:a,...r});})}function w(e,r="asset"){let n=encodeURIComponent(e);return navigator.userAgent.includes("Windows")?`https://${r}.localhost/${n}`:`${r}://localhost/${n}`} + +async function a(i){return c$1("tauri",i)} + +var W={};e(W,{TauriEvent:()=>c,emit:()=>D,listen:()=>E,once:()=>_});async function s(n,t){return a({__tauriModule:"Event",message:{cmd:"unlisten",event:n,eventId:t}})}async function m(n,t,i){await a({__tauriModule:"Event",message:{cmd:"emit",event:n,windowLabel:t,payload:i}});}async function o(n,t,i){return a({__tauriModule:"Event",message:{cmd:"listen",event:n,windowLabel:t,handler:s$1(i)}}).then(r=>async()=>s(n,r))}async function u(n,t,i){return o(n,t,r=>{i(r),s(n,r.id).catch(()=>{});})}var c=(e=>(e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_CREATED="tauri://window-created",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_FILE_DROP="tauri://file-drop",e.WINDOW_FILE_DROP_HOVER="tauri://file-drop-hover",e.WINDOW_FILE_DROP_CANCELLED="tauri://file-drop-cancelled",e.MENU="tauri://menu",e.CHECK_UPDATE="tauri://update",e.UPDATE_AVAILABLE="tauri://update-available",e.INSTALL_UPDATE="tauri://update-install",e.STATUS_UPDATE="tauri://update-status",e.DOWNLOAD_PROGRESS="tauri://update-download-progress",e))(c||{});async function E(n,t){return o(n,null,t)}async function _(n,t){return u(n,null,t)}async function D(n,t){return m(n,void 0,t)} + +// Copyright 2021 Tauri Programme within The Commons Conservancy +/** + * A key-value store persisted by the backend layer. + */ +class Store { + constructor(path) { + this.path = path; + } + /** + * Inserts a key-value pair into the store. + * + * @param key + * @param value + * @returns + */ + async set(key, value) { + return await c$1("plugin:store|set", { + path: this.path, + key, + value, + }); + } + /** + * Returns the value for the given `key` or `null` the key does not exist. + * + * @param key + * @returns + */ + async get(key) { + return await c$1("plugin:store|get", { + path: this.path, + key, + }); + } + /** + * Returns `true` if the given `key` exists in the store. + * + * @param key + * @returns + */ + async has(key) { + return await c$1("plugin:store|has", { + path: this.path, + key, + }); + } + /** + * Removes a key-value pair from the store. + * + * @param key + * @returns + */ + async delete(key) { + return await c$1("plugin:store|delete", { + path: this.path, + key, + }); + } + /** + * Clears the store, removing all key-value pairs. + * + * Note: To clear the storage and reset it to it's `default` value, use `reset` instead. + * @returns + */ + async clear() { + return await c$1("plugin:store|clear", { + path: this.path, + }); + } + /** + * Resets the store to it's `default` value. + * + * If no default value has been set, this method behaves identical to `clear`. + * @returns + */ + async reset() { + return await c$1("plugin:store|reset", { + path: this.path, + }); + } + /** + * Returns a list of all key in the store. + * + * @returns + */ + async keys() { + return await c$1("plugin:store|keys", { + path: this.path, + }); + } + /** + * Returns a list of all values in the store. + * + * @returns + */ + async values() { + return await c$1("plugin:store|values", { + path: this.path, + }); + } + /** + * Returns a list of all entries in the store. + * + * @returns + */ + async entries() { + return await c$1("plugin:store|entries", { + path: this.path, + }); + } + /** + * Returns the number of key-value pairs in the store. + * + * @returns + */ + async length() { + return await c$1("plugin:store|length", { + path: this.path, + }); + } + /** + * Attempts to load the on-disk state at the stores `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() { + return await c$1("plugin:store|load", { + path: this.path, + }); + } + /** + * Saves the store to disk at the stores `path`. + * + * As the store is only persisted to disk before the apps exit, changes might be lost in a crash. + * This method lets you persist the store to disk whenever you deem necessary. + * @returns + */ + async save() { + return await c$1("plugin:store|save", { + path: this.path, + }); + } + /** + * Listen to changes on a store key. + * @param key + * @param cb + * @returns A promise resolving to a function to unlisten to the event. + */ + async onKeyChange(key, cb) { + return await E("store://change", (event) => { + if (event.payload.path === this.path && event.payload.key === key) { + cb(event.payload.value); + } + }); + } + /** + * Listen to changes on the store. + * @param cb + * @returns A promise resolving to a function to unlisten to the event. + */ + async onChange(cb) { + return await E("store://change", (event) => { + if (event.payload.path === this.path) { + cb(event.payload.key, event.payload.value); + } + }); + } +} + +export { Store }; +//# sourceMappingURL=index.min.js.map diff --git a/dist-js/index.min.js.map b/dist-js/index.min.js.map new file mode 100644 index 0000000..e86dc32 --- /dev/null +++ b/dist-js/index.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.min.js","sources":["../../../node_modules/.pnpm/@tauri-apps+api@1.2.0/node_modules/@tauri-apps/api/chunk-FEIY7W7S.js","../../../node_modules/.pnpm/@tauri-apps+api@1.2.0/node_modules/@tauri-apps/api/chunk-RCPA6UVN.js","../../../node_modules/.pnpm/@tauri-apps+api@1.2.0/node_modules/@tauri-apps/api/chunk-HNLFKTAJ.js","../../../node_modules/.pnpm/@tauri-apps+api@1.2.0/node_modules/@tauri-apps/api/chunk-3WDDWFXT.js","../guest-js/index.ts"],"sourcesContent":["var d=Object.defineProperty;var e=(c,a)=>{for(var b in a)d(c,b,{get:a[b],enumerable:!0})};export{e as a};\n","import{a as d}from\"./chunk-FEIY7W7S.js\";var f={};d(f,{convertFileSrc:()=>w,invoke:()=>c,transformCallback:()=>s});function u(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function s(e,r=!1){let n=u(),t=`_${n}`;return Object.defineProperty(window,t,{value:o=>(r&&Reflect.deleteProperty(window,t),e==null?void 0:e(o)),writable:!1,configurable:!0}),n}async function c(e,r={}){return new Promise((n,t)=>{let o=s(i=>{n(i),Reflect.deleteProperty(window,`_${a}`)},!0),a=s(i=>{t(i),Reflect.deleteProperty(window,`_${o}`)},!0);window.__TAURI_IPC__({cmd:e,callback:o,error:a,...r})})}function w(e,r=\"asset\"){let n=encodeURIComponent(e);return navigator.userAgent.includes(\"Windows\")?`https://${r}.localhost/${n}`:`${r}://localhost/${n}`}export{s as a,c as b,w as c,f as d};\n","import{b as o}from\"./chunk-RCPA6UVN.js\";async function a(i){return o(\"tauri\",i)}export{a};\n","import{a}from\"./chunk-HNLFKTAJ.js\";import{a as l}from\"./chunk-RCPA6UVN.js\";import{a as d}from\"./chunk-FEIY7W7S.js\";var W={};d(W,{TauriEvent:()=>c,emit:()=>D,listen:()=>E,once:()=>_});async function s(n,t){return a({__tauriModule:\"Event\",message:{cmd:\"unlisten\",event:n,eventId:t}})}async function m(n,t,i){await a({__tauriModule:\"Event\",message:{cmd:\"emit\",event:n,windowLabel:t,payload:i}})}async function o(n,t,i){return a({__tauriModule:\"Event\",message:{cmd:\"listen\",event:n,windowLabel:t,handler:l(i)}}).then(r=>async()=>s(n,r))}async function u(n,t,i){return o(n,t,r=>{i(r),s(n,r.id).catch(()=>{})})}var c=(e=>(e.WINDOW_RESIZED=\"tauri://resize\",e.WINDOW_MOVED=\"tauri://move\",e.WINDOW_CLOSE_REQUESTED=\"tauri://close-requested\",e.WINDOW_CREATED=\"tauri://window-created\",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_FILE_DROP=\"tauri://file-drop\",e.WINDOW_FILE_DROP_HOVER=\"tauri://file-drop-hover\",e.WINDOW_FILE_DROP_CANCELLED=\"tauri://file-drop-cancelled\",e.MENU=\"tauri://menu\",e.CHECK_UPDATE=\"tauri://update\",e.UPDATE_AVAILABLE=\"tauri://update-available\",e.INSTALL_UPDATE=\"tauri://update-install\",e.STATUS_UPDATE=\"tauri://update-status\",e.DOWNLOAD_PROGRESS=\"tauri://update-download-progress\",e))(c||{});async function E(n,t){return o(n,null,t)}async function _(n,t){return u(n,null,t)}async function D(n,t){return m(n,void 0,t)}export{m as a,o as b,u as c,c as d,E as e,_ as f,D as g,W as h};\n",null],"names":["d","c","s","u","o","l","invoke","listen"],"mappings":"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","x_google_ignoreList":[0,1,2,3]} \ No newline at end of file diff --git a/dist-js/index.mjs b/dist-js/index.mjs new file mode 100644 index 0000000..adf1250 --- /dev/null +++ b/dist-js/index.mjs @@ -0,0 +1,177 @@ +import { invoke } from '@tauri-apps/api/tauri'; +import { listen } from '@tauri-apps/api/event'; + +// Copyright 2021 Tauri Programme within The Commons Conservancy +/** + * A key-value store persisted by the backend layer. + */ +class Store { + constructor(path) { + this.path = path; + } + /** + * Inserts a key-value pair into the store. + * + * @param key + * @param value + * @returns + */ + async set(key, value) { + return await invoke("plugin:store|set", { + path: this.path, + key, + value, + }); + } + /** + * Returns the value for the given `key` or `null` the key does not exist. + * + * @param key + * @returns + */ + async get(key) { + return await invoke("plugin:store|get", { + path: this.path, + key, + }); + } + /** + * Returns `true` if the given `key` exists in the store. + * + * @param key + * @returns + */ + async has(key) { + return await invoke("plugin:store|has", { + path: this.path, + key, + }); + } + /** + * Removes a key-value pair from the store. + * + * @param key + * @returns + */ + async delete(key) { + return await invoke("plugin:store|delete", { + path: this.path, + key, + }); + } + /** + * Clears the store, removing all key-value pairs. + * + * Note: To clear the storage and reset it to it's `default` value, use `reset` instead. + * @returns + */ + async clear() { + return await invoke("plugin:store|clear", { + path: this.path, + }); + } + /** + * Resets the store to it's `default` value. + * + * If no default value has been set, this method behaves identical to `clear`. + * @returns + */ + async reset() { + return await invoke("plugin:store|reset", { + path: this.path, + }); + } + /** + * Returns a list of all key in the store. + * + * @returns + */ + async keys() { + return await invoke("plugin:store|keys", { + path: this.path, + }); + } + /** + * Returns a list of all values in the store. + * + * @returns + */ + async values() { + return await invoke("plugin:store|values", { + path: this.path, + }); + } + /** + * Returns a list of all entries in the store. + * + * @returns + */ + async entries() { + return await invoke("plugin:store|entries", { + path: this.path, + }); + } + /** + * Returns the number of key-value pairs in the store. + * + * @returns + */ + async length() { + return await invoke("plugin:store|length", { + path: this.path, + }); + } + /** + * Attempts to load the on-disk state at the stores `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() { + return await invoke("plugin:store|load", { + path: this.path, + }); + } + /** + * Saves the store to disk at the stores `path`. + * + * As the store is only persisted to disk before the apps exit, changes might be lost in a crash. + * This method lets you persist the store to disk whenever you deem necessary. + * @returns + */ + async save() { + return await invoke("plugin:store|save", { + path: this.path, + }); + } + /** + * Listen to changes on a store key. + * @param key + * @param cb + * @returns A promise resolving to a function to unlisten to the event. + */ + async onKeyChange(key, cb) { + return await listen("store://change", (event) => { + if (event.payload.path === this.path && event.payload.key === key) { + cb(event.payload.value); + } + }); + } + /** + * Listen to changes on the store. + * @param cb + * @returns A promise resolving to a function to unlisten to the event. + */ + async onChange(cb) { + return await listen("store://change", (event) => { + if (event.payload.path === this.path) { + cb(event.payload.key, event.payload.value); + } + }); + } +} + +export { Store }; +//# sourceMappingURL=index.mjs.map diff --git a/dist-js/index.mjs.map b/dist-js/index.mjs.map new file mode 100644 index 0000000..0aceaac --- /dev/null +++ b/dist-js/index.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"index.mjs","sources":["../guest-js/index.ts"],"sourcesContent":[null],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/guest-js/index.ts b/guest-js/index.ts new file mode 100644 index 0000000..cc6058d --- /dev/null +++ b/guest-js/index.ts @@ -0,0 +1,203 @@ +// Copyright 2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from "@tauri-apps/api/tauri"; +import { listen, UnlistenFn } from "@tauri-apps/api/event"; + +interface ChangePayload { + path: string; + key: string; + value: T | null; +} + +/** + * A key-value store persisted by the backend layer. + */ +export class Store { + path: string; + constructor(path: string) { + this.path = path; + } + + /** + * Inserts a key-value pair into the store. + * + * @param key + * @param value + * @returns + */ + async set(key: string, value: unknown): Promise { + return await invoke("plugin:store|set", { + path: this.path, + key, + value, + }); + } + + /** + * 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", { + path: this.path, + key, + }); + } + + /** + * Returns `true` if the given `key` exists in the store. + * + * @param key + * @returns + */ + async has(key: string): Promise { + return await invoke("plugin:store|has", { + path: this.path, + key, + }); + } + + /** + * Removes a key-value pair from the store. + * + * @param key + * @returns + */ + async delete(key: string): Promise { + return await invoke("plugin:store|delete", { + path: this.path, + key, + }); + } + + /** + * Clears the store, removing all key-value pairs. + * + * Note: To clear the storage and reset it to it's `default` value, use `reset` instead. + * @returns + */ + async clear(): Promise { + return await invoke("plugin:store|clear", { + path: this.path, + }); + } + + /** + * Resets the store to it's `default` value. + * + * If no default value has been set, this method behaves identical to `clear`. + * @returns + */ + async reset(): Promise { + return await invoke("plugin:store|reset", { + path: this.path, + }); + } + + /** + * Returns a list of all key in the store. + * + * @returns + */ + async keys(): Promise { + return await invoke("plugin:store|keys", { + path: this.path, + }); + } + + /** + * Returns a list of all values in the store. + * + * @returns + */ + async values(): Promise { + return await invoke("plugin:store|values", { + path: this.path, + }); + } + + /** + * Returns a list of all entries in the store. + * + * @returns + */ + async entries(): Promise> { + return await invoke("plugin:store|entries", { + path: this.path, + }); + } + + /** + * Returns the number of key-value pairs in the store. + * + * @returns + */ + async length(): Promise { + return await invoke("plugin:store|length", { + path: this.path, + }); + } + + /** + * Attempts to load the on-disk state at the stores `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 { + return await invoke("plugin:store|load", { + path: this.path, + }); + } + + /** + * Saves the store to disk at the stores `path`. + * + * As the store is only persisted to disk before the apps 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 { + return await invoke("plugin:store|save", { + path: this.path, + }); + } + + /** + * Listen to changes on a store key. + * @param key + * @param cb + * @returns A promise resolving to a function to unlisten to the event. + */ + 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); + } + }); + } + + /** + * Listen to changes on the store. + * @param cb + * @returns A promise resolving to a function to unlisten to the event. + */ + 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); + } + }); + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..02e5d23 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "tauri-plugin-store-api", + "version": "0.0.0", + "description": "Simple, persistent key-value store.", + "license": "MIT or APACHE-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "type": "module", + "browser": "dist-js/index.min.js", + "module": "dist-js/index.mjs", + "types": "dist-js/index.d.ts", + "exports": { + "import": "./dist-js/index.mjs", + "types": "./dist-js/index.d.ts", + "browser": "./dist-js/index.min.js" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "!dist-js/**/*.map", + "README.md", + "LICENSE" + ], + "devDependencies": { + "tslib": "^2.5.0" + }, + "dependencies": { + "@tauri-apps/api": "^1.2.0" + } +} diff --git a/rollup.config.mjs b/rollup.config.mjs new file mode 100644 index 0000000..6555e98 --- /dev/null +++ b/rollup.config.mjs @@ -0,0 +1,11 @@ +import { readFileSync } from "fs"; + +import { createConfig } from "../../shared/rollup.config.mjs"; + +export default createConfig({ + input: "guest-js/index.ts", + pkg: JSON.parse( + readFileSync(new URL("./package.json", import.meta.url), "utf8") + ), + external: [/^@tauri-apps\/api/], +}); diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..03d2918 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,37 @@ +// Copyright 2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Serialize, Serializer}; +use std::path::PathBuf; + +/// The error types. +#[derive(thiserror::Error, Debug)] +#[non_exhaustive] +pub enum Error { + #[error("Failed to serialize store. {0}")] + Serialize(Box), + #[error("Failed to deserialize store. {0}")] + Deserialize(Box), + /// JSON error. + #[error(transparent)] + Json(#[from] serde_json::Error), + /// IO error. + #[error(transparent)] + Io(#[from] std::io::Error), + /// Store not found + #[error("Store \"{0}\" not found")] + NotFound(PathBuf), + /// Some Tauri API failed + #[error(transparent)] + Tauri(#[from] tauri::Error), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..26588b7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,320 @@ +// Copyright 2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +pub use error::Error; +use log::warn; +use serde::Serialize; +pub use serde_json::Value as JsonValue; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + sync::Mutex, +}; +pub use store::{Store, StoreBuilder}; +use tauri::{ + plugin::{self, TauriPlugin}, + AppHandle, Manager, RunEvent, Runtime, State, +}; + +mod error; +mod store; + +#[derive(Serialize, Clone)] +struct ChangePayload<'a> { + path: &'a Path, + key: &'a str, + value: &'a JsonValue, +} + +#[derive(Default)] +pub struct StoreCollection { + stores: Mutex>>, + frozen: bool, +} + +pub fn with_store) -> Result>( + app: AppHandle, + collection: State<'_, StoreCollection>, + path: impl AsRef, + f: F, +) -> Result { + let mut stores = collection.stores.lock().expect("mutex poisoned"); + + let path = path.as_ref(); + if !stores.contains_key(path) { + if collection.frozen { + return Err(Error::NotFound(path.to_path_buf())); + } + let mut store = StoreBuilder::new(app, path.to_path_buf()).build(); + // ignore loading errors, just use the default + if let Err(err) = store.load() { + warn!( + "Failed to load store {:?} from disk: {}. Falling back to default values.", + path, err + ); + } + stores.insert(path.to_path_buf(), store); + } + + f(stores + .get_mut(path) + .expect("failed to retrieve store. This is a bug!")) +} + +#[tauri::command] +async fn set( + app: AppHandle, + stores: State<'_, StoreCollection>, + path: PathBuf, + key: String, + value: JsonValue, +) -> Result<(), Error> { + with_store(app, stores, path, |store| store.insert(key, value)) +} + +#[tauri::command] +async fn get( + app: AppHandle, + stores: State<'_, StoreCollection>, + path: PathBuf, + key: String, +) -> Result, Error> { + with_store(app, stores, path, |store| Ok(store.get(key).cloned())) +} + +#[tauri::command] +async fn has( + app: AppHandle, + stores: State<'_, StoreCollection>, + path: PathBuf, + key: String, +) -> Result { + with_store(app, stores, path, |store| Ok(store.has(key))) +} + +#[tauri::command] +async fn delete( + app: AppHandle, + stores: State<'_, StoreCollection>, + path: PathBuf, + key: String, +) -> Result { + with_store(app, stores, path, |store| store.delete(key)) +} + +#[tauri::command] +async fn clear( + app: AppHandle, + stores: State<'_, StoreCollection>, + path: PathBuf, +) -> Result<(), Error> { + with_store(app, stores, path, |store| store.clear()) +} + +#[tauri::command] +async fn reset( + app: AppHandle, + collection: State<'_, StoreCollection>, + path: PathBuf, +) -> Result<(), Error> { + with_store(app, collection, path, |store| store.reset()) +} + +#[tauri::command] +async fn keys( + app: AppHandle, + stores: State<'_, StoreCollection>, + path: PathBuf, +) -> Result, Error> { + with_store(app, stores, path, |store| { + Ok(store.keys().cloned().collect()) + }) +} + +#[tauri::command] +async fn values( + app: AppHandle, + stores: State<'_, StoreCollection>, + path: PathBuf, +) -> Result, Error> { + with_store(app, stores, path, |store| { + Ok(store.values().cloned().collect()) + }) +} + +#[tauri::command] +async fn entries( + app: AppHandle, + stores: State<'_, StoreCollection>, + path: PathBuf, +) -> Result, Error> { + with_store(app, stores, path, |store| { + Ok(store + .entries() + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .collect()) + }) +} + +#[tauri::command] +async fn length( + app: AppHandle, + stores: State<'_, StoreCollection>, + path: PathBuf, +) -> Result { + with_store(app, stores, path, |store| Ok(store.len())) +} + +#[tauri::command] +async fn load( + app: AppHandle, + stores: State<'_, StoreCollection>, + path: PathBuf, +) -> Result<(), Error> { + with_store(app, stores, path, |store| store.load()) +} + +#[tauri::command] +async fn save( + app: AppHandle, + stores: State<'_, StoreCollection>, + path: PathBuf, +) -> Result<(), Error> { + with_store(app, stores, path, |store| store.save()) +} + +// #[derive(Default)] +pub struct Builder { + stores: HashMap>, + frozen: bool, +} + +impl Default for Builder { + fn default() -> Self { + Self { + stores: Default::default(), + frozen: false, + } + } +} + +impl Builder { + /// Registers a store with the plugin. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use tauri_plugin_store::{StoreBuilder,PluginBuilder}; + /// + /// let store = StoreBuilder::new("store.bin".parse()?).build(); + /// + /// let builder = PluginBuilder::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 + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use tauri_plugin_store::{StoreBuilder,PluginBuilder}; + /// + /// let store = StoreBuilder::new("store.bin".parse()?).build(); + /// + /// let builder = PluginBuilder::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 + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use tauri_plugin_store::{StoreBuilder,PluginBuilder}; + /// + /// let store = StoreBuilder::new("store.bin".parse()?).build(); + /// + /// let builder = PluginBuilder::default().freeze(); + /// + /// # Ok(()) + /// # } + /// ``` + pub fn freeze(mut self) -> Self { + self.frozen = true; + self + } + + /// Builds the plugin. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use tauri_plugin_store::{StoreBuilder,PluginBuilder}; + /// use tauri::Wry; + /// + /// let store = StoreBuilder::new("store.bin".parse()?).build(); + /// + /// let plugin = PluginBuilder::default().build::(); + /// + /// # Ok(()) + /// # } + /// ``` + pub fn build(mut self) -> TauriPlugin { + plugin::Builder::new("store") + .invoke_handler(tauri::generate_handler![ + set, get, has, delete, clear, reset, keys, values, length, entries, load, save + ]) + .setup(move |app_handle| { + 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 {:?} from disk: {}. Falling back to default values.", + path, err + ); + } + } + + app_handle.manage(StoreCollection { + stores: Mutex::new(self.stores), + frozen: self.frozen, + }); + + Ok(()) + }) + .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() { + if let Err(err) = store.save() { + eprintln!("failed to save store {:?} with error {:?}", store.path, err); + } + } + } + }) + .build() + } +} diff --git a/src/store.rs b/src/store.rs new file mode 100644 index 0000000..17ad48c --- /dev/null +++ b/src/store.rs @@ -0,0 +1,319 @@ +// Copyright 2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{ChangePayload, Error}; +use serde_json::Value as JsonValue; +use std::{ + collections::HashMap, + fs::{create_dir_all, read, File}, + io::Write, + path::PathBuf, +}; +use tauri::{AppHandle, Manager, Runtime}; + +type SerializeFn = + fn(&HashMap) -> Result, Box>; +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) +} + +/// Builds a [`Store`] +pub struct StoreBuilder { + app: AppHandle, + path: PathBuf, + defaults: Option>, + cache: HashMap, + serialize: SerializeFn, + deserialize: DeserializeFn, +} + +impl StoreBuilder { + /// Creates a new [`StoreBuilder`]. + /// + /// # Examples + /// ``` + /// # fn main() -> Result<(), Box> { + /// use tauri_plugin_store::StoreBuilder; + /// + /// let builder = StoreBuilder::new("store.bin".parse()?); + /// + /// # Ok(()) + /// # } + /// ``` + pub fn new(app: AppHandle, path: PathBuf) -> Self { + Self { + app, + path, + defaults: None, + cache: Default::default(), + serialize: default_serialize, + deserialize: default_deserialize, + } + } + + /// Inserts a default key-value pair. + /// + /// # Examples + /// ``` + /// # fn main() -> Result<(), Box> { + /// use tauri_plugin_store::StoreBuilder; + /// use std::collections::HashMap; + /// + /// let mut defaults = HashMap::new(); + /// + /// defaults.insert("foo".to_string(), "bar".into()); + /// + /// let builder = StoreBuilder::new("store.bin".parse()?) + /// .defaults(defaults); + /// + /// # Ok(()) + /// # } + pub fn defaults(mut self, defaults: HashMap) -> Self { + self.cache = defaults.clone(); + self.defaults = Some(defaults); + self + } + + /// Inserts multiple key-value pairs. + /// + /// # Examples + /// ``` + /// # fn main() -> Result<(), Box> { + /// use tauri_plugin_store::StoreBuilder; + /// + /// let builder = StoreBuilder::new("store.bin".parse()?) + /// .default("foo".to_string(), "bar".into()); + /// + /// # Ok(()) + /// # } + pub fn default(mut self, key: String, value: JsonValue) -> Self { + self.cache.insert(key.clone(), value.clone()); + self.defaults + .get_or_insert(HashMap::new()) + .insert(key, value); + self + } + + /// Defines a custom serialization function. + /// + /// # Examples + /// ``` + /// # fn main() -> Result<(), Box> { + /// use tauri_plugin_store::StoreBuilder; + /// + /// let builder = StoreBuilder::new("store.json".parse()?) + /// .serialize(|cache| serde_json::to_vec(&cache).map_err(Into::into)); + /// + /// # Ok(()) + /// # } + pub fn serialize(mut self, serialize: SerializeFn) -> Self { + self.serialize = serialize; + self + } + + /// Defines a custom deserialization function + /// + /// # Examples + /// ``` + /// # fn main() -> Result<(), Box> { + /// use tauri_plugin_store::StoreBuilder; + /// + /// let builder = StoreBuilder::new("store.json".parse()?) + /// .deserialize(|bytes| serde_json::from_slice(&bytes).map_err(Into::into)); + /// + /// # Ok(()) + /// # } + pub fn deserialize(mut self, deserialize: DeserializeFn) -> Self { + self.deserialize = deserialize; + self + } + + /// Builds the [`Store`]. + /// + /// # Examples + /// ``` + /// # fn main() -> Result<(), Box> { + /// use tauri_plugin_store::StoreBuilder; + /// + /// let store = StoreBuilder::new("store.bin".parse()?).build(); + /// + /// # Ok(()) + /// # } + pub fn build(self) -> Store { + Store { + app: self.app, + path: self.path, + defaults: self.defaults, + cache: self.cache, + serialize: self.serialize, + deserialize: self.deserialize, + } + } +} + +#[derive(Clone)] +pub struct Store { + app: AppHandle, + pub(crate) path: PathBuf, + defaults: Option>, + cache: HashMap, + serialize: SerializeFn, + deserialize: DeserializeFn, +} + +impl Store { + /// Update the store from the on-disk state + pub fn load(&mut self) -> Result<(), Error> { + let app_dir = self + .app + .path_resolver() + .app_data_dir() + .expect("failed to resolve app dir"); + let store_path = app_dir.join(&self.path); + + let bytes = read(store_path)?; + + self.cache + .extend((self.deserialize)(&bytes).map_err(Error::Deserialize)?); + + Ok(()) + } + + /// Saves the store to disk + pub fn save(&self) -> Result<(), Error> { + let app_dir = self + .app + .path_resolver() + .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"))?; + + let bytes = (self.serialize)(&self.cache).map_err(Error::Serialize)?; + let mut f = File::create(&store_path)?; + f.write_all(&bytes)?; + + Ok(()) + } + + pub fn insert(&mut self, key: String, value: JsonValue) -> Result<(), Error> { + self.cache.insert(key.clone(), value.clone()); + self.app.emit_all( + "store://change", + ChangePayload { + path: &self.path, + key: &key, + value: &value, + }, + )?; + + Ok(()) + } + + pub fn get(&self, key: impl AsRef) -> Option<&JsonValue> { + self.cache.get(key.as_ref()) + } + + pub fn has(&self, key: impl AsRef) -> bool { + self.cache.contains_key(key.as_ref()) + } + + pub fn delete(&mut self, key: impl AsRef) -> Result { + let flag = self.cache.remove(key.as_ref()).is_some(); + if flag { + self.app.emit_all( + "store://change", + ChangePayload { + path: &self.path, + key: key.as_ref(), + value: &JsonValue::Null, + }, + )?; + } + Ok(flag) + } + + pub fn clear(&mut self) -> Result<(), Error> { + let keys: Vec = self.cache.keys().cloned().collect(); + self.cache.clear(); + for key in keys { + self.app.emit_all( + "store://change", + ChangePayload { + path: &self.path, + key: &key, + value: &JsonValue::Null, + }, + )?; + } + Ok(()) + } + + pub fn reset(&mut self) -> Result<(), Error> { + let has_defaults = self.defaults.is_some(); + + if has_defaults { + if let Some(defaults) = &self.defaults { + for (key, value) in &self.cache { + if defaults.get(key) != Some(value) { + let _ = self.app.emit_all( + "store://change", + ChangePayload { + path: &self.path, + key, + value: defaults.get(key).unwrap_or(&JsonValue::Null), + }, + ); + } + } + self.cache = defaults.clone(); + } + Ok(()) + } else { + self.clear() + } + } + + pub fn keys(&self) -> impl Iterator { + self.cache.keys() + } + + pub fn values(&self) -> impl Iterator { + self.cache.values() + } + + pub fn entries(&self) -> impl Iterator { + self.cache.iter() + } + + pub fn len(&self) -> usize { + self.cache.len() + } + + pub fn is_empty(&self) -> bool { + self.cache.is_empty() + } +} + +impl std::fmt::Debug for Store { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Store") + .field("path", &self.path) + .field("defaults", &self.defaults) + .field("cache", &self.cache) + .finish() + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 120000 index 0000000..7cd38da --- /dev/null +++ b/tsconfig.json @@ -0,0 +1 @@ +../../shared/tsconfig.json \ No newline at end of file