# How Cordis Implements Interception for Service Configuration

> Discover how Cordis implements service configuration interception using prototype-chained maps in its Context. Learn how plugins inject configuration layers for seamless service resolution.

- Repository: [Cordiverse/cordis](https://github.com/cordiverse/cordis)
- Tags: how-to-guide
- Published: 2026-08-24

---

**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`](https://github.com/cordiverse/cordis/blob/main/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`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/service.ts) (lines 51-66). This method walks the intercept prototype chain to collect all applicable configurations:

```typescript
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`](https://github.com/cordiverse/cordis/blob/main/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:

```typescript
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:

```typescript
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

- **[`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts)**: Holds the hidden intercept map and provides the `Context.intercept()` method for adding layers.
- **[`packages/core/src/service.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/service.ts)**: Contains the abstract `Service` class implementing `[symbols.resolveConfig]` that walks the intercept chain.
- **[`packages/core/src/logger.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/logger.ts)**: Demonstrates built-in service usage of the interception mechanism.
- **[`packages/loader/src/config/isolate.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/isolate.ts)**: Establishes fresh intercept prototypes for each loader entry using `Object.create(entry.ctx[Context.intercept])`.
- **[`packages/core/tests/service.spec.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/tests/service.spec.ts)**: Validates that intercepts correctly override service configurations.

## 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`](https://github.com/cordiverse/cordis/blob/main/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`](https://github.com/cordiverse/cordis/blob/main/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.