How Wand Enhancer Handles Data Storage and Persistence: A Deep Dive into the localStorage Architecture
Wand Enhancer persists all user data using a centralized browser localStorage abstraction layer in web-panel/src/shared/storage.ts, providing type-safe JSON serialization and hierarchical key management without external databases.
The k1tbyte/Wand-Enhancer repository implements a lightweight, client-side persistence strategy designed specifically for its Electron-based environment. Instead of relying on external databases or complex backend systems, the application stores user preferences—such as pinned cheats, trainer presets, and UI settings—directly in the browser's localStorage. This approach ensures instantaneous data retrieval and survival across application restarts while maintaining strict type safety and namespace isolation.
The Centralized Storage Abstraction Layer
All persistence logic in Wand Enhancer flows through a single source of truth: web-panel/src/shared/storage.ts. This module wraps raw localStorage calls in strongly-typed helper functions, preventing the fragmentation of storage logic across the codebase.
Type-Safe JSON Serialization Functions
The storage layer exposes generic helper functions that handle JSON serialization and deserialization with compile-time type checking:
loadJson<T>(key: string): T | undefined– Retrieves and safely parses JSON values fromlocalStorage, returningundefinedfor missing keys.saveJson<T>(key: string, value: T): void– Serializes values to JSON and writes them immediately tolocalStorage.loadStringSet(key: string): Set<string>– Specialized helper for reading string arrays and converting them toSetobjects.saveStringSet(key: string, set: Set<string>): void– PersistsSet<string>collections as JSON arrays.
These abstractions ensure that higher-level modules never interact with raw storage APIs directly. For example, both web-panel/src/trainer/pinned-storage.ts and web-panel/src/trainer/preset-storage.ts import these helpers to manage their respective data domains.
Strict Key Naming Conventions
The storage.ts file enforces a rigorous naming convention that prevents key collisions and enables versioning. Keys are generated through exported factory functions rather than hard-coded strings:
// From web-panel/src/shared/storage.ts
export const PINNED_CHEATS_KEY = (gameId: string) =>
`wand-remote.pinned-cheats.v1:${gameId}`;
export const PRESET_KEY = (id: string) =>
`wand-remote.presets.v1:${id}`;
This namespacing strategy uses the wand-remote.* prefix to isolate Wand Enhancer's data from other browser extensions or the host Electron application. The v1 segment allows for future schema migrations without breaking existing user data.
Hierarchical Storage Identification
A critical challenge in trainer applications is mapping complex object hierarchies to flat storage keys. Wand Enhancer solves this through a dedicated identity resolution system.
The getTrainerStorageId Helper
The getTrainerStorageId(trainer) function (exported from storage.ts) serves as the single source of truth for deriving storage identifiers. It translates trainer objects into hierarchical IDs following the priority chain: gameId → titleId → trainerId → 'global'.
This function is consumed by both the pin-storage and preset-storage modules to ensure consistent key generation:
import { getTrainerStorageId, saveJson } from '@/shared/storage';
function storeTrainerData(trainer: Trainer, data: any) {
const storageId = getTrainerStorageId(trainer);
// Generates appropriate hierarchical ID before persisting
saveJson(storageId, data);
}
By centralizing this logic, the codebase eliminates duplication and prevents bugs caused by inconsistent key construction across different feature modules.
Namespace Isolation and Version Control
The storage architecture deliberately avoids collisions through:
- Prefixed keys: All entries use the
wand-remote.namespace - Versioned schemas: The
.v1suffix enables future IndexedDB or schema migrations - Hierarchical granularity: Storage IDs respect the relationship between games, titles, and trainers
Practical Implementation Examples
Storing Pinned Cheats
When users pin a cheat for a specific game, the system persists the configuration using the PINNED_CHEATS_KEY generator:
import { saveJson, PINNED_CHEATS_KEY } from '@/shared/storage';
const pin = { cheatId: 'godMode', enabled: true };
const gameId = '12345';
// Persists to: wand-remote.pinned-cheats.v1:12345
saveJson(PINNED_CHEATS_KEY(gameId), pin);
Loading Trainer Presets
Preset retrieval demonstrates the type-safe deserialization pattern:
import { loadJson, PRESET_KEY } from '@/shared/storage';
const trainerId = 'trainer-abc';
const preset = loadJson(PRESET_KEY(trainerId));
if (preset) {
// Re-hydrate runtime state from localStorage
applyPreset(preset);
}
Managing String Collections
For collections like recent searches or favorite categories, the specialized Set helpers provide cleaner APIs:
import { loadStringSet, saveStringSet } from '@/shared/storage';
const recentCheats = loadStringSet('wand-remote.recent-cheats');
recentCheats.add('infiniteHealth');
saveStringSet('wand-remote.recent-cheats', recentCheats);
Why localStorage? Architecture Benefits
Wand Enhancer's persistence strategy is deliberately lightweight and optimized for its Electron-based desktop environment:
- No external dependencies: Everything lives client-side, eliminating network latency and server maintenance
- Immediate persistence: Writes flush instantly to
localStorage, ensuring no data loss on unexpected crashes - Cross-session survival: User preferences persist across browser reloads and separate Wand Enhancer launches
- Migration-ready: The isolated abstraction layer means switching to IndexedDB or SQLite in the future requires changes only to
storage.ts
Summary
- Centralized abstraction: All storage operations route through
web-panel/src/shared/storage.ts, preventing code duplication and ensuring type safety - Hierarchical key management: The
getTrainerStorageIdfunction provides consistent storage identifier resolution across game/trainer hierarchies - Versioned namespacing: Keys follow the
wand-remote.<feature>.v1:<id>pattern to prevent collisions and enable schema evolution - Client-side persistence: The application uses browser
localStorageexclusively, fitting the Electron architecture without external databases - Strong typing: Generic
loadJson<T>andsaveJson<T>functions provide compile-time type checking for all persisted structures
Frequently Asked Questions
Where does Wand Enhancer store user preferences like pinned cheats?
Wand Enhancer stores all persistent data in the browser's localStorage through the abstraction layer in web-panel/src/shared/storage.ts. Pinned cheats are saved under keys generated by PINNED_CHEATS_KEY(gameId), which produces strings like wand-remote.pinned-cheats.v1:12345.
How does the application prevent storage key collisions?
The codebase enforces a strict naming convention where all keys use the wand-remote. prefix combined with feature-specific namespaces (e.g., pinned-cheats, presets) and version identifiers (e.g., v1). Key generation is centralized in storage.ts through exported functions, preventing hard-coded strings throughout the application.
Is the storage layer type-safe?
Yes. The storage.ts module exports generic functions like loadJson<T> and saveJson<T> that provide compile-time type checking. When loading data, TypeScript knows the expected structure, and the functions handle JSON serialization/deserialization internally while preserving type information.
Can the storage backend be changed without rewriting the entire application?
Yes. Because all storage logic is isolated in web-panel/src/shared/storage.ts, migrating from localStorage to IndexedDB or another persistence mechanism would require modifications only to the helper functions within that single file. Higher-level modules like pinned-storage.ts and preset-storage.ts would continue to work unchanged.
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 →