# Data System Reference This is the main entry point for Cherry Studio's data management documentation. The application uses four data systems based on data characteristics and loading requirements. ## Quick Navigation ### System Overview (Architecture) - [Boot Config Overview](./boot-config-overview.md) - Early boot configuration system - [Cache Overview](./cache-overview.md) - Three-tier caching architecture - [Preference Overview](./preference-overview.md) - User settings management - [DataApi Overview](./data-api-overview.md) - Business data API architecture - [App State Overview](./app-state-overview.md) - Internal continuity markers (main-process) ### Usage Guides (Code Examples) - [Cache Usage](./cache-usage.md) - useCache hooks, CacheService examples - [Preference Usage](./preference-usage.md) - usePreference hook, PreferenceService examples - [DataApi in Renderer](./data-api-in-renderer.md) - useQuery/useMutation, DataApiService - [DataApi in Main](./data-api-in-main.md) - Handlers, Services patterns ### Reference Guides (Coding Standards) - [API Design Guidelines](./api-design-guidelines.md) - RESTful design rules - [Database Patterns](./database-patterns.md) - DB naming, schema patterns, [Write Serialization (`withWriteTx`)](./database-patterns.md#write-serialization-dbservicewithwritetx) — the conventional wrapper for multi-statement / read-then-write atomicity (a direct `db.transaction()` is equivalent; single autocommit writes need neither), running as one synchronous `BEGIN IMMEDIATE` transaction on the one better-sqlite3 connection - [Database Construction](./database-construction.md) - Boot build order, drizzle migrations, CUSTOM_SQL_STATEMENTS replay, FTS5/`fts_rowid`, additive-vs-rebuild - [API Types](./api-types.md) - API type system, schemas, error handling - [Cache Schema Guide](./cache-schema-guide.md) - Adding new cache keys (fixed and template) - [Preference Schema Guide](./preference-schema-guide.md) - Adding new preference keys - [Boot Config Schema Guide](./boot-config-schema-guide.md) - Adding new boot config keys - [Layered Preset Pattern](./best-practice-layered-preset-pattern.md) - Presets with user overrides - [Default Values & Nullability](./best-practice-default-values-and-nullability.md) - Column nullability rules, default value placement across DB / `$defaultFn` / service, PATCH derivation patterns - [Ordering Guide](./data-ordering-guide.md) - Unified RESTful spec for sortable resources (fractional indexing) - [Pagination Guide](./data-pagination-guide.md) - Canonical spec for offset vs cursor (keyset) pagination: types, server impl, hooks, FTS, gotchas - [V2 Migration Guide](./v2-migration-guide.md) - Migration system - [Database Seeding Guide](./database-seeding-guide.md) - Seeding architecture, version strategies, adding new seeders ### Testing - [Test Mocks](../../../tests/__mocks__/README.md) - Unified mocks for Cache, Preference, and DataApi --- ## Choosing the Right System ### Quick Decision Table | Service | Data Characteristics | Lifecycle | Data Loss Impact | Examples | | --------------------- | ---------------------------- | --------------------------------- | -------------------------- | ----------------------------------------------------- | | **BootConfigService** | Process-level, pre-lifecycle | Permanent until changed | Low (can rebuild) | Hardware acceleration, Chromium flags, data directory | | **CacheService** | Regenerable, temporary | ≤ App process or survives restart | None to minimal | API responses, computed results, UI state | | **PreferenceService** | User settings, key-value | Permanent until changed | Low (can rebuild) | Theme, language, font size, shortcuts | | **DataApiService** | Business data, structured | Permanent | **Severe** (irreplaceable) | Topics, messages, files, knowledge base | | `app_state` (table) | Internal continuity marker (main-process) | Until owner drops the key | Continuity break (re-runs a one-time flow) | Migration status, seeding journal | ### Decision Flowchart Ask these questions in order: 1. **Must this setting be loaded before the lifecycle system takes over?** - Yes → **BootConfigService** (process-level flags, Chromium switches, data directory) - No → Continue to #2 2. **Can this data be regenerated or lost without affecting the user?** - Yes → **CacheService** - No → Continue to #3 3. **Is this a user-configurable setting that affects app behavior?** - Yes → Does it have a fixed key and stable value structure? - Yes → **PreferenceService** - No (structure changes often) → **DataApiService** - No → Continue to #4 4. **Is this business data created/accumulated through user activity?** - Yes → **DataApiService** - No → Continue to #5 5. **Is this an internal marker the app writes for itself to stay consistent across restarts (migration / seeding / one-time setup state)?** - Yes → **`app_state` table** (main-process; see [App State Overview](./app-state-overview.md)) - No → Reconsider #2 (most data falls into one of these categories) --- ## System Characteristics ### BootConfigService - Early Boot Configuration Use BootConfigService when: - Setting must be loaded **synchronously before the lifecycle system takes over** - Setting affects **process-level behavior** that cannot change at runtime (Chromium flags, data directory) - Setting **cannot wait** for database initialization **Key characteristics**: - Synchronous file-based loading (`boot-config.json`) - Minimal key set — only process-level configuration - Accessed through PreferenceService (`BootConfig.*` prefix) after lifecycle starts ```typescript // Early boot (src/main/index.ts) — direct access, only option at this stage import { bootConfigService } from '@main/data/bootConfig' if (bootConfigService.get('app.disable_hardware_acceleration')) { app.disableHardwareAcceleration() } // Renderer / lifecycle services — via PreferenceService (standard access) const [disableHwAccel, setDisableHwAccel] = usePreference('BootConfig.app.disable_hardware_acceleration') ``` ### CacheService - Runtime & Cache Data Use CacheService when: - Data can be **regenerated or lost without user impact** - No backup or cross-device synchronization needed - Lifecycle is tied to component, window, or app session - You need other main-process services to react to cache changes (`subscribeChange` / `subscribeSharedChange`) **Two sub-categories**: 1. **Performance cache**: Computed results, API responses, expensive calculations 2. **UI state cache**: Temporary settings, scroll positions, panel states **Three tiers based on persistence needs**: - `useCache` (memory): Lost on app restart, per-renderer (no cross-window sync) - `useSharedCache` (shared): Cross-window sharing via Main; lost on restart - `usePersistCache` (persist): Survives app restart. Renderer persists to `localStorage` (renderer-authoritative); Main persists to its own JSON file (main-authoritative, via `getPersist` / `setPersist` / `hasPersist`). The two stores are independent; Main also relays renderer persist sync between windows. ```typescript // Good: Temporary computed results const [searchResults, setSearchResults] = useCache('search.results', []) // Good: UI state that can be lost const [sidebarCollapsed, setSidebarCollapsed] = useSharedCache('ui.sidebar.collapsed', false) // Good: Recent items (nice to have, not critical) const [recentSearches, setRecentSearches] = usePersistCache('search.recent', []) ``` ### PreferenceService - User Preferences Use PreferenceService when: - Data is a **user-modifiable setting that affects app behavior** - Structure is key-value with **predefined keys** (users modify values, not keys) - **Value structure is stable** (won't change frequently) - Data loss has **low impact** (user can reconfigure) **Key characteristics**: - Auto-syncs across all windows - Each preference item should be **atomic** (one setting = one key) - Values are typically: boolean, string, number, or simple array/object ```typescript // Good: App behavior settings const [theme, setTheme] = usePreference('app.theme.mode') const [language, setLanguage] = usePreference('app.language') const [fontSize, setFontSize] = usePreference('chat.message.font_size') // Good: Feature toggles const [showTimestamp, setShowTimestamp] = usePreference('chat.display.show_timestamp') ``` ### DataApiService - User Data Use DataApiService when: - Data is **business data accumulated through user activity** - Data is **structured with dedicated schemas/tables** - Users can **create, delete, modify records** (no fixed limit) - Data loss would be **severe and irreplaceable** - Data volume can be **large** (potentially GBs) **Key characteristics**: - No automatic window sync (fetch on demand for fresh data) - May contain sensitive data (encryption consideration) - Requires proper CRUD operations and transactions ```typescript // Good: User-generated business data const { data: topics } = useQuery('/topics') const { trigger: createTopic } = useMutation('/topics', 'POST') // Good: Conversation history (irreplaceable) const { data: messages } = useQuery('/messages', { query: { topicId } }) // Good: User files and knowledge base const { data: files } = useQuery('/files') ``` ### `app_state` Table - Internal Continuity Markers Use the `app_state` table when: - Data is an **internal marker the app writes for itself**, not a user-facing setting - It **must survive restarts**, and losing it would make the user **re-experience a one-time flow** (re-run migration, re-seed, repeat setup) - It is needed at **app startup** — current consumers run at or before the lifecycle's earliest phase **Key characteristics**: - Main-process only; **no dedicated service** — the owner reads/writes the table via its own DB handle - One owner per key; keys namespaced `:`; **no cross-domain reads** See [App State Overview](./app-state-overview.md) for full rules and the key registry. --- ## Common Anti-patterns | Wrong Choice | Why It's Wrong | Correct Choice | | ------------------------------------------------- | ------------------------------------------------ | ------------------------------- | | Storing AI provider configs in Cache | User loses configured providers on restart | **PreferenceService** | | Storing conversation history in Preferences | Unbounded growth, complex structure | **DataApiService** | | Storing topic list in Preferences | User-created records, can grow large | **DataApiService** | | Storing theme/language in DataApi | Overkill for simple key-value settings | **PreferenceService** | | Storing API responses in DataApi | Regenerable data, doesn't need persistence | **CacheService** | | Storing window positions in Preferences | Can be lost without impact | **CacheService** (persist tier) | | Storing hardware acceleration flag in Preferences | Too late — must load before lifecycle takes over | **BootConfigService** | | Storing user theme in BootConfig | Doesn't need early boot loading | **PreferenceService** | | Using DataApi for window/process control | No database backing, pure side effects, retry is harmful | **IPC handler** | | Using DataApi for external service calls | Side effects, no CRUD semantics, timeout mismatch | **IPC handler** | | Using DataApi to wrap existing IPC calls | Adds indirection without value, confuses layering | **Keep as IPC** | | Side effects bundled into a DataApi write | Data business-logic layer only — side effects must not ride along, however deeply nested | **IPC handler** (+ Entity Service for DB part) | | Storing migration/seed state in Cache | Lost on restart → user re-runs a one-time flow | **`app_state` table** | ## Edge Cases - **Recently used items** (e.g., recent files, recent searches): Use `usePersistCache` - nice to have but not critical if lost - **Draft content** (e.g., unsaved message): Use `useSharedCache` for cross-window, consider auto-save to DataApi for recovery - **Computed statistics**: Use `useCache` with TTL - regenerate when expired - **User-created templates/presets**: Use **DataApiService** - user-generated content that can grow --- ## Architecture Overview ``` ┌─────────────────┐ │ React Components│ └─────────┬───────┘ │ ┌─────────▼───────┐ │ React Hooks │ ← useDataApi, usePreference('...'), └─────────┬───────┘ usePreference('BootConfig.*'), useCache │ ┌─────────▼───────┐ │ Services │ ← DataApiService, PreferenceService, CacheService └─────────┬───────┘ │ ┌─────────▼───────┐ │ IPC Layer │ ← Main Process Communication └────┬────────┬───┘ │ │ ┌────────────────────▼─┐ ┌───▼──────────────────────┐ │ PreferenceService │ │ Other Main Services │ │ (routes BootConfig.* │ │ (DataApi, Cache, etc.) │ │ to bootConfigService│ └──────────────────────────┘ │ for boot config keys│ └──────────┬───────────┘ │ ┌───────────────▼─────────────┐ │ BootConfigService │ │ (sync load, ~/.cherrystudio/ │ │ boot-config.json — also used directly │ │ in early boot before lifecycle) │ └─────────────────────────────────────────┘ ``` ## Related Source Code ### Type Definitions - `src/shared/data/api/` - API type system - `src/shared/data/bootConfig/` - Boot config type definitions and schemas - `src/shared/data/cache/` - Cache type definitions and schemas (`cacheSchemas.ts`, `cacheTypes.ts`, `cacheValueTypes.ts`, `templateKey.ts`) - `src/shared/data/preference/` - Preference type definitions ### Main Process Implementation - `src/main/data/bootConfig/` - Boot config service - `src/main/data/api/` - API server and handlers - `src/main/data/CacheService.ts` - Cache service - `src/main/data/PreferenceService.ts` - Preference service (also routes `BootConfig.*` keys) - `src/main/data/db/` - Database schemas ### Renderer Process Implementation - `src/renderer/data/DataApiService.ts` - API client - `src/renderer/data/CacheService.ts` - Cache service - `src/renderer/data/PreferenceService.ts` - Preference service - `src/renderer/data/hooks/` - React hooks