# How the Prompt-Optimizer Storage Layer Handles Multiple Providers: Dexie, LocalStorage, and File

> Discover how the prompt-optimizer storage layer elegantly manages Dexie LocalStorage and File providers via the IStorageProvider interface and StorageFactory for versatile data persistence.

- Repository: [且炼时光/prompt-optimizer](https://github.com/linshenkx/prompt-optimizer)
- Tags: internals
- Published: 2026-02-23

---

**The prompt-optimizer storage layer abstracts all persistent data behind the `IStorageProvider` interface and uses a `StorageFactory` to instantiate Dexie (IndexedDB), LocalStorage, or File-based providers depending on the runtime environment.**

The `linshenkx/prompt-optimizer` project implements a unified storage architecture that seamlessly adapts to web browsers, Electron desktops, and test environments. By decoupling the business logic from specific storage mechanisms, the prompt-optimizer storage layer ensures consistent data persistence across platforms while leveraging the unique capabilities of each backend.

## The Storage Abstraction Interface

All storage implementations conform to the `IStorageProvider` interface defined in [`packages/core/src/services/storage/types.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/core/src/services/storage/types.ts). This contract standardizes CRUD operations, transactional capabilities, and provider metadata across every backend.

The **StorageFactory** in [`packages/core/src/services/storage/factory.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/core/src/services/storage/factory.ts) manages provider instantiation and caching:

```typescript
// packages/core/src/services/storage/factory.ts
static create(type: StorageType): IStorageProvider {
  // Singleton pattern: reuse existing instances
  if (this.instances.has(type)) {
    return this.instances.get(type)!;
  }
  
  let instance: IStorageProvider;
  switch (type) {
    case 'localStorage': 
      instance = new LocalStorageProvider(); 
      break;
    case 'dexie': 
      instance = new DexieStorageProvider(); 
      break;
    case 'file': 
      // Electron-specific, instantiated directly in main process
      break;
  }
  
  this.instances.set(type, instance);
  return instance;
}

```

The factory exposes `isSupported(type)` to verify runtime availability (e.g., checking for IndexedDB) and `getSupportedTypes()` to list viable providers for the current platform.

## Dexie Storage Provider (IndexedDB)

The `DexieStorageProvider` in [`packages/core/src/services/storage/dexieStorageProvider.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/core/src/services/storage/dexieStorageProvider.ts) delivers high-performance, transactional storage using IndexedDB via the Dexie.js wrapper.

**Database Schema:**
- **Name:** `PromptOptimizerDB` (configurable via `window.__TEST_DB_NAME__` for testing)
- **Table:** `storage` with schema `++id, key, value, timestamp`

**Core API Implementation:**

```typescript
// packages/core/src/services/storage/dexieStorageProvider.ts
async setItem(key: string, value: any): Promise<void> {
  await this.db.storage.put({ 
    key, 
    value, 
    timestamp: Date.now() 
  });
}

async getItem(key: string): Promise<any> {
  const record = await this.db.storage.get(key);
  return record?.value ?? null;
}

async removeItem(key: string): Promise<void> {
  await this.db.storage.delete(key);
}

```

**Advanced Transactional Features:**

- **`atomicUpdate`** – Implements optimistic concurrency with exponential backoff for conflicting writes
- **`batchUpdate`** – Bulk operations using `bulkPut` and `bulkDelete` within a single transaction
- **`exportAll / importAll`** – Full database serialization for backup and migration

**Capabilities:** `supportsAtomic: true`, `supportsBatch: true`, no fixed size limit.

## LocalStorage Provider

The `LocalStorageProvider` in [`packages/core/src/services/storage/localStorageProvider.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/core/src/services/storage/localStorageProvider.ts) wraps the browser's synchronous `localStorage` API with asynchronous locking to prevent race conditions.

**Concurrency Control:**
Each key operates under an independent **async lock** (`async-mutex`), ensuring that concurrent read-modify-write cycles serialize correctly:

```typescript
// packages/core/src/services/storage/localStorageProvider.ts
async updateData(key: string, modifier: (data: any) => any): Promise<void> {
  const release = await this.lock.acquire(key);
  try {
    const current = localStorage.getItem(key);
    const parsed = current ? JSON.parse(current) : null;
    const newValue = modifier(parsed);
    localStorage.setItem(key, JSON.stringify(newValue));
  } finally {
    release();
  }
}

```

**Standard API:**
All methods return Promises to maintain interface consistency with other providers:
- `getItem(key)` – Retrieves and parses JSON
- `setItem(key, value)` – Serializes and stores
- `removeItem(key)` – Deletes entry
- `clearAll()` – Clears entire localStorage

**Capabilities:** `supportsAtomic: true` (via locking), `supportsBatch: true`, approximately **5 MiB** maximum storage.

## File Storage Provider (Electron)

The `FileStorageProvider` in [`packages/core/src/services/storage/fileStorageProvider.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/core/src/services/storage/fileStorageProvider.ts) implements durable persistence for Electron applications using the local filesystem.

**Storage Location:**
Persists to [`prompt-optimizer-data.json`](https://github.com/linshenkx/prompt-optimizer/blob/main/prompt-optimizer-data.json) within Electron's `userData` directory.

**Robust Initialization:**
The provider implements a **recovery-aware initialization** sequence that attempts to load the primary file, falls back to a backup copy, and initializes a fresh store if both fail:

```typescript
// packages/core/src/services/storage/fileStorageProvider.ts
async initialize(): Promise<void> {
  await this.loadFromFileWithRecovery();
}

private async loadFromFileWithRecovery(): Promise<void> {
  // Attempt primary file, then backup, then fresh start
  // Implementation handles corrupted JSON gracefully
}

```

**Atomic Write Strategy:**
To prevent data corruption during crashes, writes use **atomic file operations**:
1. Serialize data to a temporary file
2. Rename temporary file to target (atomic on POSIX/Windows)

**Debounced Persistence:**
Writes are **debounced** (`WRITE_DELAY = 500ms`) to batch rapid successive updates. The `flush()` method forces immediate synchronization with configurable timeout and retry limits:

```typescript
// packages/core/src/services/storage/fileStorageProvider.ts
async setItem(key: string, value: any): Promise<void> {
  this.data.set(key, value);
  this.scheduleWrite();  // Debounced flush
}

async flush(): Promise<void> {
  // Immediate write with retry logic
  await this.atomicWrite(JSON.stringify(Object.fromEntries(this.data)));
}

```

**Capabilities:** `supportsAtomic: true`, `supportsBatch: true`, unlimited storage (disk-constrained).

## Runtime Provider Selection

The application selects the appropriate provider based on the execution context:

```typescript
import { StorageFactory } from '@/services/storage/factory';

const storage = StorageFactory.create(
  typeof window === 'undefined' 
    ? 'file'           // Electron main process (Node.js)
    : window.indexedDB 
      ? 'dexie'        // Modern browser with IndexedDB
      : 'localStorage' // Fallback for legacy browsers
);

```

This detection logic ensures optimal storage selection: **Dexie** for high-performance web applications, **LocalStorage** for simple browser fallback, and **File** for Electron desktop builds requiring unlimited persistence.

## Summary

- The **prompt-optimizer storage layer** uses the `IStorageProvider` interface to abstract all persistence operations, enabling seamless swapping between backends without changing business logic.
- **StorageFactory** ([`packages/core/src/services/storage/factory.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/core/src/services/storage/factory.ts)) implements a singleton pattern that caches provider instances and exposes runtime capability detection via `isSupported()` and `getSupportedTypes()`.
- **DexieStorageProvider** leverages IndexedDB with transactional support for `atomicUpdate`, `batchUpdate`, and full database `exportAll/importAll` operations.
- **LocalStorageProvider** wraps the synchronous browser API with per-key async locks to provide atomic read-modify-write cycles despite localStorage's single-threaded nature.
- **FileStorageProvider** delivers Electron-specific persistence with recovery-aware initialization, debounced atomic file writes, and unlimited storage capacity.

## Frequently Asked Questions

### How does the storage factory decide which provider to instantiate?

The `StorageFactory.create()` method accepts a `StorageType` string (`'dexie'`, `'localStorage'`, or `'file'`) and returns the corresponding singleton instance. Runtime selection logic typically checks `typeof window` to detect Electron's main process (selecting `'file'`), then falls back to checking `window.indexedDB` availability for `'dexie'`, otherwise defaulting to `'localStorage'`.

### What happens if the Electron file storage becomes corrupted?

The `FileStorageProvider` implements a recovery chain in `loadFromFileWithRecovery()`. It first attempts to parse the primary [`prompt-optimizer-data.json`](https://github.com/linshenkx/prompt-optimizer/blob/main/prompt-optimizer-data.json). If that fails, it attempts to load from a backup file. If both fail, it initializes a fresh empty data store, ensuring the application remains functional even after filesystem corruption.

### Can I use atomic updates with LocalStorage despite it being synchronous?

Yes. While `localStorage` itself is synchronous, the `LocalStorageProvider` wraps all operations with per-key async locks using the `async-mutex` library. This ensures that `updateData` calls serialize concurrent modifications, providing atomic read-modify-write semantics even when multiple async operations attempt to modify the same key simultaneously.

### What are the storage size limits for each provider?

**DexieStorageProvider** has no fixed size limit beyond the browser's IndexedDB quota (typically 50MB+ depending on the browser). **LocalStorageProvider** is constrained to approximately **5 MiB** total for all keys combined due to browser limitations. **FileStorageProvider** has no practical limit beyond available disk space, making it suitable for large datasets in Electron applications.