Skip to content
源码版本47f9438 (dsh@0.1)

依赖注入与服务

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.check symbol 是 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 装饰器:
  • 作为类装饰器:往静态 inject map 写入
  • 作为方法装饰器:把方法调用挂到 ctx.inject(...) 在服务可用时触发
vendor/cordis/src/registry.ts:71-89Inject.resolve(inject, result) 把 array 形式/对象形式/带原型链继承的 inject 元数据规范化成 plain map,供 Fiber 构造器消费。

RegistryService

vendor/cordis/src/registry.ts:195-210class RegistryService:
ts
_internal = new Map<Function, Plugin.Runtime>()

以 callback 作 identity key,counter 单调递增分配 fiber uid。

plugin() 与 delete()

vendor/cordis/src/registry.ts:316-336plugin(plugin, config, getOuterStack):
ts
plugin(plugin, config, getOuterStack) {
  // resolve → 复用或新建 runtime → new Fiber(...)
  // 把 fiber 包成带 then 的 PromiseLike
}
vendor/cordis/src/registry.ts:258-267delete(plugin):删 runtime 记录并对每个 fiber dispose()——这是卸载入口。

Plugin 的三种 shape

Plugin 有三种合法形态,resolve() 统一抽出 callback 作为 registry identity:

  1. Function — 直接作为 apply 回调
  2. Constructor — class,new 出实例
  3. { 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-302inject(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 对齐。

接下来读什么