What Is a Service in Cordis and How to Extend It

A Service in Cordis is an abstract base class that encapsulates reusable functionality, automatically registers instances with a Context, and provides lifecycle management, callable proxies, and configuration resolution.

In the cordiverse/cordis framework, the Service class serves as the fundamental building block for modular, context-aware architecture. Every Service instance binds to a Context that manages its lifecycle, isolation, and effect system, making it essential to understand how to properly extend Service in Cordis when building plugins or custom components.

Core Architecture of Service in Cordis

The Service Base Class

The abstract Service class is defined in packages/core/src/service.ts and provides the foundation for all reusable components in the framework. When you extend this class, the constructor automatically registers the instance with the current Context via ctx.reflect.provide(name, this, this[Service.check]) at lines 33-35.

Every service receives a name that determines how other components access it. This name defaults to the class's static provide property or can be passed explicitly via super(ctx, name) at lines 18-21. Once registered, the service gains access to the surrounding Context through this.ctx (lines 29-31), which exposes utilities like events, logger, and the effect system.

Callable Services and Proxy Behavior

Cordis supports callable services through the Service.invoke symbol defined at lines 26-28. If your subclass implements this symbol, the constructor wraps the instance with a callable proxy using createCallable, allowing the service to be invoked directly as a function while retaining its methods and properties.

How to Extend a Service in Cordis

Creating a Basic Subclass

To create a custom service, extend the Service class, call super(ctx, name) in your constructor, and define your public methods. The subclass automatically registers itself in the Context and inherits full lifecycle management.

import { Context, Service } from 'cordis'

class GreeterService extends Service {
  constructor(ctx: Context) {
    super(ctx, 'greeter')          // registers as ctx.greeter
  }

  greet(name: string) {
    return `Hello, ${name}!`
  }
}

// Usage
const ctx = new Context()
ctx.registry.add(GreeterService)
ctx.greeter.greet('Alice') // → "Hello, Alice!"

Implementing Callable Services

To make your service callable like a function, implement the Service.invoke symbol. This creates a proxy that intercepts direct function calls while preserving method access.

class CounterService extends Service {
  static [Service.invoke] = Symbol('invoke') // enable callable proxy

  private count = 0

  [Service.invoke](step = 1) {
    this.count += step
    return this.count
  }

  reset() {
    this.count = 0
  }
}

// Usage
ctx.registry.add(CounterService)
ctx.counter(2)   // → 2
ctx.counter()    // → 3
ctx.counter.reset()

Runtime Extension and Inheritance

You can extend existing services at runtime using standard class inheritance. The new subclass inherits all functionality while adding or overriding methods, and ctx.registry.add() replaces the original registration.

class AdvancedGreeter extends GreeterService {
  shout(name: string) {
    return this.greet(name).toUpperCase()
  }
}

// Replace the original service dynamically
ctx.registry.add(AdvancedGreeter)
ctx.greeter.shout('Bob') // → "HELLO, BOB!"

Configuration and Advanced Features

Config Resolution with Service[resolveConfig]

The framework provides Service[resolveConfig] at lines 51-66 in packages/core/src/service.ts for merging configuration objects from the context hierarchy. This method supports custom merge logic and integrates with Context.intercept (defined in packages/core/src/context.ts at lines 9-26) to resolve layered configuration settings.

Internal Extension Mechanics

Cordis includes an internal extension helper Service[extend] at lines 41-48 that creates shallow copies (or callable copies) of services and merges additional properties. The framework uses this for plugin mocks and creating "shadow" services, though direct subclassing remains the recommended approach for application code.

Summary

  • A Service in Cordis is an abstract class in packages/core/src/service.ts that encapsulates functionality and automatically registers with a Context.
  • Extend services by subclassing Service, calling super(ctx, name), and implementing your methods.
  • Make services callable by defining the Service.invoke symbol, which triggers the createCallable proxy wrapper.
  • Access configuration merging through Service[resolveConfig] and context hierarchy isolation via Context.isolate.
  • Reference implementation examples exist in packages/timer/src/index.ts and test coverage in packages/core/tests/service.spec.ts.

Frequently Asked Questions

What file contains the Service class in Cordis?

The Service abstract class is located in packages/core/src/service.ts. This file defines the core lifecycle methods, callable service logic, and configuration resolution symbols used throughout the framework.

How do I make a Cordis service callable like a function?

Implement the static Service.invoke symbol in your subclass and define a method using that symbol as the key. The constructor detects this at lines 26-28 and wraps the instance with a callable proxy via createCallable, allowing direct invocation syntax like ctx.serviceName().

Can I extend an existing Cordis service at runtime?

Yes, use standard JavaScript class inheritance to create a subclass of the existing service, then register it with ctx.registry.add(). The new class inherits all parent methods and can override or extend functionality, effectively replacing the original service in the context.

What is the difference between Service[extend] and subclassing?

Subclassing creates a new class definition for permanent extension, while Service[extend] (lines 41-48) creates runtime shallow copies of existing service instances for temporary modifications or mocking. Application developers should use subclassing; Service[extend] is primarily for internal framework operations and testing utilities.

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 →