The Core Services Layer (@prompt-optimizer/core package) contains all business logic for the Prompt Optimizer application. It provides platform-independent services for AI model interactions, prompt optimization, data management, and persistence. This layer is shared across all platform implementations (web, desktop, extension, MCP server) and serves as the foundation of the application's functionality.
This document introduces the architecture and design patterns of the core services layer. For details on specific services, see:
For UI components that consume these services, see User Interface Layer.
Sources: packages/core/package.json1-46 packages/core/src/index.ts1-229
The core services layer follows a service-oriented architecture with clear separation of concerns. Services are organized into distinct functional domains, each managing a specific aspect of the application's business logic.
Sources: packages/core/src/index.ts18-229 packages/core/src/services/llm/service.ts1-418 packages/core/src/services/prompt/service.ts1-50
Services are created using factory functions that handle dependency injection and environment-specific configuration. The initialization flow follows a specific order to ensure dependencies are available when needed.
Sources: packages/core/src/services/llm/service.ts386-398 packages/core/src/utils/environment.ts136-167 packages/core/src/services/storage/factory.ts1-50
All core services follow a consistent factory function pattern that enables:
The typical factory function structure:
Key Pattern Elements:
createLLMService) returns interface type (ILLMService)isRunningInElectron()) determines implementationElectronLLMProxy delegates to main processLLMService with TextAdapterRegistrySources: packages/core/src/services/llm/service.ts386-398 packages/core/src/services/prompt/factory.ts1-50
When running in Electron's renderer process, services use proxy classes to communicate with the main process via IPC. This solves CORS restrictions and enables access to Node.js APIs.
Sources: packages/core/src/services/llm/electron-proxy.ts1-50 packages/desktop/preload.js1-100 packages/core/src/utils/environment.ts136-167
The storage layer provides a unified interface (IStorageProvider) for persisting data across different runtime environments. This abstraction allows the same business logic to work with IndexedDB (browser), file system (Node.js/Electron), or in-memory storage (tests).
Sources: packages/core/src/services/storage/types.ts1-100 packages/core/src/services/storage/dexieStorageProvider.ts1-150 packages/core/src/services/storage/fileStorageProvider.ts1-200
The StorageFactory automatically selects the appropriate storage implementation based on the runtime environment:
| Environment | Storage Provider | Storage Location | Notes |
|---|---|---|---|
| Web Browser | DexieStorageProvider | IndexedDB | Persists across sessions, domain-scoped |
| Chrome Extension | DexieStorageProvider | IndexedDB | Extension-scoped storage |
| Electron Desktop | FileStorageProvider | userData/storage/*.json | File system in app data directory |
| MCP Server | FileStorageProvider | process.cwd()/storage/*.json | File system in working directory |
| Tests | MemoryStorageProvider | In-memory Map<string, string> | Ephemeral, isolated per test |
Sources: packages/core/src/services/storage/factory.ts1-100 packages/desktop/main.js1-200
All storage keys are defined as constants in CORE_SERVICE_KEYS to prevent typos and enable centralized management:
Sources: packages/core/src/constants/storage-keys.ts1-100
The core services layer achieves platform independence through three key strategies:
The environment.ts utility provides functions to detect the runtime environment:
Environment Variable Priority (highest to lowest):
window.runtime_config) - Docker environmentprocess.env - Node.js environmentimport.meta.env - Vite build environmentSources: packages/core/src/utils/environment.ts113-255
Services use environment detection to provide appropriate implementations:
Sources: packages/core/src/services/llm/service.ts386-398 packages/core/src/utils/environment.ts136-167
All services depend on IStorageProvider interface rather than concrete implementations, allowing the storage factory to provide the appropriate backend:
Sources: packages/core/src/services/model/manager.ts22-36
The adapter pattern isolates provider-specific API implementations from the core business logic. This is used extensively for LLM providers and image generation services.
Key Benefits:
ITextProviderAdapterLLMServiceSources: packages/core/src/services/llm/adapters/registry.ts1-200 packages/core/src/services/llm/types.ts1-150
Factory functions provide a single entry point for service creation, handling dependency injection and environment-specific configuration:
Example Factory Chain:
Sources: packages/core/src/services/storage/factory.ts1-50 packages/core/src/services/model/manager.ts1-50 packages/core/src/services/llm/service.ts386-398
Services managing collections of data follow the repository pattern, providing CRUD operations and specialized queries:
Common Repository Methods:
| Method Category | Examples | Purpose |
|---|---|---|
| Initialization | ensureInitialized(), isInitialized() | Async initialization handling |
| Retrieval | getAll(), getById(), getEnabled() | Query operations |
| Mutation | add(), update(), delete() | Modify data |
| State Management | enable(), disable() | Toggle state |
| Import/Export | exportData(), importData() | Data portability |
Example: ModelManager Repository Interface
Sources: packages/core/src/services/model/types.ts97-112 packages/core/src/services/model/manager.ts22-698
The storage layer uses the strategy pattern to swap storage implementations at runtime based on the environment:
Sources: packages/core/src/services/storage/types.ts1-100 packages/core/src/services/storage/factory.ts1-100
Services follow a specific initialization sequence to ensure dependencies are available and data is loaded from storage:
Critical Initialization Steps:
StorageFactory detects environment and creates appropriate providerLazy Initialization:
Some services use lazy initialization to avoid circular dependencies:
Sources: packages/core/src/services/model/manager.ts42-55 packages/core/src/services/storage/factory.ts1-100 packages/core/src/services/llm/service.ts22-30
The core services layer uses typed error classes with internationalization-friendly error codes. All error codes are defined as constants to enable UI layer to display localized error messages.
Error Class Hierarchy:
Sources: packages/core/src/constants/error-codes.ts1-200 packages/core/src/services/llm/errors.ts1-50
The core services layer includes comprehensive testing infrastructure with support for mocking external APIs and isolating tests:
| Utility | Purpose | Location |
|---|---|---|
| MemoryStorageProvider | In-memory storage for test isolation | packages/core/src/services/storage/memoryStorageProvider.ts1-100 |
| VCR System | Record/replay HTTP interactions | packages/core/tests/unit/utils/vcr.spec.ts1-200 |
| Mock Services | Mock LLM/Image services for testing | packages/core/tests/unit/utils/llm-mock-service.spec.ts1-200 |
Test Example:
Sources: packages/core/tests/unit/model/manager.test.ts1-650 packages/core/src/services/storage/memoryStorageProvider.ts1-100
Refresh this wiki