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

Dependency Injection and Services

Cordis's dependency injection (DI) does not rely on constructor arguments; it is a registry model where "the service name is the key". A Service subclass registers itself at construction, RegistryService turns the plugin into a callback used as the identity key for the runtime record, and plugins declare the required service names via the inject field; a fiber auto-reloads when those services appear or disappear.

The Service Base Class

vendor/cordis/src/service.ts:11-59 defines abstract class Service<T>:
ts
constructor(ctx, name) {
  ctx.reflect.provide(name, self, this[Service.check])
}
  • On construction it calls ctx.reflect.provide(name, self, this[Service.check]) to register itself.
  • If the service carries [Service.invoke], createCallable makes the instance callable (e.g. ctx.logger('name')).
  • The Service.check symbol is an availability predicate; it is passed into provide and the reflect layer calls it when ctx.get(strict=true) runs — only services whose check() returns true are considered "available" by the inject system.

Config Merging

[symbols.resolveConfig](base?, head?) at vendor/cordis/src/service.ts:86-102:

ts
[symbols.resolveConfig](base?, head?) {
  // collect same-named intercept configs along the Context.intercept prototype chain
  // merge from root to leaf; Config.merge first, otherwise Object.assign
}

This is why ctx.intercept('llm', { provider: 'x' }) works — when a service starts it merges all same-named intercept configs along the prototype chain.

Registry: The Plugin Ledger

@Inject Decorator

The @Inject decorator at vendor/cordis/src/registry.ts:37-60:

  • As a class decorator: writes into the static inject map.
  • As a method decorator: hooks the method call onto ctx.inject(...) to trigger when the service becomes available.

Inject.resolve(inject, result) at vendor/cordis/src/registry.ts:71-89 normalizes inject metadata — in array form, object form, or inherited through the prototype chain — into a plain map for the Fiber constructor to consume.

RegistryService

class RegistryService at vendor/cordis/src/registry.ts:195-210:

ts
_internal = new Map<Function, Plugin.Runtime>()

It uses callback as the identity key; counter monotonically increments to allocate fiber uids.

plugin() and delete()

plugin(plugin, config, getOuterStack) at vendor/cordis/src/registry.ts:316-336:

ts
plugin(plugin, config, getOuterStack) {
  // resolve → reuse or create runtime → new Fiber(...)
  // wrap the fiber into a PromiseLike with then
}

delete(plugin) at vendor/cordis/src/registry.ts:258-267: removes the runtime record and calls dispose() on each fiber — the unloading entry point.

The Three Plugin Shapes

A Plugin has three legal shapes; resolve() uniformly extracts callback as the registry identity:

  1. Function — used directly as the apply callback.
  2. Constructor — a class, new-ed into an instance.
  3. { apply } Object — an object carrying an apply method.

Runtime: Shared Record per Callback

Plugin.Runtime is "the shared record for all fibers of the same callback": name, Config schema, list of fibers. So multiple ctx.plugin() calls on the same plugin reuse the runtime rather than re-registering. This is important — it lets a plugin have its own fiber in each isolate subtree while sharing schema and metadata.

Two Key Tricks

1. The inject Sugar

inject(inject, callback) at vendor/cordis/src/registry.ts:300-302 is in fact:

ts
inject(inject, callback) {
  return this.plugin({ inject, apply: callback, name: callback.name })
}

The callback executes when the service becomes available, and is unloaded and re-run when the service disappears.

2. Manual Symbol.hasInstance

Service[Symbol.hasInstance] is implemented manually to pierce the Proxy — ordinary instanceof fails on a proxy, so the prototype chain is walked manually to find the constructor. This is part of the recurring "dual-realm consistency" theme in Cordis: instanceof inside a vm sandbox has to align with the host realm.

Unofficial community learning site. Content based on the MIT-licensed deepseek-ai/deepseek-harness source. · Privacy · Terms · About