A2 · Cordis 内核五概念:Context / Service / Event / Effect / Inject
developer preview :API 可能变更。Cordis 以 vendored 形式位于
vendor/cordis,本文路径以该目录为准。
1. 五个核心概念速览
dsh 的底层框架是 Cordis。理解以下五个概念,就理解了 dsh 的扩展机制:
#mermaid-svg-7wJBp3OtRB8ClvwZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7wJBp3OtRB8ClvwZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7wJBp3OtRB8ClvwZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7wJBp3OtRB8ClvwZ .error-icon{fill:#552222;}#mermaid-svg-7wJBp3OtRB8ClvwZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7wJBp3OtRB8ClvwZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7wJBp3OtRB8ClvwZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7wJBp3OtRB8ClvwZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7wJBp3OtRB8ClvwZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7wJBp3OtRB8ClvwZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7wJBp3OtRB8ClvwZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7wJBp3OtRB8ClvwZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7wJBp3OtRB8ClvwZ .marker.cross{stroke:#333333;}#mermaid-svg-7wJBp3OtRB8ClvwZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7wJBp3OtRB8ClvwZ p{margin:0;}#mermaid-svg-7wJBp3OtRB8ClvwZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7wJBp3OtRB8ClvwZ .cluster-label text{fill:#333;}#mermaid-svg-7wJBp3OtRB8ClvwZ .cluster-label span{color:#333;}#mermaid-svg-7wJBp3OtRB8ClvwZ .cluster-label span p{background-color:transparent;}#mermaid-svg-7wJBp3OtRB8ClvwZ .label text,#mermaid-svg-7wJBp3OtRB8ClvwZ span{fill:#333;color:#333;}#mermaid-svg-7wJBp3OtRB8ClvwZ .node rect,#mermaid-svg-7wJBp3OtRB8ClvwZ .node circle,#mermaid-svg-7wJBp3OtRB8ClvwZ .node ellipse,#mermaid-svg-7wJBp3OtRB8ClvwZ .node polygon,#mermaid-svg-7wJBp3OtRB8ClvwZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7wJBp3OtRB8ClvwZ .rough-node .label text,#mermaid-svg-7wJBp3OtRB8ClvwZ .node .label text,#mermaid-svg-7wJBp3OtRB8ClvwZ .image-shape .label,#mermaid-svg-7wJBp3OtRB8ClvwZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-7wJBp3OtRB8ClvwZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7wJBp3OtRB8ClvwZ .rough-node .label,#mermaid-svg-7wJBp3OtRB8ClvwZ .node .label,#mermaid-svg-7wJBp3OtRB8ClvwZ .image-shape .label,#mermaid-svg-7wJBp3OtRB8ClvwZ .icon-shape .label{text-align:center;}#mermaid-svg-7wJBp3OtRB8ClvwZ .node.clickable{cursor:pointer;}#mermaid-svg-7wJBp3OtRB8ClvwZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7wJBp3OtRB8ClvwZ .arrowheadPath{fill:#333333;}#mermaid-svg-7wJBp3OtRB8ClvwZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7wJBp3OtRB8ClvwZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7wJBp3OtRB8ClvwZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7wJBp3OtRB8ClvwZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7wJBp3OtRB8ClvwZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7wJBp3OtRB8ClvwZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7wJBp3OtRB8ClvwZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7wJBp3OtRB8ClvwZ .cluster text{fill:#333;}#mermaid-svg-7wJBp3OtRB8ClvwZ .cluster span{color:#333;}#mermaid-svg-7wJBp3OtRB8ClvwZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7wJBp3OtRB8ClvwZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7wJBp3OtRB8ClvwZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-7wJBp3OtRB8ClvwZ .icon-shape,#mermaid-svg-7wJBp3OtRB8ClvwZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7wJBp3OtRB8ClvwZ .icon-shape p,#mermaid-svg-7wJBp3OtRB8ClvwZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7wJBp3OtRB8ClvwZ .icon-shape .label rect,#mermaid-svg-7wJBp3OtRB8ClvwZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7wJBp3OtRB8ClvwZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7wJBp3OtRB8ClvwZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7wJBp3OtRB8ClvwZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Cordis 内核五概念
declare module
等就绪再加载
emit/waterfall/...
teardown 自动撤销
通过 effect
Context
服务容器 ctx
Service
占据 ctx.key
Inject
依赖等待
Event
类型化通信
Effect
可逆注册
Plugin
apply(ctx)
- 插件(Plugin) :实现
Service的对象,或带apply(ctx)的函数。 - 上下文(Context) :服务的容器,服务占据稳定的
ctx.<key>。 - 注入(Inject):插件声明依赖的服务,等待其就绪才启动。
- 事件(Event):类型化通信通道,按模式分发(详见 A3)。
- 副作用(Effect):可逆注册,reload / teardown 时自动撤销。
2. Context:服务容器
Context 是 Cordis 的核心代理对象。服务通过 declare module 合并把自身挂到 ctx 上,其他插件按 key 查找而非 import 具体实现。
ts
// vendor/cordis/src/context.ts
export class Context {
static readonly effect: unique symbol = symbols.effect
static readonly isolate: unique symbol = symbols.isolate
static readonly intercept: unique symbol = symbols.intercept
extend(meta = {}): this { /* ... */ }
isolate(name: string, label?: symbol) { /* ... */ }
intercept<K extends InjectKey>(name: K, config: any): this
}
3. Service:占用一个 ctx key
每个服务是一个 Service 子类,构造时向 ctx.reflect.provide(name, self) 注册自身,占据稳定 key。
ts
// vendor/cordis/src/service.ts
export abstract class Service<out T = never> {
static readonly init: unique symbol = symbols.init
static readonly check: unique symbol = symbols.check
constructor(protected ctx: Context, name: string) {
name ??= this.constructor['provide'] as string
// ...
self.ctx.reflect.provide(name, self, this[symbols.check])
}
}
4. Inject:声明依赖,表达加载顺序
插件用 inject 声明所需服务;Cordis 在依赖就绪后才挂载它,加载顺序由依赖关系表达,而非手动编排。
ts
// vendor/cordis/src/registry.ts
export function Inject<K extends InjectKey>(name: K, config?: ...) { /* 装饰器 */ }
export class RegistryService {
inject(inject: Inject, callback: Plugin.Function<void>) {
return this.plugin({ inject, apply: callback, name: callback.name })
}
}
5. Event:类型化、带模式分发
事件通过 TS 声明合并注册,再以 emit / parallel / serial / waterfall / bail 分发(详见 A3)。
6. Effect:可逆的副作用
所有注册(监听、工具、定时器)都通过 ctx.effect() 或 ctx.on() 安装。一个 effect 返回 disposer,teardown 时按相反顺序执行。
ts
// vendor/cordis/src/fiber.ts
export const enum FiberState { PENDING, LOADING, ACTIVE, FAILED, DISPOSED, UNLOADING }
export class Fiber {
effect(execute: () => Effect, label = 'anonymous'): any { /* 返回 disposer */ }
}
7. 可运行示例:最小插件
ts
// scratch-plugin/src/hello.ts
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export function apply(ctx: Context) {
ctx.effect(() => {
const timer = setInterval(() => console.log('heartbeat'), 5000)
return () => clearInterval(timer) // 卸载时自动清理
})
}
加载方式见 A4(通过 --patch)。
8. 核心术语中英对照
| 中文 | 英文 | 说明 |
|---|---|---|
| 上下文 | Context | 服务容器 |
| 服务 | Service | 占用 ctx.<key> 的单例 |
| 注入 | Inject | 声明服务依赖 |
| 事件 | Event | 类型化通信通道 |
| 副作用 | Effect | 可逆注册,返回 disposer |
| 纤维 | Fiber | 插件运行时实例 |
9. 官方文档 vs 源码 对照表
| 主题 | 官方文档 | 精确源码路径 |
|---|---|---|
| Cordis 入门 | reference/cordis-primer |
docs/cordis-primer.md |
| Context 类 | --- | vendor/cordis/src/context.ts |
| Service 基类 | --- | vendor/cordis/src/service.ts |
| 插件加载 / 注入 | --- | vendor/cordis/src/registry.ts |
| Effect 生命周期 | --- | vendor/cordis/src/fiber.ts |
10. 一句话小结
Cordis 把「能力」抽象为「占据 ctx key 的服务 + 类型化事件 + 可逆 effect」,dsh 的全部可替换性都建立在这三件套之上。