Understanding the Cordis Fiber Class: Complete Guide to Plugin Lifecycle Management
The Cordis Fiber class is the core runtime object in cordiverse/cordis that manages a plugin’s entire lifecycle—from instantiation and configuration to hot reloading and graceful disposal.
The Cordis Fiber orchestrates how plugins execute within the Cordis framework. Defined in the cordiverse/cordis repository, this class encapsulates state management, effect registration, and the complex state machine that governs when a plugin is loading, active, or unloading. Understanding the Fiber is essential for building robust plugins that handle configuration changes and service dependencies correctly.
What Is the Cordis Fiber Class?
The Cordis Fiber class represents a single instance of plugin execution within a Cordis application. Located in packages/core/src/fiber.ts, the Fiber class is instantiated by Context.plugin() and stored on the parent context as ctx.fiber.
Unlike simple function calls, a Fiber maintains persistent state across reloads. It tracks the plugin’s configuration, manages disposable side effects like timers and event listeners, and implements a formal state machine that transitions through PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED. This architecture ensures that plugins can be safely reloaded without leaking resources or leaving orphaned asynchronous operations.
Cordis Fiber Architecture and State Machine
File Location and Construction
The Fiber implementation resides in packages/core/src/fiber.ts. When you call ctx.plugin(), the framework constructs a new Fiber instance (lines 22-36) with the following parameters:
- The parent
Context - The plugin’s resolved configuration
- An injection map for dependency tracking
- Runtime metadata
- A callback for generating outer stack traces
During construction, the Fiber creates a child context via this.context = parent.extend({ fiber: this }) and registers itself in the parent’s registry. This linkage ensures that when the parent context shuts down, all child Fibers dispose automatically.
The FiberState Lifecycle
The Fiber class uses the FiberState enum to track exactly where a plugin stands in its lifecycle (lines 78-84). The private _getState() and _updateState() methods (lines 48-62) transition between states based on the current epoch, error status, and disposal flags.
Core lifecycle states:
- PENDING – The Fiber is created but not yet executing
- LOADING – The plugin callback is currently running
- ACTIVE – The plugin is fully initialized and serving requests
- UNLOADING – The plugin is executing disposal logic
- DISPOSED – All effects have been cleaned up and the Fiber is inactive
A separate FAILED state occurs when _error is populated, preventing state transitions until update() clears the error condition (lines 224-227).
Epoch Tracking and Reload Cycles
Cordis implements hot reloading through an epoch string that tracks which service implementations are currently satisfied. When injected dependencies change, the _setEpoch() method (lines 99-115) compares the new epoch against the current one.
If the epoch becomes __INACTIVE__, the Fiber triggers _unload() (lines 167-210) to dispose all effects. If valid services become available, _reload() executes the plugin callback again while preserving the implementation store and restoring disposable effects. This mechanism allows plugins to restart without losing their place in the dependency graph.
Effect Management in Cordis Fiber
Registering Effects with effect()
The Fiber.effect() method (lines 75-119) is the public API for registering disposable operations. Before accepting an effect, the Fiber calls assertActive() to verify the current state is appropriate for new operations.
Effects can be synchronous functions, iterables, async iterables, or promises. The method wraps each effect in a disposal wrapper that remains callable throughout the Fiber’s lifetime. When the Fiber unloads, it iterates through all registered effects and executes their cleanup logic in reverse order.
Automatic Cleanup and Error Handling
The _execute() helper (invoked by effect()) wraps effect execution in try-catch blocks. If an effect throws synchronously or returns a rejected promise, the Fiber catches the error and routes it through ctx.logger.error (lines 173-182). This prevents unhandled promise rejections from crashing the entire application while marking the Fiber as failed.
When disposal occurs, the Fiber continues executing remaining cleanup routines even if individual disposal callbacks throw, ensuring that resource leaks are minimized during error conditions.
Controlling the Plugin Lifecycle
Starting and Stopping Plugins
While ctx.plugin() initiates the lifecycle, the Fiber provides direct control through several public methods:
restart()– Forces the epoch to__INACTIVE__and triggers a full reload cycledispose()– Registered automatically during construction, this initiates the_unload()sequenceawait()– Returns a promise that resolves when any pending inertia (reload or unload operations) completes, re-throwing any stored errors (lines 62-78)
These methods allow external systems to coordinate plugin availability during deployments or dependency updates.
Handling Configuration Updates
The update(config, noSave?) method (lines 78-95) enables hot configuration reloading without restarting the entire application. It performs three operations:
- Validates the new configuration against the plugin’s
Configschema - Runs the
internal/updatewaterfall hook to notify other services - Calls
restart()to reload the plugin with the new parameters
This workflow ensures type safety and gives dependent plugins an opportunity to react to configuration changes before the target plugin restarts.
Error Recovery and the await() Method
When errors occur during _reload() or _unload(), the Fiber stores the error in _error and transitions to the FAILED state. The await() method provides a safe way to interact with a potentially failed Fiber—it resolves any pending operations and re-throws the stored error if one exists. This pattern allows manager code to decide whether to retry, log, or escalate the failure.
Practical Examples
Creating a Plugin and Accessing Its Fiber
import { Context } from 'cordis'
export function MyPlugin(ctx: Context) {
// Access the Fiber managing this plugin instance
const fiber = ctx.fiber
// Register a disposable interval timer
fiber.effect(() => {
const timer = setInterval(() => console.log('tick'), 1000)
return () => clearInterval(timer)
}, 'my-plugin:timer')
}
The ctx.fiber property provides direct access to the managing Fiber instance. The fiber.effect() call registers a cleanup function that Cordis automatically invokes when the plugin unloads or reloads.
Manually Restarting a Plugin
async function restartPlugin(ctx: Context) {
const fiber = await ctx.plugin(MyPlugin)
await fiber.restart()
}
ctx.plugin() resolves to the Fiber instance. Calling fiber.restart() forces the epoch to __INACTIVE__, triggering _unload() followed by _reload() to refresh the plugin state.
Updating Plugin Configuration
// Initial load with 2000ms interval
await ctx.plugin(MyPlugin, { interval: 2000 })
// Later: update to 5000ms without full restart
const fiber = ctx.fiber
await fiber.update({ interval: 5000 })
The update() method validates the new configuration, runs update hooks, and restarts the Fiber. All effects registered previously are disposed and recreated with the new configuration context.
Handling Errors in Effects
fiber.effect(() => {
// Synchronous errors are caught and logged
throw new Error('initialization failed')
})
fiber.effect(async () => {
// Asynchronous rejections are also handled
await Promise.reject('async failure')
})
Both synchronous and asynchronous errors within effects are caught by the Fiber’s execution wrapper, logged via ctx.logger.error, and stored in the Fiber’s error state for inspection via await().
Summary
The Cordis Fiber class serves as the central orchestrator for plugin execution in the cordiverse/cordis framework:
- State Machine – Tracks plugin lifecycle through
FiberState(PENDING, LOADING, ACTIVE, UNLOADING, DISPOSED) - Effect Management – Provides
effect()API for registering disposables that auto-cleanup on unload - Hot Reloading – Uses epoch tracking (
_setEpoch(),_reload()) to restart plugins when dependencies change without losing context - Error Boundaries – Catches and logs errors during initialization and disposal, exposing them through
await() - Configuration API – Enables runtime updates via
update()with schema validation and hook notification
Frequently Asked Questions
What is the Cordis Fiber class?
The Cordis Fiber class is the core runtime object defined in packages/core/src/fiber.ts that manages a single plugin instance’s execution. It encapsulates the plugin’s state, configuration, disposable effects, and lifecycle transitions, acting as the intermediary between the Cordis context system and the plugin’s actual code.
How does Cordis handle plugin reloading?
Cordis implements hot reloading through an epoch tracking mechanism. When injected services change or fiber.update() is called, the Fiber executes _setEpoch() to compare the new service environment against the current one. If changes are detected, _unload() disposes existing effects and _reload() re-executes the plugin callback while preserving the implementation store, allowing the plugin to restart with fresh dependencies.
What happens when a Fiber encounters an error?
When an error occurs during effect execution, disposal, or initialization, the Fiber catches it via try-catch blocks in _execute() and stores it in the _error property. The Fiber transitions to a FAILED state, preventing further state changes until update() or restart() clears the error. The await() method can be used to retrieve and re-throw these errors for handling by parent contexts.
How do I access the Fiber instance from within a plugin?
Cordis extends the Context interface to include a fiber property (declared in packages/core/src/fiber.ts, lines 8-11). Inside any plugin function, you can access ctx.fiber to get the current Fiber instance. This allows direct registration of effects, manual lifecycle control via restart(), or configuration updates through update().
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →