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

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 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.

// 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 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.

// 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. 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.

// 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.

// 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 demonstrates reading and writing the function mode preference using the centralized key definitions and composable helpers.

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 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 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, 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 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, defining IPreferenceService, IStorageProvider, and related contracts that ensure type safety across the core and UI packages.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →