# How Wand Enhancer Handles Data Storage and Persistence: A Deep Dive into the localStorage Architecture

> Discover how Wand Enhancer manages data storage and persistence using a type-safe localStorage architecture. Learn about its JSON serialization and key management for robust browser data handling.

- Repository: [k1tbyte/Wand-Enhancer](https://github.com/k1tbyte/Wand-Enhancer)
- Tags: deep-dive
- Published: 2026-07-13

---

**Wand Enhancer persists all user data using a centralized browser `localStorage` abstraction layer in [`web-panel/src/shared/storage.ts`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/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`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/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 from `localStorage`, returning `undefined` for missing keys.
- **`saveJson<T>(key: string, value: T): void`** – Serializes values to JSON and writes them immediately to `localStorage`.
- **`loadStringSet(key: string): Set<string>`** – Specialized helper for reading string arrays and converting them to `Set` objects.
- **`saveStringSet(key: string, set: Set<string>): void`** – Persists `Set<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`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/web-panel/src/trainer/pinned-storage.ts) and [`web-panel/src/trainer/preset-storage.ts`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/web-panel/src/trainer/preset-storage.ts) import these helpers to manage their respective data domains.

### Strict Key Naming Conventions

The [`storage.ts`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/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:

```ts
// 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`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/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:

```ts
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 `.v1` suffix 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:

```ts
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:

```ts
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:

```ts
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`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/storage.ts)

## Summary

- **Centralized abstraction**: All storage operations route through [`web-panel/src/shared/storage.ts`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/web-panel/src/shared/storage.ts), preventing code duplication and ensuring type safety
- **Hierarchical key management**: The `getTrainerStorageId` function 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 `localStorage` exclusively, fitting the Electron architecture without external databases
- **Strong typing**: Generic `loadJson<T>` and `saveJson<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`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/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`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/storage.ts) through exported functions, preventing hard-coded strings throughout the application.

### Is the storage layer type-safe?

Yes. The [`storage.ts`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/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`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/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`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/pinned-storage.ts) and [`preset-storage.ts`](https://github.com/k1tbyte/Wand-Enhancer/blob/main/preset-storage.ts) would continue to work unchanged.