依赖注入与服务
Cordis 的依赖注入(DI)不靠构造器参数,而靠"服务名即 key"的 registry 模型。Service 子类在构造时注册自己,RegistryService 把 plugin 转成 callback 作 runtime 记录的 identity key。插件用 inject 字段声明所需服务名,fiber 在服务出现/消失时自动 reload。
Service 基类
vendor/cordis/src/service.ts:11-59 定义了 abstract class Service<T>:ts
constructor(ctx, name) {
ctx.reflect.provide(name, self, this[Service.check])
}- 构造时调
ctx.reflect.provide(name, self, this[Service.check])注册自己 - 若服务带
[Service.invoke]则createCallable让实例可调用(如ctx.logger('name')) Service.checksymbol 是 availability 谓语,provide时传入,reflect 层在ctx.get(strict=true)时调用——只有check()返回 true 的服务才被 inject 系统视为"已可用"
配置合并
vendor/cordis/src/service.ts:86-102 的 [symbols.resolveConfig](base?, head?):ts
[symbols.resolveConfig](base?, head?) {
// 沿 Context.intercept 原型链收集同名 intercept config
// 从根到叶合并,Config.merge 优先,否则 Object.assign
}这就是为什么 ctx.intercept('llm', { provider: 'x' }) 能生效——Service 启动时沿原型链合并所有同名 intercept config。
Registry:plugin 的登记簿
@Inject 装饰器
vendor/cordis/src/registry.ts:37-60 的 @Inject 装饰器:- 作为类装饰器:往静态
injectmap 写入 - 作为方法装饰器:把方法调用挂到
ctx.inject(...)在服务可用时触发
vendor/cordis/src/registry.ts:71-89 的 Inject.resolve(inject, result) 把 array 形式/对象形式/带原型链继承的 inject 元数据规范化成 plain map,供 Fiber 构造器消费。RegistryService
vendor/cordis/src/registry.ts:195-210 的 class RegistryService:ts
_internal = new Map<Function, Plugin.Runtime>()以 callback 作 identity key,counter 单调递增分配 fiber uid。
plugin() 与 delete()
vendor/cordis/src/registry.ts:316-336 的 plugin(plugin, config, getOuterStack):ts
plugin(plugin, config, getOuterStack) {
// resolve → 复用或新建 runtime → new Fiber(...)
// 把 fiber 包成带 then 的 PromiseLike
}vendor/cordis/src/registry.ts:258-267 的 delete(plugin):删 runtime 记录并对每个 fiber dispose()——这是卸载入口。Plugin 的三种 shape
Plugin 有三种合法形态,resolve() 统一抽出 callback 作为 registry identity:
- Function — 直接作为 apply 回调
- Constructor — class,
new出实例 { apply }Object — 带 apply 方法的对象
Runtime:同 callback 共享记录
Plugin.Runtime 是"同一 callback 的所有 fiber 共享记录":name、Config schema、fibers 列表。所以同插件多次 ctx.plugin() 复用 runtime 而非重复注册。这点很重要——它让一个插件在多个 isolate 子树里各有 fiber,但共享 schema 与元数据。
两个关键 trick
1. inject 的语法糖
vendor/cordis/src/registry.ts:300-302 的 inject(inject, callback) 其实是:ts
inject(inject, callback) {
return this.plugin({ inject, apply: callback, name: callback.name })
}callback 在服务可用时执行、服务消失时卸载并重跑。
2. 手动 Symbol.hasInstance
Service[Symbol.hasInstance] 手动实现以穿透 Proxy——普通 instanceof 在 proxy 上失效,这里手动遍历原型链查 constructor。这是 Cordis 处处可见的"双 realm 一致性"主题的一部分:vm 沙箱里的 instanceof 要与 host realm 对齐。