# How the Cordis Context Proxy Handles Service Lookups: Fiber-Scoped Resolution Explained

> Discover how the Cordis Context Proxy handles service lookups with fiber-scoped resolution. Learn about lazy dependency resolution and symbolic isolation.

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

---

**The Cordis `Context` object is wrapped in a JavaScript `Proxy` that intercepts property access to perform hierarchical service lookups across fiber-scoped stores, combining symbolic isolation with accessor definitions to resolve dependencies lazily.**

The Cordis framework implements a sophisticated dependency injection system where the `Context` class serves as the central registry for services. Understanding how the Context proxy handles service lookups is essential for developers building plugins and managing service lifecycles in Cordis applications. The lookup mechanism combines JavaScript Proxy traps with a fiber-based isolation model to enable dynamic, hierarchical service resolution.

## Proxy Implementation in Context Creation

Every `Context` instance is immediately wrapped in a JavaScript `Proxy` upon instantiation. In [`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts) (lines **36‑48**), the constructor creates the proxy using a handler defined in the `ReflectService` class. The actual proxy handler implementation resides in [`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts) (lines **63‑94**), where the `get` trap defines the core lookup logic.

This architectural choice allows Cordis to intercept every property access on a context instance, enabling runtime resolution of services rather than static property assignment.

## The Four-Stage Lookup Process

When you access a property on a `Context` instance (e.g., `ctx.myService`), the proxy's `get` trap executes a prioritized resolution sequence:

### Stage 1: Special Property Bypass

The handler first checks if the property key is a **symbol**, **reserved word**, **numeric string**, or starts with an underscore (`_`). These special properties bypass the service lookup logic entirely and resolve directly via `Reflect.get(target, prop)`, returning the raw value from the Context instance.

### Stage 2: Own Property Resolution

If the property exists as an own property on the target `Context` object, the handler retrieves it and wraps the value using the internal `getTraceable` function. This ensures that direct context properties maintain proper tracking while avoiding the service registry overhead.

### Stage 3: Registered Service Accessors

The `ReflectService` maintains a map of property definitions (`props`) that track registered services. If the requested property is declared as an **accessor** in this map, the handler invokes the accessor's `get` method directly. This allows for computed service properties that execute custom logic upon access.

### Stage 4: Fiber-Scoped Hierarchical Resolution

The most complex stage occurs when the property is not found in the previous stages. The handler checks if the current fiber is active (`ctx.fiber.runtime` is true):

1. **Isolation Key Lookup**: It reads the isolation key for the requested name from `ctx[symbols.isolate][prop]`.
2. **Current Fiber Check**: It searches `fiber.store?.[prop]` for a registered service implementation (`Impl`).
3. **Required Validation**: If not found, it checks whether the property is required (`prop in fiber.inject`). Missing required services in inactive fibers trigger an error.
4. **Parent Traversal**: The search climbs to the parent fiber while the isolation key matches, ultimately reaching the root fiber if necessary.

If no fiber is active, the lookup falls back to `ReflectService.get`, which returns the stored value from the global implementation map via `this._getImpl(name, strict)?.value`.

## Service Registration and Storage

Before services can be looked up, they must be registered through `ReflectService.provide` (defined in [`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts), lines **150‑190**, and exposed via [`packages/core/src/registry.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/registry.ts), lines **75‑92**). This method:

- Creates a service entry in the `props` map with `{ type: 'service' }`.
- Ensures a unique symbol key exists in the root isolate map (`this.ctx.root[symbols.isolate][name]`).
- Stores an `Impl` record in both the global `ReflectService.store` map and the current fiber's `store`.
- Notifies dependent fibers when the service becomes available.

This dual-storage approach (global and fiber-scoped) enables the hierarchical resolution that respects isolation boundaries while maintaining global visibility.

## Practical Usage Examples

```typescript
import { Context, Inject } from 'cordis'

// Create a new context (automatically proxied)
const ctx = new Context()

// Register a service
ctx.provide('myService', { hello: 'world' })

// Access the service directly – the proxy triggers the lookup logic
console.log(ctx.myService) // → { hello: 'world' }

// Use @Inject to declare a dependency in a plugin
class MyPlugin {
  @Inject('myService')
  static async apply(ctx: Context) {
    // `ctx.myService` resolves via the proxy as above
    ctx.logger.info(ctx.myService.hello)
  }
}

ctx.plugin(MyPlugin) // registers and runs the plugin

```

When a plugin runs in a child fiber, the lookup ascends the fiber chain until it locates the service or throws an error if the required service is unavailable.

## Summary

- The Cordis `Context` is wrapped in a JavaScript `Proxy` at instantiation ([`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts), lines **36‑48**) to intercept all property access.
- The proxy handler in [`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts) implements a four-stage lookup: special properties, own properties, registered accessors, and fiber-scoped hierarchy.
- Service resolution respects **fiber isolation** through symbolic keys stored in `ctx[symbols.isolate]`, climbing the parent fiber chain until finding a match.
- Services are registered via `ReflectService.provide`, which stores implementations in both global and fiber-scoped maps for hierarchical resolution.
- The system supports both direct property access and decorator-based injection (`@Inject`), with the same proxy-driven lookup mechanism underlying both approaches.

## Frequently Asked Questions

### How does the Context proxy determine which fiber to search for services?

The proxy reads the current active fiber from `ctx.fiber` and checks if `ctx.fiber.runtime` is true. If active, it retrieves the isolation key for the requested property from `ctx[symbols.isolate][prop]` and begins searching from the current fiber's `store` map, climbing to parent fibers while the isolation key matches.

### What happens if a service is not found in the fiber hierarchy?

If the service is not found during the fiber traversal, the handler checks whether the property is listed in `fiber.inject` as a required dependency. For required services, it throws an error indicating the missing dependency. If not required and no fiber is active, it falls back to the global `ReflectService.get` method.

### Where is the proxy handler defined in the Cordis source code?

The proxy handler is defined in [`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts) (lines **63‑94** for the `get` trap, and lines **150‑190** for service registration logic). The `Context` constructor in [`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts) (lines **36‑48**) attaches this handler to the instance immediately upon creation.

### How does service isolation work across different fibers?

Service isolation uses symbolic keys stored in the root context's isolate map (`ctx.root[symbols.isolate][name]`). When a service is provided, it creates a unique symbol key. During lookup, the proxy compares isolation keys while traversing the fiber hierarchy, stopping at fiber boundaries where the isolation key differs, effectively sandboxing services between isolated contexts.