Inside Cordis: The Lifecycle Tree That Powers DeepSeek's Plugin Architecture
Most Node.js frameworks leave resource cleanup and dependency lifecycle to the developer, producing leaks that surface only under load or during hot reloads. Cordis bakes lifecycle management into the runtime itself, so plugins that depend on a database, open WebSockets, or register timers are guaranteed to release those resources when their dependencies shift or the plugin unloads — a property that makes long-running multi-tenant services and HMR-driven development workflows far less brittle.
Cordis wraps application composition into four core primitives: Context, Fiber, Reflect, and Registry. A Context is a Proxy that resolves service lookups through the current plugin's scope chain, so `ctx.database` always returns the correct instance without manual imports. Each plugin installation creates a Fiber — a runtime unit that moves through PENDING, LOADING, ACTIVE, UNLOADING, and DISPOSED states — carrying its own configuration, declared dependencies, provided services, and all created resources.
Dependency injection is continuous rather than one-shot. When a plugin declares `inject: ['database']`, Cordis keeps it in PENDING until the database service appears, and automatically unloads and reloads it if that service later disappears or changes. Side effects registered via `ctx.effect()` return cleanup functions that the framework calls in reverse order when the owning Fiber unloads, preventing timer leaks, lingering WebSocket connections, and stale event listeners.
Service isolation through `isolate()` lets the same service name map to different instances in separate contexts, enabling multi-tenant architectures where plugin code stays identical across tenants. The event system inherits the same scoping rules — listeners belong to a Context and vanish when their plugin unloads. Because every side effect is bound to a Fiber, configuration updates and hot module replacement can safely tear down an old resource tree and rebuild a new one without leftover state.
Continuous dependency injection — where a plugin's runtime state tracks service availability rather than receiving a one-time injection — is a design choice that shifts failure handling from runtime errors to lifecycle state transitions. A plugin never executes with a missing dependency; it simply waits.
Binding event listeners to a Context rather than a global bus solves a class of cross-tenant contamination bugs that are otherwise easy to miss in testing and catastrophic in production.
The reverse-order resource cleanup guarantee means Cordis effectively enforces a dependency-aware teardown order without requiring the developer to manually sequence disposal calls.