Cherry Studio 共享数据类型层(src/shared/data)完全指南:Data API、Cache、Preference 与 BootConfig 的类型契约
【免费下载链接】cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio
Cherry Studio 的共享数据类型层位于 src/shared/data,是主进程(Main)与渲染进程(Renderer)之间所有数据系统的唯一类型契约来源:Data API 的请求/响应类型、Cache 的三层缓存键体系、Preference 的用户偏好键体系、BootConfig 的进程级启动配置,以及迁移(Migration)与预设目录(Presets)的类型定义都汇聚于此。阅读本文后,你将掌握该目录的完整结构、各子系统的核心类型与导入约定,并能正确判断业务数据该归属哪一套数据系统,以及如何新增 Cache/Preference/API 类型而不破坏编译期校验。
目录结构与设计思想
src/shared/data按数据系统的边界拆分为七个自治子目录,每个子目录只服务于一套数据系统,互不交叉引用(Cache 类型可以引用 API 的 DTO,但反向不允许):
src/shared/data/ ├── api/ # Data API 类型系统 │ ├── types.ts # 核心请求/响应与分页类型 │ ├── paths.ts # 路径模板字面量工具 │ ├── errors.ts # 错误处理(ErrorCode、DataApiError、工厂) │ └── schemas/ # 领域级 API schema(topics、messages、agents……) ├── bootConfig/ # Boot Config schema 与键类型 ├── cache/ # Cache 系统类型定义 │ ├── cacheTypes.ts # 核心缓存类型 │ ├── cacheSchemas.ts # 缓存键 schema(fixed + template) │ ├── cacheValueTypes.ts # 缓存值类型 │ └── templateKey.ts # 模板键匹配工具 ├── migration/ # 跨进程 v2 迁移结果/进度类型 ├── preference/ # Preference 系统类型定义 │ ├── preferenceTypes.ts # 核心偏好类型 │ └── preferenceSchemas.ts # 偏好 schema(生成文件) ├── presets/ # 应用自持的预设目录 └── types/ # 通用共享数据类型其中api/与cache/目录内各自还有__tests__/,例如 templateKey.test.ts 验证模板键匹配,preferenceSchemas.test.ts 验证偏好 schema 的键与默认值完整性——类型层本身也配有单测,保证契约演进不回归。
这套目录划分与 docs/references/data/README.md 描述的四大数据系统一一对应:bootConfig/↔ BootConfigService、cache/↔ CacheService、preference/↔ PreferenceService、api/↔ DataApiService,另有migration/、presets/与通用types/作为补充。
Data API 类型系统(api/)
api/是 Cherry Studio Data API 的类型基础设施,为渲染进程与主进程之间的 IPC 通信提供端到端类型安全。它的内部结构为:
src/shared/data/api/ ├── types.ts # 核心类型(DataRequest、DataResponse、ApiClient)与 schema 工具 ├── paths.ts # 路径模板字面量类型(/items/:id → /items/${string}) ├── errors.ts # ErrorCode 枚举、DataApiError 类、DataApiErrorFactory、可重试配置 └── schemas/ ├── apiSchemas.ts # 通过交叉类型组合全部领域 schema 为 ApiSchemas └── *.ts # 领域级 schema 定义与 DTO基础设施类型的导入约定
api/没有 barrel 文件,基础设施类型必须按模块直接导入。核心请求/响应、分页与查询参数类型在types,错误类型在errors,路径工具在paths:
import type { DataRequest, DataResponse, ApiClient, // 分页类型 OffsetPaginationParams, OffsetPaginationResponse, CursorPaginationParams, CursorPaginationResponse, PaginationResponse, // 查询参数类型 SortParams, SearchParams } from '@shared/data/api/types' // 分页类型守卫同样位于 types import { isOffsetPaginationResponse, isCursorPaginationResponse } from '@shared/data/api/types' import { ErrorCode, DataApiError, DataApiErrorFactory, isDataApiError, toDataApiError } from '@shared/data/api/errors'领域 DTO 则直接从各自的 schema 文件导入:
// Topic 领域 import type { Topic, CreateTopicDto, UpdateTopicDto } from '@shared/data/api/schemas/topics' // Message 领域 import type { Message, CreateMessageDto } from '@shared/data/api/schemas/messages'核心类型:HttpMethod 与成功状态码
types.ts 定义了 API 系统的基础约束。HttpMethod只允许五种方法,这是 schema 编译期校验的合法集合:
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'成功状态码以常量对象形式声明(避免魔法数字),并派生为类型:
export const SuccessStatus = { OK: 200, // 请求成功 CREATED: 201, // 资源创建成功 ACCEPTED: 202, // 异步任务已接受,稍后处理 NO_CONTENT: 204 // 成功且无响应体 } as constHandler 的返回类型HandlerResult<T>支持两种形式:直接返回数据T(自动推断状态码),或返回{ data, status }自定义状态码;isCustomStatusResult类型守卫用于区分二者。
Schema 的编译期校验
types.ts中的EndpointMethodConstraint要求每个端点的定义必须包含response字段,允许可选params、query、body。配合ValidateMethods(只允许合法 HTTP 方法)与ValidateResponses(缺失response时报出精确的编译错误),在schemas/apiSchemas.ts组合点通过AssertValidSchemas完成兜底校验——即使某个 schema 忘了显式调用校验工具,组合时依然会触发 TypeScript 错误。
Schema 文件按“返回实体”组织
schema 文件按被操作或返回实体的领域组织,而不是按 URL 前缀。父资源(:topicId、:providerId)只起到限定作用域的作用,不决定路由归属哪个文件:
| 路由 | 返回实体 | 所在文件 |
|---|---|---|
'/topics/:topicId/messages' | Message | messages.ts |
'/topics/:topicId/tree' | Tree(Message 派生视图) | messages.ts |
'/topics/:id/active-node' | ActiveNodeResponse(Topic 状态) | topics.ts |
当路由的 URL 父资源与返回实体不一致时,以实体为准。当前仓库的schemas/下已包含 30+ 个领域文件,从 topics.ts、messages.ts、agents.ts、providers.ts,到 files.ts、knowledges.ts、translate.ts 等,每个领域文件都配套了 schema 单测(见 schemas/tests)。
分页与查询参数类型
API 系统支持两种分页模式,类型可组合:
| 类型 | 字段 | 适用场景 |
|---|---|---|
OffsetPaginationParams | page?、limit? | 传统页码导航 |
CursorPaginationParams | cursor?、limit? | 无限滚动、实时流;cursor是排他边界,游标项不会返回 |
SortParams | sortBy?、sortOrder? | 排序 |
SearchParams | search? | 文本搜索 |
响应类型方面:OffsetPaginationResponse<T>携带items、total、page;CursorPaginationResponse<T>携带items、nextCursor?;PaginationResponse<T>是两者的联合,需要时可用isOffsetPaginationResponse/isCursorPaginationResponse守卫收窄。更完整的模式选择与游标语义见 数据分页指南,排序规范见 数据排序指南。
错误处理体系
errors.ts 提供类型安全的错误处理与自动可重试判定。推荐通过工厂构造错误:
import { DataApiError, DataApiErrorFactory, ErrorCode, isDataApiError, toDataApiError } from '@shared/data/api/errors' throw DataApiErrorFactory.notFound('Topic', id) throw DataApiErrorFactory.validation({ name: ['Name is required'] }) throw DataApiErrorFactory.timeout('fetch topics', 3000) throw DataApiErrorFactory.database(originalError, 'insert topic')也可以直接用类构造,并利用isRetryable、isClientError(4xx)、isServerError(5xx)等属性进行分支处理。IPC 跨进程传输时,主进程用apiError.toJSON()序列化,渲染进程用DataApiError.fromJSON()还原;未知错误可用toDataApiError(unknownError, 'context')统一转换。
以下错误码被自动判定为可重试:SERVICE_UNAVAILABLE(503)、TIMEOUT(504)、RATE_LIMIT_EXCEEDED(429)、DATABASE_ERROR(500)、INTERNAL_SERVER_ERROR(500)、RESOURCE_LOCKED(423)。
Cache 类型系统(cache/)
cache/为三层缓存架构提供类型支撑,详情见 缓存系统总览。三层分别为:内存(Memory,进程内、重启丢失)、共享(Shared,跨窗口、经 Main 中转、重启丢失)、持久(Persist,重启后保留——渲染进程写入自己的localStorage,Main 写入自己的 JSON 文件)。持久层是两套互相独立的存储,Main 无法读取渲染进程的持久数据,反之亦然;Main 仅负责在窗口之间转发渲染进程的 persist 同步消息。
键类型:Fixed、Template、Casual
| 类型 | 示例 schema | 调用示例 | 可用层级 |
|---|---|---|---|
| Fixed | 'app.user.avatar': string | get('app.user.avatar') | Memory / Shared / Persist |
| Template | 'scroll.position.${topicId}': number | get('scroll.position.t42') | Memory / Shared |
| Casual | (无——仅类型参数) | getCasual<T>('my.dynamic.key') | Memory only |
模板键的所有实例共享同一个默认值——例如所有web_search.provider.last_used_key.*都回退到''。Casual 键在编译期被阻止匹配任何 schema 模式(UseCacheCasualKey定义于 cacheSchemas.ts 第 393 行附近)。
键命名约定
cacheSchemas.ts 头部注释规定了严格的键命名约定,所有 fixed 和 template 键必须符合namespace.sub.key_name格式:
- 至少 2 个由点号(
.)分隔的段; - 每段只能使用小写字母、数字、下划线;
- 正则约束:
/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/; - 模板占位符
${xxx}被视为字面字符串段。
有效示例:'app.path.resources'、'chat.multi_select_mode'、'scroll.position.${topicId}';无效示例:'userAvatar'(缺点号分隔)、'App.user'(不允许大写)、'scroll.position:${id}'(不允许冒号)。该约定由 ESLint 规则data-schema-key/valid-key强制实施。
模板键支持多个占位符:'entity.cache.${type}_${id}'可匹配'entity.cache.user_456',并在调用处推断出对应值类型。
模板键的运行时机制
templateKey.ts 是模板键的运行时实现。isTemplateKey判断键是否含${...};templateToRegex将模板模式转换为正则:每个${variable}占位符展开为([\w\-]+)——只接受 ASCII 单词字符与连字符,点号、冒号和非 ASCII 字符会被拒绝,这使订阅层与data-schema-key/valid-keyESLint 规则保持对齐,占位符变量名在运行时被忽略;findMatchingSharedCacheSchemaKey则根据具体键反查匹配的 schema 键(fixed 或 template 模式),用于查找模板的默认值。对应测试见 templateKey.test.ts。
Preference 类型系统(preference/)
preference/为偏好系统提供类型支撑,详情见 偏好系统总览。核心契约如下:
- DB 支撑的键与默认值在 preferenceSchemas.ts 中生成(该文件是生成产物,不直接手工编辑);
PreferenceKeyType覆盖 SQLite 支撑的键;UnifiedPreferenceKeyType还包含以BootConfig.前缀暴露的公共 BootConfig 键;- 每个键都有生成的默认值,因此在渲染进程缓存未命中加载完成前,调用方也能观察到值;
- 键集合由 schema 固定,用户只能改值、不能改键。
在 preferenceTypes.ts 中可以看到类型分层:
/** DB-backed preferences only (stored in SQLite) */ export type PreferenceDefaultScopeType = PreferenceSchemas['default'] export type PreferenceKeyType = keyof PreferenceDefaultScopeType /** Unified type: DB-backed preferences + file-backed boot config (BootConfig.* prefix) */ export type UnifiedPreferenceType = PreferenceDefaultScopeType & BootConfigPreferenceKeys export type UnifiedPreferenceKeyType = keyof UnifiedPreferenceType该文件还定义了各类偏好的值类型:PreferenceUpdateOptions(optimistic布尔,控制渲染进程写是否乐观更新)、ThemeMode(light/dark/system)、LanguageVarious(受限的 UI 语言枚举)、SelectionTriggerMode、SelectionFilterMode、MenuPresentationMode等。
主进程的PreferenceService.resolveKey()负责统一键的路由:普通生成键(如ui.theme_mode)→ SQLite 偏好行;公共BootConfig.app.*键 → 文件支撑的bootConfigService;内部BootConfig.temp.*键 → 在统一偏好边界被拒绝。混合setMultiple会在写入前校验每一条路由,但 BootConfig 写入与 SQLite 事务是两套独立存储,不构成跨存储的原子提交。偏好系统与 Cache、BootConfig 的边界划分详见 数据系统总览。
BootConfig 与 Migration 类型
bootConfig/包含 bootConfigSchemas.ts 与 bootConfigTypes.ts,定义进程级启动配置(如硬件加速、Chromium 开关、数据目录)的键与值类型。BootConfig 必须在生命周期系统接管之前同步加载,键集合刻意保持最小,只容纳进程级配置;生命周期启动后统一通过 PreferenceService 的BootConfig.*前缀访问。migration/下是v2/types.ts,定义跨进程 v2 迁移的结果与进度类型,配合 v2 迁移指南 使用。
Presets:应用自持的预设目录
presets/存放应用自持的预设目录(app-owned preset catalogs),与 DataApi 中用户可增删改的业务数据不同,这些是应用内置、schema 固定的目录数据。当前仓库包含 binaryTools.ts、codeCliTools.ts、webSearchProviders.ts、mcpServers.ts、miniApps.ts、localModel.ts、translateLanguages.ts 等,每个预设文件都配有 schema 测试(见 presets/tests),保证预设数据与 schema 契约一致。
通用共享类型(types/)
types/存放不专属于任何单一数据系统的领域实体类型,按领域拆分为 30+ 个文件:topic.ts、message.ts、agent.ts、assistant.ts、provider.ts、model.ts、file.ts、knowledge.ts、group.ts、prompt.ts、tag.ts、pin.ts、note.ts、translate.ts、trace.ts、aiUsageRecord.ts、webSearch.ts、channel.ts、miniApp.ts、painting.ts等。API schema 中的 DTO 与实体类型引用这些共享类型(例如cacheSchemas.ts就引用了@shared/data/types/channel的ChannelStatus与@shared/data/types/miniApp的MiniAppRegion),形成“通用实体类型 → 领域 schema → 组合 ApiSchemas”的引用链。每个领域类型同样配有单测,如 message.test.ts、fileEntry.test.ts 等。
快速参考:导入约定汇总
来自 src/shared/data/README.md 的官方导入约定,可在日常开发中直接套用:
// API 基础设施类型(直接导入模块;api/ 没有 barrel) import type { DataRequest, DataResponse, ApiClient } from '@shared/data/api/types' import { ErrorCode, DataApiError, DataApiErrorFactory } from '@shared/data/api/errors' // 领域 DTO(来自 schema 文件) import type { CreateTopicDto } from '@shared/data/api/schemas/topics' import type { Topic } from '@shared/data/types/topic' // Cache 类型 import type { SharedCacheKey, UseCacheKey } from '@shared/data/cache/cacheSchemas' // Preference 类型 import type { PreferenceKeyType } from '@shared/data/preference/preferenceTypes'@shared/data/*路径别名由项目的 TypeScript 配置统一解析(见 tsconfig.json),主进程、渲染进程与 preload 均可使用。
如何选择数据系统:类型层背后的决策指南
src/shared/data的目录划分本质上回答了一个架构问题:新数据应该放进哪套系统?数据系统总览 给出了决策表与决策流程,可按顺序自问:
- 该设置必须在生命周期系统接管之前加载吗?是 → BootConfigService(进程级开关、Chromium 参数、数据目录);
- 该数据丢失后能否重新生成、且不影响用户?是 → CacheService(三层缓存任选);
- 这是影响应用行为的用户可配置设置吗?是且键固定、值结构稳定 → PreferenceService;结构频繁变动 → DataApiService;
- 这是用户活动产生的业务数据吗?是 → DataApiService(数据丢失影响严重、量级可达 GB);
- 这是防止迁移/播种/对账工作重启后重复执行的属主私有标记吗?是 →
app_state表(仅主进程,每键单一属主,键按<scope>:<name>命名)。
官方还明确列出常见反模式,例如:把 AI 提供商配置放进 Cache(重启即丢)应改 Preference;把会话历史放进 Preference(无界增长、结构复杂)应改 DataApi;把窗口位置放进 Preference 应改 Cache 的 persist 层;把硬件加速开关放进 Preference 则为时已晚,必须用 BootConfig。
源码阅读路径
若想深入类型层背后的实现,可以按以下路径阅读:
- 类型定义:
src/shared/data/api/、src/shared/data/cache/、src/shared/data/preference/、src/shared/data/bootConfig/ - 主进程实现:bootConfig 服务、API 服务器与 handler、CacheService.ts、PreferenceService.ts、数据库 schema
- 渲染进程实现:DataApiService.ts、CacheService.ts、PreferenceService.ts、React hooks 目录
- 配套文档:数据系统参考(入口)、缓存 schema 指南、偏好 schema 指南、BootConfig schema 指南、API 设计指南、测试 Mocks
这套共享类型层是整个 Cherry Studio 数据架构的“契约中心”:只要在src/shared/data中正确地定义类型,主进程的 handler 实现、渲染进程的 hooks 调用与 IPC 序列化就能获得端到端的编译期保证,这也是仓库在 docs/references/data 中沉淀大量 schema 指南的根本原因。
【免费下载链接】cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考