# How the Preference and Settings Service Manages User Configurations in Prompt Optimizer

> Discover how the preference and settings service in prompt optimizer manages user configurations with type-safe abstractions, key validation, automatic migration, and import/export capabilities.

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

---

**The preference and settings service in linshenkx/prompt-optimizer provides a type-safe abstraction over storage providers that prefixes keys with `pref:`, validates writes against a whitelist, migrates legacy keys automatically, and exposes import/export capabilities for cross-platform settings synchronization.**

The linshenkx/prompt-optimizer repository implements a sophisticated preference and settings service to persist user-specific configurations—including themes, languages, and UI modes—across web browsers, desktop Electron builds, and extension environments. This architecture centralizes storage key definitions in dedicated constants and sanitizes all data through a service layer that wraps various storage backends such as IndexedDB or Electron Store.

## Architecture of the Preference and Settings Service

### Centralized Storage Key Definitions

All UI-related configuration keys are defined centrally in [`packages/core/src/constants/storage-keys.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/core/src/constants/storage-keys.ts) within the `UI_SETTINGS_KEYS` constant. This prevents key collisions and provides TypeScript autocompletion while maintaining a single source of truth for valid configuration options.

```typescript
// packages/core/src/constants/storage-keys.ts
export const UI_SETTINGS_KEYS = {
  THEME_ID: "app:settings:ui:theme-id",
  PREFERRED_LANGUAGE: "app:settings:ui:preferred-language",
  BUILTIN_TEMPLATE_LANGUAGE: "app:settings:ui:builtin-template-language",
  FUNCTION_MODE: "app:settings:ui:function-mode",
  // sub‑mode persistence
  BASIC_SUB_MODE: "app:settings:ui:basic-sub-mode",
  PRO_SUB_MODE: "app:settings:ui:pro-sub-mode",
  IMAGE_SUB_MODE: "app:settings:ui:image-sub-mode",
} as const;

```

### Core Service Implementation

The `PreferenceService` class in [`packages/core/src/services/preference/service.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/core/src/services/preference/service.ts) implements the `IPreferenceService` interface and wraps an injected `IStorageProvider`. Every key is automatically prefixed with `pref:` via the private `getPrefKey` method, creating a dedicated namespace that isolates user preferences from other application data.

```typescript
// packages/core/src/services/preference/service.ts
export class PreferenceService implements IPreferenceService {
  private readonly PREFIX = "pref:";   // all keys stored with this prefix
  private storageProvider: IStorageProvider;

  async get<T>(key: string, defaultValue: T): Promise<T> {
    const prefKey = this.getPrefKey(key);
    const stored = await this.storageProvider.getItem(prefKey);
    return stored === null ? defaultValue : JSON.parse(stored) as T;
  }

  async set<T>(key: string, value: T): Promise<void> {
    const prefKey = this.getPrefKey(key);
    await this.storageProvider.setItem(prefKey, JSON.stringify(value));
  }

  private getPrefKey(key: string): string {
    return `${this.PREFIX}${key}`;
  }
}

```

### UI Integration via Composables

UI components interact with the preference and settings service through the `usePreferenceManager` composable located in [`packages/ui/src/composables/storage/usePreferenceManager.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/ui/src/composables/storage/usePreferenceManager.ts). This layer provides `getPreference` and `setPreference` helpers that automatically validate the injected `services` object and delegate to the underlying service, ensuring consistent error handling across the application.

```typescript
// packages/ui/src/composables/storage/usePreferenceManager.ts
export async function getPreference<T>(
  services: AppServices,
  key: string,
  defaultValue: T,
) {
  if (!services?.preferenceService) {
    throw new Error(`[getPreference] preferenceService unavailable`);
  }
  return await services.preferenceService.get<T>(key, defaultValue);
}

export async function setPreference<T>(
  services: AppServices,
  key: string,
  value: T,
) {
  if (!services?.preferenceService) {
    throw new Error(`[setPreference] preferenceService unavailable`);
  }
  await services.preferenceService.set<T>(key, value);
}

```

## Safety Mechanisms and Data Integrity

### Whitelist Validation and Sanitization

Before persisting any value, the preference and settings service validates the key against the `UI_SETTINGS_KEYS` whitelist and sanitizes the input to prevent injection attacks or malformed JSON entries. This ensures only approved configuration options are stored in the user's environment, maintaining data integrity across storage backends.

### Automatic Legacy Key Migration

The service maintains a `LEGACY_KEY_MAPPING` dictionary that automatically upgrades deprecated short keys to new fully-qualified formats. This backward compatibility layer ensures existing user data remains accessible after schema updates without requiring manual migration scripts or user intervention.

## Import and Export Capabilities

Implementing the `IImportExportable` interface, the preference and settings service exposes `exportData` and `importData` methods that serialize all stored preferences as a plain `Record<string, string>` object. This enables users to backup their configurations or migrate settings between devices and platforms seamlessly.

```typescript
// Export all UI settings as JSON
const exported = await services.preferenceService.exportData();
// Store / download the JSON file for backup
await downloadFile('user-settings.json', JSON.stringify(exported));

// Import settings from a backup file
const imported = JSON.parse(await readFile('user-settings.json'));
await services.preferenceService.importData(imported);

```

## Practical Usage Examples

The following example from [`packages/ui/src/composables/mode/useFunctionMode.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/ui/src/composables/mode/useFunctionMode.ts) demonstrates reading and writing the function mode preference using the centralized key definitions and composable helpers.

```typescript
import { UI_SETTINGS_KEYS } from '@prompt-optimizer/core';
import { usePreferences } from '@/composables/storage/usePreferenceManager';

const { getPreference, setPreference } = usePreferences(services);

// Read the current function mode, default to 'basic'
const mode = await getPreference<string>(services, UI_SETTINGS_KEYS.FUNCTION_MODE, 'basic');

// Update the mode after user toggles it
await setPreference<string>(services, UI_SETTINGS_KEYS.FUNCTION_MODE, 'pro');

```

## Summary

- **Centralized key definitions** in [`packages/core/src/constants/storage-keys.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/core/src/constants/storage-keys.ts) prevent naming collisions and provide compile-time safety through the `UI_SETTINGS_KEYS` constant.
- The `PreferenceService` class in [`packages/core/src/services/preference/service.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/core/src/services/preference/service.ts) prefixes all keys with `pref:` and validates them against a whitelist before storage.
- **Legacy key mappings** ensure backward compatibility when configuration schemas evolve, automatically upgrading deprecated keys during read operations.
- **Import and export functionality** via the `IImportExportable` interface enables complete settings backup and restoration as plain JSON objects.
- UI components access the preference and settings service through type-safe composables in [`packages/ui/src/composables/storage/usePreferenceManager.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/ui/src/composables/storage/usePreferenceManager.ts), abstracting the underlying storage provider implementation.

## Frequently Asked Questions

### How does the preference service handle different storage backends?

The preference and settings service abstracts storage through the `IStorageProvider` interface, allowing the same `PreferenceService` logic to operate with IndexedDB in browsers, local files in Node.js environments, or Electron Store in desktop builds without requiring code changes. The specific provider is injected during service initialization, making the core logic storage-agnostic.

### What happens if an invalid key is passed to setPreference?

The service validates each key against the `UI_SETTINGS_KEYS` whitelist before writing. Invalid keys are rejected at the validation layer, preventing arbitrary data pollution in the storage backend and ensuring only approved UI settings defined in [`packages/core/src/constants/storage-keys.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/core/src/constants/storage-keys.ts) are persisted.

### Can users transfer settings between the web and desktop versions?

Yes. Because the service implements `IImportExportable`, users can call `exportData` to generate a JSON backup containing all `pref:` prefixed values on one platform and `importData` to restore it on another. This maintains consistent preferences across web, Electron, and extension environments since the underlying data structure remains identical.

### Where are the type definitions for the preference service located?

The TypeScript interfaces and type definitions for the preference and settings service are located in [`packages/core/src/services/preference/types.ts`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/core/src/services/preference/types.ts), defining `IPreferenceService`, `IStorageProvider`, and related contracts that ensure type safety across the core and UI packages.