跪拜 Guibai
← All articles
Artificial Intelligence

Inside Cordis: The Lifecycle Tree That Powers DeepSeek's Plugin Architecture

By 神奇小汤圆 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

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.

Summary

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.

Takeaways
Every plugin installation creates a Fiber — a stateful runtime unit with its own lifecycle, not a one-shot function call.
Context is a Proxy: accessing `ctx.database` triggers a scoped service resolution up the Fiber chain rather than a plain property read.
Declaring `inject: ['database']` keeps a plugin in PENDING until the service exists, and automatically unloads it if the service disappears.
`ctx.effect()` registers a cleanup function that Cordis calls in reverse order when the owning Fiber unloads, preventing timer, connection, and listener leaks.
`isolate()` lets the same service name resolve to different instances in separate contexts, enabling multi-tenant setups without changing plugin code.
Event listeners are scoped to their Context and automatically removed when the plugin unloads, so different tenants never accidentally handle each other's events.
Hot module replacement works because every side effect is bound to a Fiber; tearing down the old Fiber and rebuilding a new one leaves no stale state.
Conclusions

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.

Concepts & terms
Fiber
A runtime unit representing a single plugin installation. It tracks the plugin's state (PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED), its dependencies, provided services, and all resources it created.
Context Proxy
Cordis wraps the application context in a JavaScript Proxy so that property access like `ctx.database` triggers a scoped service resolution rather than a direct object read.
epoch
A version identifier computed from the runtime instance IDs of a plugin's dependency services. When dependencies change, the epoch changes, triggering the Fiber to unload or reload.
isolate
A mechanism that creates a scoped sub-context where a service name can resolve to a different instance than in the parent context, enabling multi-tenant architectures.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗