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
cwdoption specifies the target directory (e.g., the user’s stories folder), and the store creates files named<options.name>.store. - Default initialization – The
defaultsobject ensures sane initial values when no previous store file exists. - Automatic sync – The
subscribecallback registers a listener that invokespersistor.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:
- Encoding – State objects are packed using
msgpackrinto binary buffers. - Storage format – These buffers are converted to hexadecimal strings before being written to disk, resulting in files with the
.storeextension that are technically human-readable while remaining compact. - 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:
- Application start – Instantiating
new Settings()creates aPersistableinstance, which initializesElectronStoreand loads<storiesFolder>/settings.store. If the file does not exist, the store writes thedefaultsobject immediately. - Runtime mutation – User actions trigger methods like
updateTheme('dark'), which callStateful.updateto produce a new immutable state. - 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. - Session restoration – When the application restarts, the
ElectronStoreconstructor reads the existing.storefile, deserializes it back to a JavaScript object, and makes it available viathis.statebefore any UI components render.
Summary
- 5ire wraps
electron-storeinside aStateful.Persistableclass located insrc/main/internal/stateful.tsto 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>.storein 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →