What Is Spatiotemporal Composability in Cordis? A Deep Dive into the Meta-Framework

Spatiotemporal composability is Cordis's core architectural principle that enables developers to compose functionality across both spatial boundaries (hierarchical component trees) and temporal boundaries (dynamic plugin lifecycles and runtime updates).

Cordis (maintained by the cordiverse organization) implements spatiotemporal composability as its foundational paradigm. This meta-framework allows you to build complex applications from small, independent plugins that can be registered in specific scopes and safely updated while the system runs without breaking existing functionality.

The Two Dimensions of Spatiotemporal Composability

Spatiotemporal composability operates across two distinct but complementary axes. Spatial composition refers to how functionality is organized across different logical "rooms" of an application—such as UI modules, micro-frontends, or server sub-systems. Temporal composition handles the evolution of the system as plugins are added, removed, or hot-reloaded during runtime.

According to the Cordis source code, these dimensions are not treated as separate concerns but as unified properties of the application context. The framework guarantees that service lookups respect both the current where (position in the context hierarchy) and when (current version of the service implementation).

Spatial Composition via the Registry

The spatial dimension enables independent application pieces to be registered locally and later queried from any descendant context without tight coupling. In packages/core/src/registry.ts, the Registry class creates a hierarchical map of services scoped to specific contexts.

Hierarchical Service Registration

The Registry maintains parent-child relationships that allow service lookup to bubble up through the context tree. When you register a service in a parent context, all child contexts can retrieve it, but children can also override registrations locally without affecting siblings or ancestors.

import { createContext } from '@cordis/core'

// Create a root context (the "space" for the whole app)
const root = createContext()

// Register a service in that spatial scope
root.registry.add('logger', console)

// Retrieve the service from a child context (spatial composition)
const child = root.createChild()
const logger = child.registry.get('logger')
logger.info('Hello from the child context!')

In packages/core/src/context.ts, the Context class exposes the registry property that enables this spatial scoping, allowing services to be placed exactly where they are needed in the component hierarchy.

Temporal Composition with Fiber and Events

The temporal dimension allows new behavior to be introduced after the application has started and ensures old behavior can be cleanly disposed of without memory leaks or dangling references. This is implemented primarily through the Fiber class in packages/core/src/fiber.ts.

Managing Async Lifecycles with Fiber

The Fiber class tracks the lifecycle of asynchronous tasks and manages disposables—resources that need cleanup when a component unmounts or updates. When you register a disposable via fiber.disposable(), Cordis ensures it executes when that temporal boundary ends, preventing resource leaks during hot-reloads.

// Dynamically replace the logger at runtime (temporal composition)
child.fiber.disposable(() => {
  // Clean up old logger when the fiber ends
  console.log('Disposing old logger')
})

child.registry.set('logger', {
  info(msg: string) { console.error('[New]', msg) },
})

// New lookups see the updated implementation
const newLogger = child.registry.get('logger')
newLogger.info('This uses the new logger')

Cross-Time Communication via Events

For communication that spans temporal boundaries, packages/core/src/events.ts provides an EventEmitter that works with fibers. This publish/subscribe bus functions correctly even as listeners are added or removed at runtime, ensuring events reach only active, relevant contexts.

import { EventEmitter } from '@cordis/core'

// Create an emitter attached to a context's fiber
const emitter = new EventEmitter(child.fiber)

// Listener added now
emitter.on('data', payload => console.log('Received', payload))

// Later, after async work, emit an event (temporal)
setTimeout(() => emitter.emit('data', { id: 42 }), 1000)

Unifying Space and Time in Context

The Context class in packages/core/src/context.ts unifies both dimensions by carrying references to both the Registry (spatial) and Fiber (temporal). This design allows a service to be scoped to a specific subtree while simultaneously being updated temporally—such as hot-replacing a command for a specific UI region without affecting other regions.

When you invoke createContext(), you instantiate a node in the spatial hierarchy that also contains its own temporal boundaries. The Context API exposes methods like createChild() for spatial extension and manages the underlying fiber for lifecycle control. The base Service class in packages/core/src/service.ts extends this pattern, providing an abstract foundation for services that support both registration and runtime override.

Core Implementation Files

Summary

  • Spatiotemporal composability combines spatial scoping (hierarchical component trees) with temporal lifecycle management (runtime plugin updates).
  • The Registry system in packages/core/src/registry.ts enables spatial composition by allowing services to be registered at specific hierarchy levels and inherited by descendants.
  • The Fiber system in packages/core/src/fiber.ts enables temporal composition by tracking async task lifecycles and managing resource disposal during dynamic updates.
  • The Context class unifies both dimensions, exposing a concise API for registration, lookup, and disposal across space and time.
  • Cordis supports hot-reloading and dynamic plugin replacement without breaking existing functionality or leaking resources.

Frequently Asked Questions

How does spatiotemporal composability differ from standard dependency injection?

Standard dependency injection typically focuses on wiring dependencies at startup time within a static spatial hierarchy. Spatiotemporal composability extends this by adding the temporal dimension—services can be added, removed, or replaced after the application has started, and the framework manages the cleanup of old implementations through fiber disposables.

Can services be updated at runtime without restarting the application?

Yes. Cordis supports runtime updates through the Registry.set() method combined with Fiber disposables. When you replace a service registration, new lookups immediately receive the updated implementation, while the fiber ensures previous instances are properly disposed of via registered cleanup callbacks.

What is the relationship between Context and Fiber in Cordis?

Each Context instance contains a reference to a Fiber that manages the temporal lifecycle of that specific spatial scope. The context handles where services exist in the hierarchy (spatial), while the fiber handles when they are active and how they are cleaned up (temporal).

How does the Registry handle service resolution across parent and child contexts?

The Registry implements hierarchical lookup logic where get() calls first check the local context, then traverse up through parent contexts until finding a match. This allows children to inherit services from ancestors while maintaining the ability to override registrations locally without affecting other branches of the tree.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →