What Is a Fiber in Cordis and What Is Its Purpose?
A Fiber in Cordis is the fundamental runtime unit that encapsulates a single plugin instance and its execution context, managing everything from lifecycle states and configuration validation to dependency injection and hot‑reloading.
In the cordiverse/cordis framework, every plugin you register becomes a Fiber. This architecture abstracts the complex orchestration of plugin systems, ensuring side‑effects are tracked deterministically and the runtime remains stable even when individual plugins fail.
Core Responsibilities of a Fiber
The Fiber class in packages/core/src/fiber.ts handles seven critical responsibilities that make Cordis plugins robust and predictable.
Lifecycle State Management
A Fiber tracks its current state through the FiberState enum, which defines six possible states:
PENDING– initial state before loading beginsLOADING– actively executing the plugin functionACTIVE– successfully loaded and runningFAILED– encountered an error during load or reloadUNLOADING– actively disposing and cleaning upDISPOSED– fully cleaned up and inactive
The internal state machine transitions between these states based on method calls and async operations. You can inspect fiber.state at any time to check a plugin's health.
Configuration Validation
Before a plugin executes, the Fiber validates its configuration against a StandardSchemaV1 definition. The resolveConfig function handles this validation, throwing a ValidationError when the provided config fails schema requirements.
This validation happens in the Fiber constructor and during fiber.update() calls, ensuring malformed configuration never reaches plugin code.
Effect Execution and Disposal
The effect method registers side‑effects that automatically clean up when the Fiber unloads:
ctx.fiber.effect(() => {
const timer = setInterval(() => ctx.logger.info('tick'), 1000)
return () => clearInterval(timer) // disposer
}, 'periodic tick')
Effects can return:
- A function (disposer)
- An iterable of disposers
- A promise resolving to either
The _execute helper in packages/core/src/fiber.ts manages execution and guarantees proper disposal ordering—inner effects dispose before outer effects.
Dependency Injection
When created, a Fiber instantiates a child Context that inherits from its parent while exposing itself via ctx.fiber. This context is injected into the plugin function, allowing access to services declared by other plugins.
The constructor injection logic (packages/core/src/fiber.ts#L33-L45) validates that required services exist and satisfy interface constraints before activation proceeds.
Hot‑Reloading and Inertia Locking
Cordis supports dynamic plugin updates through the _setEpoch, _reload, and _unload methods. When configuration changes or dependencies update, the Fiber:
- Computes a new epoch string identifying the desired state
- Acquires the
inertiapromise as a lock to prevent overlapping operations - Executes unload then reload in sequence
- Releases the lock for the next operation
This inertia mechanism ensures consistent state even during rapid successive changes.
Error Propagation
Runtime errors in plugins transition the Fiber to FiberState.FAILED and reject the await() promise. Callers can handle failures through standard promise methods:
const badFiber = await root.plugin(faultyPlugin)
await badFiber.catch(err => {
console.error('Plugin failed:', err) // fiber.state === FiberState.FAILED
})
Errors in _reload and other internal methods follow the same propagation path through the await implementation (packages/core/src/fiber.ts#L22-L27).
Public Control API
The Fiber exposes three primary control methods for runtime interaction:
| Method | Purpose |
|---|---|
update(config) |
Change configuration and trigger hot‑reload |
restart() |
Force unload and reload with current configuration |
await() |
Returns a promise that resolves when Fiber reaches ACTIVE or rejects on FAILED |
Creating and Using Fibers
Basic Plugin Registration
Registering a plugin always returns a Fiber instance:
import { Context } from '@cordis/core'
function hello(ctx: Context) {
ctx.logger.info('Hello, world!')
}
const root = new Context()
const fiber = await root.plugin(hello) // ← Fiber created
await fiber // resolves when ACTIVE
The root.plugin() method internally constructs a new Fiber, executes its loading sequence, and returns the instance for control and monitoring.
Configuration Updates and Hot‑Reload
Plugins accepting configuration can be updated dynamically:
function greeter(ctx: Context, config: { greeting: string }) {
ctx.logger.info(config.greeting)
}
const fiber = await root.plugin(greeter, { greeting: 'Hi' })
await fiber // ACTIVE
fiber.update({ greeting: 'Hello again' })
await fiber // waits for reload completion
The update method triggers _setEpoch with new configuration, queueing a reload through the inertia lock system.
Fiber Implementation Architecture
The core implementation lives in packages/core/src/fiber.ts with supporting systems throughout the codebase:
packages/core/src/context.ts– DefinesContextwithctx.fiberreference and theextendmethod Fibers use for context inheritancepackages/core/src/service.ts– Base class for injectable services that integrate with Fiber lifecyclepackages/core/tests/fiber.spec.ts– Comprehensive tests for state transitions, locking behavior, and error scenariospackages/core/src/events.ts– Event system emittinginternal/pluginandinternal/statuslifecycle events
Summary
- A Cordis Fiber is the runtime container for a single plugin, managing its complete lifecycle from loading through disposal
- State machine tracking via
FiberStateenum provides visibility into plugin health - Automatic effect disposal ensures resources clean up in correct order when plugins unload
- Hot‑reload with inertia locking prevents race conditions during configuration changes
- Configuration validation through
StandardSchemaV1rejects invalid configs before plugin execution - Promise-based control via
await(),update(), andrestart()enables reactive plugin management
Frequently Asked Questions
How does a Fiber differ from a Context in Cordis?
A Context (ctx) is the dependency injection container passed to plugins, while a Fiber is the lifecycle manager that creates and owns that Context. Every Fiber has exactly one Context accessible via ctx.fiber, but Contexts can form hierarchies where child contexts inherit parent services. The Fiber handles the plugin's execution state; the Context handles service resolution and event routing.
What triggers a Fiber to reload automatically?
Automatic reloads occur when: (1) fiber.update(newConfig) is called with different configuration, (2) a service declared as a dependency is added, removed, or modified in a parent context, or (3) fiber.restart() is invoked explicitly. The _setEpoch method computes a string fingerprint of the desired state; when this differs from the current epoch, the Fiber queues an unload/reload cycle through the inertia lock.
Can multiple Fibers share the same plugin function?
Yes. The plugin function is just a factory; each call to ctx.plugin(myFunction) creates a distinct Fiber with its own state, context, and configuration. Multiple Fibers from the same function operate independently—one can be ACTIVE while another is FAILED or DISPOSED, with no shared state unless explicitly designed through external variables or injected services.
What happens if a Fiber's effect throws during disposal?
Disposal errors are caught and logged, but they do not prevent subsequent effects from disposing. The Fiber proceeds through its cleanup sequence, transitioning to DISPOSED once all registered disposers have executed. If critical cleanup is required, effects should implement their own error handling and retry logic rather than relying on the Fiber's default behavior.
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 →