
Cherry Studioshared跨进程基础层架构两大不变量、封闭顶层目录集与放置决策实战手册【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300 assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio本文基于 Cherry Studio 仓库的官方架构参考文档 docs/references/architecture/shared-layer.md系统讲解src/shared别名shared这一跨进程基础层的设计规则什么代码可以进入该层、顶层目录为何只有五个、以及如何用两道判定闸门完成新模块的放置决策。读完后你将掌握在 Electron 多进程架构中组织跨进程类型与纯逻辑的完整方法论并能依据 ESLint、TypeScript 路径别名等仓库实际约束验证每一条规则。shared的定位不依赖任何应用代码的跨进程基础层shared是 Cherry Studio 架构中的跨进程基础原语层对应 Renderer 架构文档 中的 Layer 4。它的核心属性有两条不依赖任何 app 代码可被main、renderer、preload三个进程层共同导入。这一别名映射在仓库的构建与工具链配置中有完整体现shared/*统一指向./src/shared/*tsconfig.json 第 18 行shared/*: [./src/shared/*]tsconfig.web.json渲染进程侧与 tsconfig.node.json主进程侧同样定义了该别名说明该层确实被两套独立的编译目标共同引用vitest.config.ts 与 electron.vite.config.ts 中也为各构建/测试目标注册了同一别名保证测试与打包时的解析一致。边界规则并非只靠约定。eslint.config.mjs 中的BAN_RENDERER_FROM_MAIN规则直接禁止主/preload 进程导入渲染进程代码并给出指向本文档的提示信息Main/preload must not import renderer code. Use shared for cross-process types, or src/main for main-only types. See docs/references/architecture/shared-layer.md.此外eslint.config.mjs 中还有一条针对shared/ipc/schemas的专项限制渲染进程只允许import type该目录——因为值导入会把整个 Zod schema 集合拖进渲染进程 bundle。这条规则说明即使是shared内部的子模块也有各自的进程侧使用约束。不变量一必须真正跨进程含 Cache 注册表这一唯一豁免一个模块类型也包括在内要进入shared必须同时被main和renderer实际使用。文档对这一不变量给出三条推论为什么shared是跨进程边界的单一事实来源single source of truth单进程代码本来就有自己的家。只能从一个进程到达的代码应放在该进程自己的层src/main/*或src/renderer/{utils,hooks,services}。禁止投机性放置如果某段代码将来可能变成跨进程应先写在main/renderer里等它真正跨进程后再迁移。最常见的失败模式就是以防万一添加的类型或工具之后从未被跨进程使用沦为死代码。唯一豁免Cache schema 注册表Cache 子系统是不变量一的唯一例外。每一个 Cache 的 key schema 及其值类型无论由哪个进程消费都必须放在shared/data/cache/cacheSchemas.tscacheValueTypes.ts中——即使是纯渲染进程专用的类型如Tab、ChatScrollAnchor、AgentOpenExternalAppTarget等也是如此。渲染进程专用的 cache 值类型放在这里属于合规而非违反不变量一不应被标记或迁移。该豁免仅适用于 Cache 子系统其他位置不变量一照常生效。仓库现状与该规则一致src/shared/data/cache/ 目录下确实包含cacheSchemas.ts、cacheValueTypes.ts、cacheTypes.ts与templateKey.ts构成整个应用跨进程共享的缓存契约。不变量二不导出可变运行时状态shared只导出类型、纯函数和不可变数据不导出任何类实例单例service / manager / registry也不导出持有可变运行时状态的东西。为什么main和renderer是相互隔离的 V8 realm一个shared模块每个进程各加载一次。所谓共享单例是一个虚构——它会悄悄变成每进程 N 个各自漂移的实例。可变状态没有合理的共享所有者它属于其生命周期与上下文所在的那个进程。new不是判据——运行时可变性 身份identity才是。允许使用new构建构建一次、随后冻结、永不变更的不可变数据例如从静态数据一次性构建的Map/Set/RegExp查找表。仓库中的实际例子src/shared/utils/command/definitions.ts 中私有非导出的commandMap就是一个new MapCommandId, ...查找表。有状态类只从shared发布定义blueprint实例在各进程内创建。文档给出的例子是ContextKeyService类定义位于跨进程层而new ContextKeyService()发生在渲染进程侧的CommandContextKeyProvider中。文档用如下表格明确了允许与禁止清单允许禁止type/interface/enum、schema 派生类型export const x new XService()任何导出的实例单例纯函数、谓词、转换器任何 registry / manager / service 实例不可变数据——常量、定义、通过new Map/Set构建的冻结查找表任何模块级持有可变运行时状态的值有状态类的定义blueprint此类的一个活动实例封闭的顶层目录集只有五个且不可扩展shared的顶层是一个封闭集合——这是 命名规范 §4.8顶层目录默认封闭在shared上的具体应用。当前src/shared/下恰好只有以下五个目录外加一个待退化的根级文件见 § 目标态与现状目录类别为什么配得上一个顶层位置ai核心领域Cherry Studio 本身就是一个 AI 产品AI 的跨进程契约与纯逻辑是一等公民镜像src/main/ai/。只承载 AI 的跨进程切片——不含 AI UI也不含各进程私有服务data跨进程基础设施数据层的跨进程契约API 实体/请求类型、cache/preference/bootConfig schema、迁移映射、预置。类似框架领域无关ipc跨进程基础设施IpcApi 框架路由define辅助、请求 事件 schema、错误模型与共享类型IpcContext、WindowId。领域无关types形状桶没有单一所有者的跨进程类型声明utils形状桶跨进程纯逻辑以及其支撑常量与类 blueprint治理规则新能力永远不能获得新的顶层目录。它只能是a核心领域仅ai、b真正的跨进程基础设施或c按形状分解进types/utils。其余一律 →types/utils。命名遵循命名规范 §4.9ai/data/ipc是单数命名空间types/utils是复数桶。仓库现状可逐一印证各目录的人格src/shared/ai/agentSession*系列上下文压缩、能力协商、斜杠命令等、transport/流式轮次状态、审批决策落盘、tools/MCP 工具名与来源策略、claudecode/内置工具注册表全部是跨进程契约与纯逻辑无 UI、无单例src/shared/data/api/含schemas/下按实体拆分的请求/响应 schema、bootConfig/、preference/、presets/内置 MCP Server、Web 搜索供应商等预置数据、types/、cache/src/shared/ipc/define.ts路由定义辅助、types.tsIpcContext等、schemas/按领域拆分的 Zod 请求/事件 schema、errors/按领域拆分的 IpcError 模型。形状二分types与utilsshared只有两个形状桶。因为没有 UI、没有 React、没有进程私有运行时渲染进程那套丰富的形状components/hooks/services/pages在此坍缩为声明 vs 纯逻辑types/utils/类型别名、接口、枚举、schema 派生类型纯函数、谓词、转换器外加类型所需的小常量外加这些函数所需的常量/静态数据以及有状态类的 blueprint两桶之间的路由遵循 命名规范 §5.2 的按形状路由表utils/导入types/是被允许的方向utils → types。单文件 vs 子目录以及 barrel 规则Barrel 的总体规则以 命名规范 §6.4 为准shared的特有规则是单个.ts文件是默认形态。多数主题就是一个文件——types/topic.ts、utils/topic.ts直接导入。只有当主题确实拥有多个文件时才升级为子目录命名规范 §4.4绝不预创建。仓库现状如此src/shared/utils/ 下text.ts、url.ts、model.ts等大量单文件主题与file/、command/、api/这类多文件子目录并存。主题子目录有且只有一个index.ts作为公共 API——显式命名导出禁止export *。这样无论主题是文件还是子目录导入面完全一致都是shared/utils/topic子目录的其余文件保持私有。桶根types/与utils/没有index.ts。桶是分类而非模块——根 barrel 把每个文件都再导出一次换不来任何聚合 API却会在每次新增文件时引入改动面和循环导入风险。导入具体文件或主题永远不导入桶。types/不放运行时测试。声明桶没有可测的运行时行为因此types/下的行为测试expect(fn(...))…是在发出信号这个文件里装着逻辑——谓词、类型守卫、转换器、工厂或函数——它应属于utils/按形状路由。把逻辑迁到utils/topic.ts从types/导入所需类型utils → types是合法方向测试随之迁移。类型守卫x is T也是运行时谓词——与逻辑同放utils/而不是与接口同放types/。由验证函数构建的 schemaz.custom(isFoo)跟随函数进入utils/纯声明式 schemaz.object({…})可以留在types/。types/里唯一合法的测试是类型级测试expectTypeOf/assertType它断言的是类型契约本身没有可迁移的运行时。但它是过渡性守卫——只有当手写类型仍是事实来源时才有其位置一旦运行时 schemaZod / IpcApi接管契约、类型变为z.infer派生schema 自身的校验就覆盖了这个断言类型级测试随该迁移一并退役。常量与静态数据默认常量住在它所属领域/主题的单文件里与服务的逻辑相邻AI 模型默认值 →ai/文件类型列表 →utils/file/。utils/constants.ts不是桶。它只承载真正全局、跨进程的残余常量KB/MB/GB、APP_NAME。只有 100% 确定某常量是应用全局且横切时才加入只要它属于任何领域就进该领域的文件。为什么这正是旧版config/constant.ts缺失的护栏——它因此长成了一个拥有 82 个导入方的杂物抽屉现已解散见下文。单进程常量 → 留在shared之外不变量一。没有config/桶。常量是数据领域文件或utils/里的冻结值能表达一个config/目录所能表达的一切且不会引来无关的全局变量。有状态类 blueprint有状态类的定义是纯代码因此随其主题模块住在utils/下。仓库中的先例src/shared/utils/blacklistMatchPattern.ts 中有状态的MatchPatternMap类——它管理黑名单匹配规则的内部allURLs数组与hostMap但只以类定义形式从shared导出由各进程自行实例化。shared没有services/桶因为 service 是进程私有的不变量二。放置决策流程两道闸门 归类按顺序执行两道闸门然后归类跨进程吗两个进程都到达它——否 → 去进程层src/main/*或src/renderer/*。豁免Cache key 的 schema 条目 值类型即使单进程使用也留在shared/data/cache/。无状态/不可变吗没有导出实例、没有可变状态——否 → 只有 blueprint 与静态数据留下实例留在各进程。归类核心领域ai/ 基础设施data、ipc/ 形状types、utils。不属于前两类 → 按形状分解进types/utils永远不新开顶层目录。反模式清单反模式说明违反的规则导出实例单例export const x new XService()或任何 registry / manager / service 实例不变量二shared中的单进程代码图方便把 main-only 或 renderer-only 逻辑放进来不变量一旧版config/constant.ts曾是震中Cache schema 注册表是唯一受认可的例外杂物抽屉文件或目录跨领域、跨进程积累无关全局的config/桶或constant.ts应按领域 进程分解不能整体搬迁了事封闭顶层集 形状分解每个能力新开顶层目录顶层已封闭任何能力都按形状分解§2 封闭集shared中的有状态 service状态没有合理的共享所有者属于main或renderer不变量二目标态与现状顶层分解已达到目标态src/shared/当前只包含ai/、data/、ipc/、types/、utils/五个目录。文档同时记录了剩余已知差距区域现状目标data/types/中的转换器/守卫——coerceSearchRole、deriveRootSpanId、readCherryMeta/withCherryMeta、knowledge.ts字符串辅助逻辑住在data的类型桶里data/types/__tests__/下的行为测试暴露了这一点§3.1 末段开放问题按形状路由应把它们迁到utils位置但 schema 派生守卫按惯例与 schema 同放——决定暂缓根级IpcChannel.tsv1 通道枚举仍位于根目录被遗留领域与数据/IpcApi 传输通道使用逐领域退役遗留条目之后把剩余的基础设施通道迁入ipc/从源码结构看根级 IpcChannel.ts 的存在与文档描述一致——它是当前顶层封闭集之外唯一可见的根级文件也解释了为什么文档将其单列为待迁移项而非违规项。规则如何在工具链中落地shared的边界不是纸面约定仓库在多层做了可执行的验证导入禁令eslint.config.mjs 禁止 main/preload 导入渲染进程代码提示语直接指向本文档shared/ipc/schemas对渲染进程仅限import typeeslint.config.mjs。路径别名tsconfig.json/tsconfig.web.json/tsconfig.node.json/vitest.config.ts/electron.vite.config.ts五处统一将shared/*映射到src/shared/*保证类型检查、测试与打包解析一致。测试形态types/桶仅存声明行为测试会被规则判定为应迁移到utils/utils/桶下的行为测试大量存在如 src/shared/utils/tests/ 中 21 个测试文件、src/shared/data/types/tests/ 中 15 个测试文件构成对声明 vs 逻辑分界的持续验证。相关文档架构总览——进程模型与shared的一句话总结Renderer 架构 §2–§3——分层模型及渲染进程如何依赖shared其 §6 拥有 command 的渲染进程侧单元格本文档拥有其shared侧单元格命名规范 §4.8 / §4.9 / §5.2——顶层目录默认封闭本文档是其shared应用、单数 vs 复数命名、按形状路由表。【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300 assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考