Service contracts are TypeScript interfaces that define the boundaries between the ObjectStack Kernel and its plugins. Every plugin implements one or more contracts, and the Kernel consumes them through dependency injection.
Design Principle: Contracts contain zero business logic. They define the shape of each service — method signatures, input types, and return types — so that implementations can be swapped without changing consumers.
Every contract is a plain TypeScript interface that depends only on shared types — never on a concrete implementation:
// packages/spec/src/contracts/data-engine.tsimport type { EngineQueryOptions, DataEngineInsertOptions, EngineUpdateOptions, EngineDeleteOptions,} from '../data/index.js';/** * IDataEngine — abstract interface for data persistence. * Plugins depend on this interface, not on concrete database implementations. */export interface IDataEngine { find(objectName: string, query?: EngineQueryOptions): Promise<any[]>; findOne(objectName: string, query?: EngineQueryOptions): Promise<any>; insert(objectName: string, data: any | any[], options?: DataEngineInsertOptions): Promise<any>; update(objectName: string, data: any, options?: EngineUpdateOptions): Promise<any>; delete(objectName: string, options?: EngineDeleteOptions): Promise<any>; // ...}
Convention: Contract interfaces are prefixed with I (e.g., IDataEngine). Services are registered and resolved by string name through the kernel service registry (e.g., ctx.registerService('data', impl) — see the CoreServiceName enum for the standard names).
Plugins declare which contracts they implement and which they consume:
import type { Plugin } from '@objectstack/core';import type { IDataEngine } from '@objectstack/spec/contracts';export const myPlugin: Plugin = { name: 'my-plugin', // Declare which other plugins must be initialized first dependencies: ['com.objectstack.cache'], // Init phase: register the services this plugin provides init(ctx) { ctx.registerService('data', new MyDataEngine(ctx)); // Consume a service another plugin registered const cache = ctx.getService('cache'); },};
flowchart LR
A[Plugin Registered] --> B[Dependencies Resolved]
B --> C[init: Services Registered]
C --> D[start: Service Started]
D --> E[Ready to Serve]
E --> F[destroy: Shutdown / Teardown]
Phase
Description
Registered
Plugin is added to the kernel with its name and dependencies
Resolved
Kernel resolves the dependency graph across all plugins
Initialized
Plugin init(ctx) hook runs — services are registered
Started
Plugin start(ctx) hook runs — connections established, servers started
Ready
Contract methods are available to the Kernel and other plugins