What Is the Reflect Service in Cordis? Core Concepts and Implementation

The Reflect Service is the central Proxy-based mechanism in Cordis that enables dependency injection by intercepting property access on Context objects, allowing services to be registered, accessed, and mixed in as if they were native object properties.

The Reflect Service serves as the architectural backbone of the Cordis framework, transforming how plugins interact with shared resources. Located in the cordiverse/cordis repository, this service—implemented primarily in packages/core/src/reflect.ts—wraps each Context instance in a JavaScript Proxy to provide a seamless, property-based API for service management.

Proxy-Based Property Interception

At the core of the Reflect Service, the ReflectService class defines a handler that governs all property interactions on a Cordis Context. The service works in conjunction with packages/core/src/context.ts, which instantiates the Reflect Service for each context, and packages/core/src/utils.ts, which supplies helper functions like getTraceable used during property resolution.

When you access a property on the context, the handler defined in lines 61-70 first consults the internal service store. If the service exists, it returns a trace-wrapped value via getTraceable. If unavailable, it falls back to registered accessor definitions or throws a descriptive error. This mechanism allows services to appear as ordinary object properties while remaining fully managed by the framework.

For write operations, the handler (lines 102-112) guards against accidental overwrites. Attempts to assign values to properties that are not declared as accessors trigger an error unless the current fiber is inactive, in which case the write proceeds directly on the target object. This ensures service integrity while permitting configuration during initialization.

Service Registration with provide()

The provide() method (lines 77-98) implements dynamic service registration with reactive updates. When called, it creates a new entry in the internal store, registers a symbol on the root isolate, and notifies every active fiber that depends on the service so they can refresh their state.

import { Context } from 'cordis'

// Register a service that becomes accessible as a property
ctx.provide('logger', console)

// Access the service through the proxy handler
ctx.logger.info('Hello from Cordis!')

This pattern ensures that services are both type-safe and reactive, with automatic dependency tracking across the fiber-based execution model. It eliminates manual service lookup by presenting registered instances as native properties.

Accessor and Mixin Patterns

Beyond simple value storage, the Reflect Service in Cordis supports computed properties and service composition through specialized registration methods.

Creating Accessor Properties

The accessor() method (lines 31-38) enables plugins to expose computed getters and setters that resolve on demand without persisting concrete values in the store. This is ideal for dynamic data or computed configuration that should not be cached.

ctx.accessor('timestamp', {
  get(_, __, err) {
    if (!Date.now()) throw err
    return Date.now()
  },
})
console.log('Current timestamp:', ctx.timestamp)

Mixing In Services

The mixin() method (lines 41-66) creates proxy-wrapped accessors that forward reads and writes to a target service, optionally binding functions to the mixed-in context. This pattern is particularly useful for exposing selected methods from one service within another context without manual delegation boilerplate.

// Mix in selected methods from the logger service
ctx.mixin('logger', ['info', 'warn'])
ctx.info('This works via mixin')
ctx.warn('And so does this')

Debugging and Value Tracing

For development and monitoring, the Reflect Service provides trace() and bind() methods (lines 69-82) that wrap values and functions. These wrappers make subsequent access traceable, enabling Cordis's internal debugging tools to track service usage and data flow throughout the application lifecycle.

const traced = ctx.bind((a: number, b: number) => a + b)
console.log(traced(2, 3)) // Arguments and return values are wrapped for debugging

Summary

  • The Reflect Service in packages/core/src/reflect.ts wraps each Cordis Context in a Proxy to intercept property access, working alongside packages/core/src/context.ts and packages/core/src/utils.ts.
  • It exposes registered services as ordinary properties while guarding against invalid writes through the handler defined in lines 61-70 and lines 102-112.
  • The provide() method (lines 77-98) handles dynamic registration with fiber notification and root isolate symbol registration.
  • accessor() (lines 31-38) enables computed properties, while mixin() (lines 41-66) facilitates service composition through proxy-wrapped method forwarding.
  • Traceability features via trace() and bind() (lines 69-82) support debugging by wrapping values and callbacks for monitoring.

Frequently Asked Questions

How does the Reflect Service handle property writes that conflict with existing services?

According to the source in packages/core/src/reflect.ts (lines 102-112), the Proxy handler guards writes by checking whether the property is declared as an accessor. If not, it throws an error unless the current fiber is inactive, in which case the assignment proceeds directly on the target object. This prevents accidental overwrites of managed services while allowing configuration during initialization phases.

What is the difference between provide() and accessor() in Cordis?

The provide() method (lines 77-98) registers a concrete value or instance in the internal service store, making it available to all fibers and contexts that depend on it. In contrast, accessor() (lines 31-38) defines computed getters and setters that execute on demand without storing a persistent value, suitable for dynamic data like timestamps or computed configuration.

How does the mixin() method work in the Cordis Reflect Service?

As implemented in lines 41-66 of packages/core/src/reflect.ts, mixin() creates proxy-wrapped accessors that forward property access to a source service. When mixing in methods like ctx.mixin('logger', ['info', 'warn']), it binds those functions to the current context while maintaining their connection to the original service, effectively importing selected capabilities without manual delegation code.

Where does the Reflect Service obtain the values returned during property reads?

When accessing a property, the handler (lines 61-70) first checks the internal store for a registered service. If found, it returns the value processed through getTraceable for debugging support. If the service is not yet available, it checks accessor definitions before throwing an error, ensuring that only properly registered or computed values are exposed through the Context proxy.

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 →