tauri-apps / plugins-workspace

All of the official Tauri plugins in one place!
https://tauri.app
Apache License 2.0
921 stars 255 forks source link

[store] Question: Why not keep the store cache in the WebView process? #15

Open Narcha opened 2 years ago

Narcha commented 2 years ago

I'm currently developing a program that uses this plugin. Currently, if I need a value from the store for the user interface, like for example the user's preferred theme, I need to call an asynchronous function that returns a promise which resolves to the requested value.

This leads to a lot of unnecessary boilerplate code, because I can't "just use" the store values, but have to add a wrapper to each part of my code that uses a store value to abstract away the async nature of the store. This leads to ugly, unnecessary code and lots of frustration.

The contents of the store file are currently cached by the plugin in the main rust process. Having the content of the store cached in the WebView process would solve this: Other parts of the user interface code can immediately use the values without needing "async wrappers".

I propose a different approach, which looks like this:

Benefits:

Performance should not be affected by this, as the store is usually called infrequently and IPC calls are reduced.

Also, take a look at the electron-store library, which I have used in the past and does exactly what I want.

Example code to showcase the API I have in mind, and some elegant typescript typing which is possible with this approach: playground

// Incomplete example
class Store<Schema>{
  cache: Partial<Schema>;
  defaults: Required<Schema>;

  constructor(store: string, defaults: Required<Schema>) {
    this.cache = {};
    this.defaults = defaults;
    // TODO: add more options to this constructor
  }

  async init() {
    // Load the store file
    // optional: register callbacks to update the cache if the file changed, using a file watcher
  }

  get<K extends keyof Schema>(key: K): Schema[K] {
    return this.cache[key] ?? this.defaults[key];
  }

  set<K extends keyof Schema>(key: K, value: Schema[K]): void {
    this.cache[key] = value;
    // call IPC to update the file
  }
}

/* --- Example user code --- */

type MyStoreSchema = {
  theme: "dark" | "light",
  foo: number,
}

const store = new Store<MyStoreSchema>("config.json", { theme: "dark", foo: 0 });

await store.init();

const theme = store.get("theme")
const foo = store.get("foo");

// `theme` is of type "dark" | "light", `foo` is a number - no casts or type assertions needed!

if (theme === "light") {
  store.set("theme", "dark");
}
Narcha commented 2 years ago

The electron-store library uses this package to manage lots of the front-end needs of a store library, it could be used/forked for this.

Narcha commented 2 years ago

Forgot to mention that I would be happy to help build a solution to this :)

ellsong commented 1 year ago

I did a test port of an electron app I use in production, and this was the biggest sticking point that made Tauri frustrating to use. Electron-store's approach was superior for two reasons.

  1. Easier function calls with less boilerplate
  2. Ability to use JSON schemas defined in Typescript

I'd love to see an implementation like this for Tauri

Narcha commented 1 year ago

I completely agree. The JSON schema would be a very welcome addition as well.

For now, I settled on using a store-like wrapper around LocalStorage to store user configuration - but I would prefer to use this plugin again once it becomes usable.

Edit:typo