Cordis Service Initialization Sequence: How the Framework Boots Up
Cordis initializes services through a deterministic pipeline that creates a Context, registers Service instances via reflection, merges configurations through interceptor chains, and executes dependency-aware init hooks.
Cordis employs a systematic service initialization sequence that transforms plain configuration objects into fully wired applications. This process centers on the Context class and the abstract Service base defined in @cordis/core. Understanding this boot pipeline is essential for developers building plugins or custom services that must initialize in the correct order with access to the proper configuration.
Step 1: Context Creation via the Entry Point
The sequence begins when you call create() from the @cordis/create package. Located in packages/create/src/index.ts, this function instantiates a new Context object and immediately registers all built-in core services—including the logger, timer, and loader—before user-defined services are processed.
import { create } from '@cordis/create'
// Creates context and registers built-in services
const app = create({
myService: { greeting: 'Hello, Cordis!' }
})
Step 2: Service Construction and Self-Registration
When a service class extends the abstract Service base class from packages/core/src/service.ts, its constructor executes a specific registration protocol:
- Identification: The constructor reads the
static providefield to determine the service name (e.g.,static provide = 'myService'). - Tracking: It creates an internal tracker object that records the association between the service instance and its context.
- Reflection: It calls
ctx.reflect.provide(name, self, this[Service.check])to register the instance with the context’s reflector.
This self-registration mechanism ensures that every service automatically inserts itself into the framework's dependency graph upon instantiation.
Step 3: Registry Population
The ctx.reflect.provide method delegates to the registry implementation in packages/core/src/registry.ts. This registry maintains a map of service names to their instances and records dependency relationships. The registry is crucial for later phases because it tracks which services depend on others, enabling the framework to respect initialization order during the boot phase.
Step 4: Configuration Resolution
Before a service becomes fully operational, Cordis merges its configuration. The Service[Symbol.resolveConfig] method (defined in packages/core/src/service.ts) walks the context’s interceptor chain to compose the final configuration object. This allows multiple plugins or中间件 to contribute to or override service settings before the service starts consuming them.
Step 5: The Initialization Phase
Once all services are constructed and registered, the framework triggers the initialization phase by calling ctx.start() (or app.start()). The context iterates over the registry and, for any service that defines the static [Service.init] symbol, invokes that hook:
import { Service, Context } from '@cordis/core'
export class MyService extends Service<{ greeting: string }> {
static provide = 'myService'
static [Service.init] (ctx: Context) {
ctx.logger.info('MyService has been initialized')
}
}
This hook supports asynchronous operations, allowing services to open database connections, start timers, or perform network discovery before the application declares itself ready.
Step 6: Dependency-Aware Ordering
The initialization sequence respects isolates (plugin boundaries) and explicit dependency metadata. If a service declares dependsOn, the framework ensures those dependencies initialize first. The Context.isolate map ensures services only see configuration and sibling services belonging to the same isolate, preventing cross-plugin side effects and providing clean sandboxing.
Step 7: Application Ready State
After all [Service.init] hooks resolve successfully, the Context is fully operational. Users can retrieve service instances via ctx.get('serviceName') or access them directly on the application object if exported as proxies.
Complete Working Example
// src/my-service.ts
import { Service, Context } from '@cordis/core'
export class MyService extends Service<{ greeting: string }> {
static provide = 'myService'
static [Service.init] (ctx: Context) {
ctx.logger.info('MyService has been initialized')
}
get greeting() {
return this.config.greeting
}
}
// src/app.ts
import { create } from '@cordis/create'
import { MyService } from './my-service'
const app = create({
myService: { greeting: 'Hello, Cordis!' }
})
// Triggers the full initialization sequence
await app.start()
console.log(app.myService.greeting) // → "Hello, Cordis!"
Summary
create()inpackages/create/src/index.tsconstructs theContextand instantiates core services.- Service constructors in
packages/core/src/service.tsself-register viactx.reflect.provide()using thestatic provideidentifier. - The registry in
packages/core/src/registry.tstracks instances and dependencies. Service[Symbol.resolveConfig]merges configuration through the interceptor chain.[Service.init]hooks execute asynchronously duringctx.start()in dependency-aware order.- Isolates via
Context.isolateenforce plugin boundaries during initialization.
Frequently Asked Questions
What triggers the service initialization sequence in Cordis?
The sequence starts when you invoke create() from @cordis/create, which constructs a Context instance and immediately instantiates built-in services. The initialization phase completes when you explicitly call await app.start(), which triggers the [Service.init] hooks for all registered services.
How does Cordis handle dependencies between services during initialization?
Cordis tracks dependencies through the registry in packages/core/src/registry.ts and respects explicit dependsOn metadata declared on service classes. The initialization iterator processes services in a dependency-aware order, ensuring that prerequisite services complete their [Service.init] hooks before dependent services begin theirs.
Can service initialization hooks be asynchronous?
Yes. The [Service.init] static method can be async or return a Promise. The framework in packages/core/src/service.ts awaits these hooks during the ctx.start() phase, allowing services to perform asynchronous setup such as establishing database connections or loading remote configuration before the application becomes ready.
Where does Cordis store the mapping between service names and instances?
Cordis stores this mapping in the context's registry, implemented in packages/core/src/registry.ts. When a service constructor calls ctx.reflect.provide(), the framework populates this registry with the service name (derived from static provide), the instance reference, and dependency metadata, making services retrievable via ctx.get('serviceName').
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →