mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-03 12:27:41 +08:00
Align cache-overview.md and cache-usage.md with the template-key and
subscribe* APIs (sharedCasual was dropped in 3fbc52e05). Extract the
"adding keys" content into a new cache-schema-guide.md aligned with
preference and boot-config schema guides. Lift non-obvious invariants
(isEqual short-circuit, TTL-with-hooks warning, Persist has no delete,
Main-wins convergence, template placeholder rules) into a first-class
Design Invariants section in the overview.
Fix two code-contradicted claims:
- useCache does not accept a TTL options argument (hook signature is
(key, initValue?)).
- Persist is renderer-authoritative; Main only relays IPC and does
not store (CacheService.ts:477-479 is "Reserved, not implemented").
Update peripheral references in the same pass so cross-references stay
coherent: v2-renderer skill, CLAUDE.md, architecture-overview.md,
api-design-guidelines.md (Cache vs DataApi matcher contrast), and the
package READMEs.
Net change: +249 / -579.
8.6 KiB
8.6 KiB
Cache System Overview
Three-tier cache for regenerable data. In-process memory, cross-window shared state, and localStorage-backed persistence.
Scope
Use Cache for data that:
- Can be regenerated or lost without user impact
- Needs no backup or cross-device sync
- Has lifecycle tied to a component, window, or app session
For user settings use Preference; for business data use DataApi.
Tiers
| Tier | Scope | Survives restart | Authority | Use for |
|---|---|---|---|---|
| Memory | Per-process | No | Local to each process | Computed results, API responses |
| Shared | All renderer windows + Main | No | Main (relays + conflict sink) | Cross-window UI state |
| Persist | All renderer windows | Yes (localStorage) | Each renderer (Main relays only) | Recent items, non-critical UI state |
Persist is renderer-only on disk — src/main/data/CacheService.ts:477-479 reserves the interface but does not implement storage; Main only forwards CacheSyncMessage { type: 'persist' } to peers.
Key Types
| Type | Example schema | Call site | Tiers |
|---|---|---|---|
| Fixed | 'app.user.avatar': string |
get('app.user.avatar') |
Memory / Shared / Persist |
| Template | 'scroll.position.${topicId}': number |
get('scroll.position.t42') |
Memory / Shared |
| Casual | (none — type argument only) | getCasual<T>('my.dynamic.key') |
Memory only |
Template keys share one default value across all instances — all web_search.provider.last_used_key.* fall back to ''. Casual keys are blocked at compile time from matching any schema pattern (UseCacheCasualKey in packages/shared/data/cache/cacheSchemas.ts:393).
Design Invariants
Non-obvious rules the code enforces; assume them when designing consumers.
- Same-value write is a no-op. Equality via
lodash.isEqual. No broadcast, no subscriber fire, no hook re-render. (src/main/data/CacheService.tsisEqualguards beforebroadcastSync/ notifier) - TTL-only refresh does not fire subscribers. Updating
expireAton the same value is silent. - Subscribers fire only on explicit writes. Lazy TTL cleanup, the 10-min GC sweep, and
onStopdo not fire. - Hooks + TTL is discouraged.
useCache/useSharedCachelog a warn when the key has TTL (src/renderer/src/data/hooks/useCache.ts:186-192,289-295) — values can expire between renders. - Hooks pin cache entries.
registerHook/unregisterHookrefcount keys;delete/deleteSharedreturnfalsewhile any hook is active. - Persist has no delete. Persist keys are fixed by schema; the API exposes only
getPersist/setPersist/hasPersist. - TTL uses absolute
expireAt(Unix ms). Every process expires the same entry at the same instant, regardless of clock skew in IPC delivery. - Main-wins convergence. All cross-window shared writes are serialized through Main; on window init, Main-priority override applies to conflicts with the renderer's pre-sync copy.
- Re-entrant callbacks are safe. Subscribers may write back into the same key; the
isEqualshort-circuit terminates loops once the value stabilizes. Callback errors are caught and logged without skipping other subscribers. - Template placeholders are runtime-anonymous.
${providerId}and${foo}match identical concrete keys. Dynamic segments match[\w\-]+only — dots, colons, and non-ASCII are rejected (packages/shared/data/cache/templateKey.ts:35-46).
Architecture
┌─────────────────────── Renderer Process ──────────────────────┐
│ useCache / useSharedCache / usePersistCache │
│ │ │
│ ▼ │
│ CacheService (Renderer) │
│ - Memory cache (local) │
│ - Shared cache (local copy; init-synced from Main) │
│ - Persist cache (localStorage, authoritative) │
└──────────────────────────┬────────────────────────────────────┘
│ IPC: Cache_Sync / Cache_GetAllShared
┌──────────────────────────▼────────────────────────────────────┐
│ CacheService (Main) │
│ - Internal cache (Main-only) │
│ - Shared cache (authoritative; relays to all windows) │
│ - Persist: IPC relay only (no Main-side store) │
│ - subscribeChange / subscribeSharedChange for Main services │
└───────────────────────────────────────────────────────────────┘
Process Responsibilities
| Concern | Main | Renderer |
|---|---|---|
| Internal memory cache | Yes (services' own scratch space) | Yes (window-local) |
| Shared cache authority | Yes | Local copy; writes broadcast via IPC to Main |
| Persist cache storage | No (relay only) | Yes (localStorage, debounced 200ms, flush on unload) |
| Init sync for new windows | Serves getAllShared() |
Calls getAllShared() on startup |
subscribeChange / subscribeSharedChange |
Main-only API; template-aware | — |
| Hook refcounting | — | registerHook / unregisterHook |
| GC (10-min sweep of expired) | Yes | — |
API Reference
Renderer
| Method | Tier | Key type |
|---|---|---|
useCache / get / set / has / delete / hasTTL |
Memory | Fixed + Template |
getCasual / setCasual / hasCasual / deleteCasual / hasTTLCasual |
Memory | Dynamic only (schema keys blocked) |
useSharedCache / getShared / setShared / hasShared / deleteShared / hasSharedTTL |
Shared | Fixed + Template |
usePersistCache / getPersist / setPersist / hasPersist |
Persist | Fixed only |
isSharedCacheReady / onSharedCacheReady |
Shared | — |
getStats(includeDetails?: boolean) |
All | — |
Main
| Method | Tier | Key type |
|---|---|---|
get / set / has / delete |
Internal | Free-form string |
getShared / setShared / hasShared / deleteShared |
Shared | Fixed + Template |
subscribeChange<T>(key, cb) |
Internal | Exact key |
subscribeSharedChange<K>(key, cb) |
Shared | Fixed + Template (fires for every matching concrete instance) |
See Also
- Cache Usage — React hooks, direct API, patterns
- Cache Schema Guide — Adding fixed and template keys
- Source:
src/main/data/CacheService.ts,src/renderer/src/data/CacheService.ts,src/renderer/src/data/hooks/useCache.ts,packages/shared/data/cache/