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

> Discover how 5ire uses electron-store and msgpackr for persistent application settings and user preferences. Learn about automatic serialization and disk writes for seamless state management.

- Repository: [Ironben/5ire](https://github.com/nanbingxyz/5ire)
- Tags: internals
- Published: 2026-03-07

---

**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`](https://github.com/nanbingxyz/5ire/blob/main/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:

```typescript
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`](https://github.com/nanbingxyz/5ire/blob/main/src/main/services/settings.ts) demonstrates real-world usage of this abstraction:

```typescript
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`](https://github.com/nanbingxyz/5ire/blob/main/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`](https://github.com/nanbingxyz/5ire/blob/main/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.