How Cordis Implements Interception for Service Configuration

Cordis implements service configuration interception through a prototype-chained map stored in each Context, allowing plugins to inject configuration layers that services resolve by walking the chain and merging configurations.

Cordis, the flexible plugin framework maintained in the cordiverse/cordis repository, provides a powerful interception mechanism that enables runtime modification of service configurations without altering original service definitions. This system allows plugins to override or extend configuration values through a layered prototype chain approach. Understanding how Cordis handles service configuration interception reveals the architectural patterns that make the framework highly extensible.

Storing Intercepted Configurations in Context

Every Context instance maintains a hidden intercept map created during instantiation. In packages/core/src/context.ts (lines 36-38), the constructor initializes this map as a plain object using a symbol key (symbols.intercept).

The public Context.intercept(name, config) method creates a new interception layer by cloning the current map via Object.create(this[symbols.intercept]). It inserts the supplied configuration under the specified service name, then returns a new context that inherits this layer through extend({ [symbols.intercept]: intercept }). This operation yields a prototype chain of intercept objects where each layer can add or override configurations for specific services without mutating parent contexts.

Resolving Intercepted Configurations in Services

When a service requires its final configuration, the abstract Service class invokes [symbols.resolveConfig] as implemented in packages/core/src/service.ts (lines 51-66). This method walks the intercept prototype chain to collect all applicable configurations:

let intercept = this.ctx[Context.intercept];
const configs: any[] = [];
while (this.name in intercept) {
  if (Object.hasOwn(intercept, this.name)) {
    configs.unshift(intercept[this.name]);   // nearest layer first
  }
  intercept = Object.getPrototypeOf(intercept);
}

The algorithm gathers all matching intercept objects, placing the nearest layer first in the array using unshift. It then merges these configurations using either the service's static Config.merge method or falling back to Object.assign for shallow merging. This ensures that intercepted values properly override defaults while preserving the original configuration as a fallback.

Built-in Service Integration

The interception mechanism powers Cordis's core services, including LoggerService. In packages/core/src/logger.ts (lines 215-221), the logger resolves its configuration by iterating over the intercept chain with while ('logger' in intercept). This allows any plugin-provided intercept—such as changing log levels or output destinations—to take effect immediately, even though the original service definition remains unchanged.

Practical Implementation Examples

To intercept configuration for the built-in logger service:

import { Context } from 'cordis';

// Create a base context
const ctx = new Context();

// Intercept the logger configuration
const ctxWithIntercept = ctx.intercept('logger', { name: 'my-logger', level: 'debug' });

// The LoggerService automatically picks up the intercept
const logger = ctxWithIntercept.logger; 
// logger.name === 'my-logger', logger.level === 'debug'

Custom services can define their own merging logic:

class MyService extends Service {
  static Config = {
    merge(...configs) {
      // Deep merge logic here
      return Object.assign({}, ...configs);
    }
  };
}

// Intercept with custom timeout
const ctx2 = ctx.intercept('my-service', { timeout: 5000 });
const myService = new MyService(ctx2, 'my-service');
// Receives merged configuration including the intercepted timeout

Key Implementation Files

Summary

  • Prototype-chained intercept objects enable layered configuration overrides while preserving parent context integrity.
  • Immutable layer creation via Object.create() ensures safe concurrent usage without polluting base contexts.
  • Configuration resolution walks the prototype chain in [symbols.resolveConfig], collecting layers with nearest-first precedence.
  • Flexible merging honors service-specific Config.merge methods or defaults to Object.assign.
  • Framework-wide adoption allows built-in services like LoggerService to respect plugin-provided configuration changes.

Frequently Asked Questions

How does Cordis store intercepted configuration layers?

Each Context instance maintains a hidden intercept map initialized in the constructor at packages/core/src/context.ts. The Context.intercept() method creates a new layer by calling Object.create(this[symbols.intercept]), establishing a prototype chain where child contexts inherit parent intercepts while adding their own configurations.

What algorithm does Cordis use to resolve intercepted configurations?

The abstract Service class implements [symbols.resolveConfig] which walks the prototype chain of the intercept map, collecting all configurations for the service name using Object.hasOwn checks. It merges them with the nearest layer taking precedence, using either the service's custom Config.merge method or Object.assign as a fallback.

Can custom services define their own configuration merging logic?

Yes, services can expose a static Config.merge method to implement deep merging or custom conflict resolution strategies. If undefined, Cordis falls back to shallow Object.assign when combining intercepted configuration layers in the resolution loop.

Where does Cordis isolate configuration contexts during loading?

The loader creates fresh intercept prototypes for each entry point in packages/loader/src/config/isolate.ts by calling Object.create(entry.ctx[Context.intercept]). This ensures that loaded modules receive isolated configuration layers while maintaining the prototype inheritance chain for proper configuration resolution.

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 →