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>: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],createCallablemakes the instance callable (e.g.ctx.logger('name')). - The
Service.checksymbol is an availability predicate; it is passed intoprovideand the reflect layer calls it whenctx.get(strict=true)runs — only services whosecheck()returns true are considered "available" by the inject system.
Config Merging
[symbols.resolveConfig](base?, head?) at vendor/cordis/src/service.ts:86-102:
[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
injectmap. - 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:
_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:
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:
- Function — used directly as the apply callback.
- Constructor — a class,
new-ed into an instance. { 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:
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.
What to read next
- Lifecycle and Middleware — how Fiber auto-reloads when service availability changes
- Plugin Registry — the two systems of static and dynamic registration