How Cordis Implements Spatiotemporal Composability: Isolating Space and Time
Cordis achieves spatiotemporal composability by strictly separating spatial concerns (service scopes via Context) from temporal concerns (lifecycle management via Fiber), allowing plugins to be isolated logically and reloaded independently without state leakage.
The Cordis framework (available at cordiverse/cordis) provides a novel architecture for building modular, hot-reloadable applications. By treating space as the logical domain where services reside and time as the execution lifecycle of those services, Cordis enables developers to compose complex systems that can be modified at runtime without cascading failures.
Spatial Composability Through Context Isolation
Spatial composability in Cordis refers to the ability to create isolated logical scopes where services and plugins operate independently. This prevents state leakage between different functional areas of an application.
The Context.isolate() Method
In packages/core/src/context.ts, the Context class provides the isolate() method that generates isolated sub-contexts. When called, it returns a new context that inherits the parent's traceable state while maintaining a private isolation map stored in symbols.isolate at line 65.
This mechanism ensures that plugins registered within one isolated context cannot access or interfere with services in sibling contexts. Each isolated context maintains its own registry entries, interceptors, and symbol definitions.
Private Isolation Maps and Symbol Scoping
The isolation map acts as a boundary that separates the symbol registry of parent and child contexts. When you create an isolated space using root.isolate('user'), the resulting context receives a fresh namespace while preserving the ability to trace back to its origin. This spatial separation is fundamental to multi-tenant architectures where user and admin domains must remain distinct.
Temporal Composability via the Fiber System
While Context handles where code runs, the Fiber class in packages/core/src/fiber.ts manages when code runs and for how long. Fibers model the temporal dimension through explicit lifecycle management and effect registration.
The Fiber Class and Lifecycle Management
Each Fiber instance represents a running plugin instance and maintains its own FiberState. The class tracks a private _disposables array that stores cleanup functions, ensuring that resources are released when a fiber unloads or restarts.
The constructor at line 33 receives a parent Context, which may itself be an isolated context from the spatial layer. This injection links the temporal fiber to a specific spatial scope, creating the foundation for spatiotemporal coordination.
Effect Registration and Disposable Management
The Fiber.effect() method at line 75 provides the primary API for temporal composition. Developers register effects—functions that execute when the fiber loads—that automatically clean up when the fiber unloads:
userSpace.fiber.effect(() => {
console.log('User plugin started')
return () => console.log('User plugin stopped')
}, 'user-plugin')
Effects are composable and can be reordered or restarted independently. When Fiber.restart() is called, the system triggers a complete unload-reload cycle of all registered effects while preserving the original spatial isolation.
Bridging Space and Time: The Spatiotemporal Integration
Cordis unifies its spatial and temporal layers through specific architectural bridges that allow contexts and fibers to interact without breaking isolation boundaries.
Fiber Injection of Isolated Contexts
When instantiating a new fiber via new Fiber(parent, ...), the constructor injects the parent context. If this parent was created via Context.isolate(), the fiber automatically operates within that isolated spatial scope. This means calling Fiber.update or Fiber.restart triggers temporal changes that respect the original spatial boundaries.
Epoch-Based Reloading and State Propagation
The _refresh() method at line 85 implements the temporal update mechanism by computing an "epoch" string based on impl.fiber.uid. When implementations change epochs, the fiber schedules _reload or _unload operations.
This epoch-based system enables efficient change propagation across isolated fibers. When one fiber updates its configuration, dependent fibers can react to these changes through the RegistryService and ReflectService without directly accessing each other's internal state. The Impl class tracks its owning fiber via impl.fiber at line 66, allowing the system to locate and notify only the relevant spatial slices when temporal events occur.
Practical Implementation Example
The following example demonstrates both spatial and temporal composition working together:
import { Context } from 'cordis'
// Spatial composition: Create isolated logical spaces
const root = new Context()
const userSpace = root.isolate('user')
const adminSpace = root.isolate('admin')
// Services remain scoped to their specific space
userSpace.registry.register('profile', () => ({ name: 'Alice' }))
adminSpace.registry.register('profile', () => ({ name: 'Bob' }))
// Temporal composition: Register lifecycle-bound effects
userSpace.fiber.effect(() => {
console.log('User plugin started')
return () => console.log('User plugin stopped')
}, 'user-plugin')
// Hot reload: Restart temporal flow while preserving spatial isolation
await userSpace.fiber.restart()
Summary
- Spatial isolation is achieved through
Context.isolate()inpackages/core/src/context.ts, which creates private isolation maps usingsymbols.isolateto prevent service leakage between contexts. - Temporal control is managed by the
Fiberclass inpackages/core/src/fiber.ts, which tracks lifecycle states, manages disposables via_disposables, and provides theeffect()API for registration of load/unload hooks. - Spatiotemporal bridging occurs when fibers receive isolated contexts during construction, allowing
Fiber.restart()andFiber.updateto trigger temporal reloads within fixed spatial boundaries. - Change propagation uses epoch-based refreshing via
Fiber._refresh()and implementation tracking throughimpl.fiberreferences to notify relevant contexts without breaking isolation.
Frequently Asked Questions
How does Cordis prevent state leakage between plugins?
Cordis prevents state leakage through the Context.isolate() method, which creates isolated sub-contexts with private symbol maps. Each isolated context maintains its own registry entries in packages/core/src/context.ts, ensuring that services registered in one space cannot access or modify services in another, even when operating concurrently.
What happens to effects when a fiber restarts?
When Fiber.restart() is called, the system invokes all disposables stored in the _disposables array, runs the unload lifecycle, and then re-executes all registered effects from Fiber.effect(). This temporal reset occurs within the same spatial context, preserving isolation while refreshing the plugin's runtime state as implemented in packages/core/src/fiber.ts.
How does Cordis track which fiber owns a specific implementation?
The framework tracks ownership through the Impl class's fiber property referenced at line 66 in packages/core/src/fiber.ts. Each implementation object stores a reference to its parent fiber (impl.fiber), while RegistryService and ReflectService maintain the mappings between plugin names and their runtime instances, enabling targeted updates without scanning unrelated contexts.
Can isolated contexts share any state, or are they completely separate?
While isolated contexts maintain private symbol registries to prevent accidental leakage, they inherit traceable state from their parent contexts. This allows for intentional sharing of specific services while keeping plugin registrations and interceptors isolated, creating a hierarchical scoping mechanism rather than complete sandboxes.
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 →