plugins-workspace icon indicating copy to clipboard operation
plugins-workspace copied to clipboard

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

Open Narcha opened this issue 1 year ago • 4 comments

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:

  • Expose a single async function in the WebView, which loads the store file initially
  • All other get() and set() calls can be synchronous.
  • Cache the store in the WebView
  • Register event handlers to update the cache or the file on disk if either changes

Benefits:

  • Avoid async boilerplate
  • Users can define a scheme of their store in Typescript, get and set functions would thus be automatically typed without needing casts or assertions in user code
  • Less IPC calls

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 avatar Sep 14 '22 19:09 Narcha