// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT import { listen, type UnlistenFn } from '@tauri-apps/api/event' import { invoke, Resource } from '@tauri-apps/api/core' interface ChangePayload { path: string resourceId?: number key: string value: T exists: boolean } /** * Options to create a store */ 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 } /** * @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 */ export async function createStore( path: string, options?: StoreOptions ): Promise { 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 */ export async function getStore(path: string): Promise { return await Store.getStore(path) } /** * A lazy loaded key-value store persisted by the backend layer. */ export class LazyStore implements IStore { private _store?: Promise private get store(): Promise { if (!this._store) { this._store = createOrExistingStore(this.path, this.options) } 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 */ async init(): Promise { await 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 | undefined) => void ): Promise { return (await this.store).onKeyChange(key, cb) } async onChange( cb: (key: string, value: T | undefined) => 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 ) { super(rid) } /** * @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 { const rid = await invoke('plugin:store|create_store', { path, ...options }) return new Store( rid // path ) } /** * 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 */ static async getStore(path: string): Promise { const rid = await invoke('plugin:store|get_store', { path }) return rid ? new Store( rid // path ) : undefined } async set(key: string, value: unknown): Promise { await invoke('plugin:store|set', { rid: this.rid, key, value }) } 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 { return await invoke('plugin:store|has', { rid: this.rid, key }) } async delete(key: string): Promise { return await invoke('plugin:store|delete', { rid: this.rid, key }) } 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 | undefined) => void ): Promise { return await listen>('store://change', (event) => { if (event.payload.resourceId === this.rid && event.payload.key === key) { cb(event.payload.exists ? event.payload.value : undefined) } }) } async onChange( 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.exists ? event.payload.value : undefined ) } }) } 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 { /** * 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 `undefined` 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 its `default` value, use {@linkcode reset} instead. * @returns */ clear(): Promise /** * Resets the store to its `default` value. * * If no default value has been set, this method behaves identical to {@linkcode clear}. * @returns */ reset(): Promise /** * Returns a list of all keys 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 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 */ load(): Promise /** * Saves the store to disk at the store's `path`. * @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. * * @since 2.0.0 */ onKeyChange( key: string, cb: (value: T | undefined) => void ): Promise /** * Listen to changes on the store. * @param cb * @returns A promise resolving to a function to unlisten to the event. * * @since 2.0.0 */ onChange( cb: (key: string, value: T | undefined) => 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 }