Cherry Studio 共享数据类型层(src/shared/data)完全指南:Data API、Cache、Preference 与 BootConfig 的类型契约
2026/9/20 13:48:54 网站建设 项目流程

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 const

Handler 的返回类型HandlerResult<T>支持两种形式:直接返回数据T(自动推断状态码),或返回{ data, status }自定义状态码;isCustomStatusResult类型守卫用于区分二者。

Schema 的编译期校验

types.ts中的EndpointMethodConstraint要求每个端点的定义必须包含response字段,允许可选paramsquerybody。配合ValidateMethods(只允许合法 HTTP 方法)与ValidateResponses(缺失response时报出精确的编译错误),在schemas/apiSchemas.ts组合点通过AssertValidSchemas完成兜底校验——即使某个 schema 忘了显式调用校验工具,组合时依然会触发 TypeScript 错误。

Schema 文件按“返回实体”组织

schema 文件按被操作或返回实体的领域组织,而不是按 URL 前缀。父资源(:topicId:providerId)只起到限定作用域的作用,不决定路由归属哪个文件:

路由返回实体所在文件
'/topics/:topicId/messages'Messagemessages.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 系统支持两种分页模式,类型可组合:

类型字段适用场景
OffsetPaginationParamspage?limit?传统页码导航
CursorPaginationParamscursor?limit?无限滚动、实时流;cursor是排他边界,游标项不会返回
SortParamssortBy?sortOrder?排序
SearchParamssearch?文本搜索

响应类型方面:OffsetPaginationResponse<T>携带itemstotalpageCursorPaginationResponse<T>携带itemsnextCursor?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')

也可以直接用类构造,并利用isRetryableisClientError(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': stringget('app.user.avatar')Memory / Shared / Persist
Template'scroll.position.${topicId}': numberget('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

该文件还定义了各类偏好的值类型:PreferenceUpdateOptionsoptimistic布尔,控制渲染进程写是否乐观更新)、ThemeModelight/dark/system)、LanguageVarious(受限的 UI 语言枚举)、SelectionTriggerModeSelectionFilterModeMenuPresentationMode等。

主进程的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.tsmessage.tsagent.tsassistant.tsprovider.tsmodel.tsfile.tsknowledge.tsgroup.tsprompt.tstag.tspin.tsnote.tstranslate.tstrace.tsaiUsageRecord.tswebSearch.tschannel.tsminiApp.tspainting.ts等。API schema 中的 DTO 与实体类型引用这些共享类型(例如cacheSchemas.ts就引用了@shared/data/types/channelChannelStatus@shared/data/types/miniAppMiniAppRegion),形成“通用实体类型 → 领域 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的目录划分本质上回答了一个架构问题:新数据应该放进哪套系统?数据系统总览 给出了决策表与决策流程,可按顺序自问:

  1. 该设置必须在生命周期系统接管之前加载吗?是 → BootConfigService(进程级开关、Chromium 参数、数据目录);
  2. 该数据丢失后能否重新生成、且不影响用户?是 → CacheService(三层缓存任选);
  3. 这是影响应用行为的用户可配置设置吗?是且键固定、值结构稳定 → PreferenceService;结构频繁变动 → DataApiService;
  4. 这是用户活动产生的业务数据吗?是 → DataApiService(数据丢失影响严重、量级可达 GB);
  5. 这是防止迁移/播种/对账工作重启后重复执行的属主私有标记吗?是 →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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询