How electron-store Persists Application Settings and User Preferences in 5ire

5ire implements a custom Stateful.Persistable abstraction that wraps electron-store to automatically serialize user preferences using msgpackr and hex encoding, writing them to disk on every state change and reloading them when the application restarts.

The open-source 5ire repository (nanbingxyz/5ire) leverages electron-store as its backbone for durable configuration storage. By wrapping the library in a type-safe, immutable state container powered by immer, the application ensures that settings like theme, language, and font size persist across sessions without requiring manual file I/O in business logic.

The Stateful.Persistable Abstraction Layer

Immutable State Management with Stateful

At the core of the persistence system is the abstract class Stateful<T> defined in src/main/internal/stateful.ts. This class maintains an immutable snapshot (#state) and provides methods like update, replace, and apply to modify state safely using immer producers. It also implements a subscription system that notifies listeners whenever state changes occur.

Wiring electron-store for Automatic Persistence

Nested within the same file, the Stateful.Persistable subclass extends Stateful and adds the actual persistence mechanism:

protected constructor(options: Stateful.Persistable.Options<T>) {
  const persistor = new ElectronStore<T>({
    defaults: options.defaults,
    name: options.name,
    cwd: options.directory,
    serialize: value => Buffer.from(Persistable.#encoder.pack(value)).toString('hex'),
    deserialize: value => Persistable.#encoder.unpack(Buffer.from(value, 'hex')),
    fileExtension: 'store',
  });

  super(() => persistor.store);

  // Automatic disk write on every state change
  this.subscribe((_, state) => {
    persistor.set(state);
  });
}

Key implementation details from the source code include:

  • Custom serialization – Data is encoded using msgpackr for compact binary representation, then converted to a hex string. This produces human-readable files for debugging while maintaining storage efficiency.
  • File location – The cwd option specifies the target directory (e.g., the user’s stories folder), and the store creates files named <options.name>.store.
  • Default initialization – The defaults object ensures sane initial values when no previous store file exists.
  • Automatic sync – The subscribe callback registers a listener that invokes persistor.set(state) immediately after every state mutation, eliminating the need for explicit save calls in service code.

Concrete Usage: The Settings Service

The Settings service in src/main/services/settings.ts demonstrates real-world usage of this abstraction:

export class Settings extends Stateful.Persistable<Settings.State> {
  constructor() {
    super({
      name: 'settings',
      directory: Container.inject(Environment).storiesFolder,
      defaults: {
        language: 'system',
        theme: 'system',
        fontSize: 'base',
      },
    });

    nativeTheme.themeSource = this.state.theme;
  }

  updateTheme(theme: Settings.Theme) { /* triggers persistence automatically */ }
  updateLanguage(language: Settings.Language) { /* triggers persistence automatically */ }
  updateFontSize(fontSize: Settings.FontSize) { /* triggers persistence automatically */ }
}

When instantiated, the constructor supplies the store name (settings), the directory path, and default values. The this.state property returns either the values loaded from disk or the provided defaults if the file is absent. Any call to mutator methods like updateTheme flows through Stateful.update, which generates patches, notifies subscribers, and triggers the Persistable subscription to write the new state back to <storiesFolder>/settings.store.

Data Serialization and Storage Format

According to the source code analysis, 5ire uses a specific encoding strategy to balance efficiency with debuggability:

  1. Encoding – State objects are packed using msgpackr into binary buffers.
  2. Storage format – These buffers are converted to hexadecimal strings before being written to disk, resulting in files with the .store extension that are technically human-readable while remaining compact.
  3. Decoding – On application startup, the hex strings are converted back to buffers and unpacked by msgpackr to restore the original JavaScript objects.

Persistence Lifecycle

The complete flow of how electron-store persists application settings across sessions follows these steps:

  1. Application start – Instantiating new Settings() creates a Persistable instance, which initializes ElectronStore and loads <storiesFolder>/settings.store. If the file does not exist, the store writes the defaults object immediately.
  2. Runtime mutation – User actions trigger methods like updateTheme('dark'), which call Stateful.update to produce a new immutable state.
  3. Automatic persistence – The subscription callback fires, calling persistor.set(state) to serialize and write the entire state object to disk using the custom hex-encoded msgpackr format.
  4. Session restoration – When the application restarts, the ElectronStore constructor reads the existing .store file, deserializes it back to a JavaScript object, and makes it available via this.state before any UI components render.

Summary

  • 5ire wraps electron-store inside a Stateful.Persistable class located in src/main/internal/stateful.ts to provide type-safe, immutable state management.
  • Automatic persistence is achieved through a subscription pattern that writes to disk immediately after every state change, using persistor.set(state).
  • Custom serialization uses msgpackr with hex string encoding, storing files as <name>.store in configurable directories (typically the user's stories folder).
  • Default values ensure the application initializes correctly even when no previous settings file exists, as implemented in src/main/services/settings.ts.

Frequently Asked Questions

Where does 5ire store user preferences on disk?

User preferences are stored as .store files in the application's stories folder, specified via the cwd (current working directory) option when constructing the ElectronStore instance. For example, the settings service creates a file named settings.store in the directory returned by Container.inject(Environment).storiesFolder.

What serialization format does 5ire use for persisted state?

The application uses msgpackr for binary serialization, then converts the resulting buffer to a hexadecimal string for storage. This approach, defined in the serialize and deserialize options of Stateful.Persistable, produces compact files while keeping them human-readable for debugging purposes.

How does 5ire ensure settings persist automatically without manual save calls?

The Stateful.Persistable class registers a subscriber during construction using this.subscribe((_, state) => { persistor.set(state); }). This callback executes immediately after every state mutation performed through update or replace methods, ensuring the disk always reflects the latest state without requiring explicit save logic in service methods.

What happens if the settings file is missing or corrupted?

If the store file does not exist, electron-store automatically initializes it using the defaults object passed to the constructor. For the Settings service, these defaults include language: 'system', theme: 'system', and fontSize: 'base', ensuring the application launches with sensible initial configuration even on first run.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →