-
Notifications
You must be signed in to change notification settings - Fork 188
feat(drivers): add Tauri Store driver and docs #753
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hairyf
wants to merge
3
commits into
unjs:main
Choose a base branch
from
hairyf:feature/tauri-store-driver
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| --- | ||
| icon: simple-icons:tauri | ||
| --- | ||
|
|
||
| # Tauri Store | ||
|
|
||
| > Store data via [Tauri Store Plugin](https://tauri.app/plugin/store) in Tauri desktop/mobile apps. | ||
|
|
||
| ::read-more{to="https://tauri.app/plugin/store"} | ||
| Learn more about Tauri Store Plugin. | ||
| :: | ||
|
|
||
| ## Usage | ||
|
|
||
| **Driver name:** `tauri` | ||
|
|
||
| Install the Tauri store plugin in your Tauri project, then install unstorage: | ||
|
|
||
| :pm-install{name="@tauri-apps/plugin-store"} | ||
|
|
||
| Usage: | ||
|
|
||
| ```js | ||
| import { createStorage } from "unstorage"; | ||
| import tauriDriver from "unstorage/drivers/tauri"; | ||
|
|
||
| const storage = createStorage({ | ||
| driver: tauriDriver({ | ||
| path: "store.json", | ||
| base: "app", | ||
| options: { autoSave: 100 }, | ||
| }), | ||
| }); | ||
| ``` | ||
|
|
||
| **Options:** | ||
|
|
||
| - `path`: Path to the store file (e.g. `"store.json"`). Required. | ||
| - `base`: Optional prefix for all keys (namespace). | ||
| - `options`: Optional [StoreOptions](https://tauri.app/plugin/store/) from `@tauri-apps/plugin-store` (e.g. `autoSave`: `false` to disable auto-save, or a number for debounce ms; default 100). | ||
|
|
||
| The driver supports `watch` via the store’s `onChange` listener for key updates. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| import { load } from "@tauri-apps/plugin-store"; | ||
|
|
||
| import { type DriverFactory, joinKeys, normalizeKey } from "./utils/index.ts"; | ||
|
|
||
| const DRIVER_NAME = "tauri"; | ||
|
|
||
| export interface TauriStorageDriverOptions { | ||
| /** | ||
| * Path to the store file (e.g. `"store.json"`). | ||
| */ | ||
| path: string; | ||
| /** | ||
| * Optional [StoreOptions](https://tauri.app/plugin/store/) (e.g. `autoSave`). | ||
| */ | ||
| options?: import("@tauri-apps/plugin-store").StoreOptions; | ||
| /** | ||
| * Optional prefix for all keys (namespace). | ||
| */ | ||
| base?: string; | ||
| } | ||
|
|
||
| type TauriStore = Awaited<ReturnType<typeof load>>; | ||
|
|
||
| const driver: DriverFactory<TauriStorageDriverOptions, Promise<TauriStore>> = (opts) => { | ||
| const base = normalizeKey(opts?.base || ""); | ||
| const resolveKey = (key: string) => joinKeys(base, key); | ||
|
|
||
| const storePromise = load(opts.path, opts.options); | ||
|
|
||
| return { | ||
| name: DRIVER_NAME, | ||
| options: opts, | ||
| getInstance: () => storePromise, | ||
| async hasItem(key) { | ||
| const store = await storePromise; | ||
| return store.has(resolveKey(key)); | ||
| }, | ||
| async getItem(key) { | ||
| const store = await storePromise; | ||
| return store.get(resolveKey(key)) ?? null; | ||
| }, | ||
| async setItem(key, value) { | ||
| const store = await storePromise; | ||
| await store.set(resolveKey(key), value); | ||
| }, | ||
| async removeItem(key) { | ||
| const store = await storePromise; | ||
| return store.delete(resolveKey(key)); | ||
| }, | ||
| async getKeys(basePrefix) { | ||
| const store = await storePromise; | ||
| const prefix = resolveKey(basePrefix || ""); | ||
| const allKeys = await store.keys(); | ||
| if (!prefix) { | ||
| return base ? allKeys.map((k) => (k.startsWith(base + ":") ? k.slice(base.length + 1) : k)).filter(Boolean) : allKeys; | ||
| } | ||
| return allKeys | ||
| .filter((k) => k === prefix || k.startsWith(prefix + ":")) | ||
| .map((k) => (base ? k.slice(base.length + 1) : k)) | ||
| .filter(Boolean); | ||
| }, | ||
| async clear(basePrefix) { | ||
| const store = await storePromise; | ||
| const prefix = resolveKey(basePrefix || ""); | ||
| const allKeys = await store.keys(); | ||
| const toRemove = prefix | ||
| ? allKeys.filter((k) => k === prefix || k.startsWith(prefix + ":")) | ||
| : base | ||
| ? allKeys.filter((k) => k === base || k.startsWith(base + ":")) | ||
| : allKeys; | ||
| await Promise.all(toRemove.map((k) => store.delete(k))); | ||
| }, | ||
| async watch(callback) { | ||
| const store = await storePromise; | ||
| return store.onChange((key, value) => { | ||
| const eventKey = base && key.startsWith(base + ":") ? key.slice(base.length + 1) : key; | ||
| callback(value === null ? "remove" : "update", eventKey); | ||
| }); | ||
| }, | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| }; | ||
| }; | ||
|
|
||
| export default driver; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { afterEach, describe, vi } from "vitest"; | ||
| import driver from "../../src/drivers/tauri.ts"; | ||
| import { testDriver } from "./utils.ts"; | ||
|
|
||
| vi.mock("@tauri-apps/plugin-store", () => { | ||
| return { | ||
| load: vi.fn(() => { | ||
| const data = new Map<string, unknown>(); | ||
| return Promise.resolve({ | ||
| has: (key: string) => Promise.resolve(data.has(key)), | ||
| get: (key: string) => Promise.resolve(data.get(key) ?? null), | ||
| set: (key: string, value: unknown) => | ||
| Promise.resolve(void data.set(key, value)), | ||
| delete: (key: string) => Promise.resolve(void data.delete(key)), | ||
| keys: () => Promise.resolve([...data.keys()]), | ||
| clear: () => Promise.resolve(void data.clear()), | ||
| onChange: (_cb: (key: string, value: unknown) => void) => () => {}, | ||
|
hairyf marked this conversation as resolved.
|
||
| }); | ||
| }), | ||
| }; | ||
| }); | ||
|
|
||
| describe("drivers: tauri", () => { | ||
| afterEach(() => { | ||
| vi.resetAllMocks(); | ||
| }); | ||
|
|
||
| testDriver({ | ||
| driver: driver({ path: "store.json" }), | ||
| }); | ||
|
|
||
| testDriver({ | ||
| driver: driver({ path: "store.json", base: "app" }), | ||
| }); | ||
| }); | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filter
watch()events to the configured base namespace.When
baseis set, this still forwards changes for every key in the store. A driver scoped toappwill emit events for unrelated namespaces likeother:*, which breaks the isolation the rest of the driver enforces.🐛 Proposed fix
async watch(callback) { const store = await storePromise; return store.onChange((key, value) => { - const eventKey = base && key.startsWith(base + ":") ? key.slice(base.length + 1) : key; + if (base && key !== base && !key.startsWith(`${base}:`)) { + return; + } + const eventKey = + base ? (key === base ? "" : key.slice(base.length + 1)) : key; callback(value === null ? "remove" : "update", eventKey); }); },🤖 Prompt for AI Agents