ObjectStackObjectStack

Service Contracts Overview

Reference for all ObjectStack service contracts — TypeScript interfaces that define the API between the Kernel and plugins

Service Contracts Overview

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.


Why Contracts?

BenefitDescription
DecouplingThe Kernel never depends on a concrete implementation
TestabilityMock any service by implementing its contract interface
PortabilitySwap SQL for NoSQL, local auth for OAuth — same contract
Type SafetyFull TypeScript inference from Zod schemas to contract interfaces
Plugin EcosystemThird-party plugins implement contracts to extend the platform

Contract Catalog

Data Contracts

ContractInterfaceDescription
Data EngineIDataEngineCore data persistence — CRUD, queries, aggregations, transactions
Metadata ServiceIMetadataServiceObject and field definition management, schema registry
Search ServiceISearchServiceFull-text search indexing and querying
Data DriverIDataDriverLow-level database adapter (SQL, NoSQL, API)

Authentication & Security Contracts

ContractInterfaceDescription
Auth ServiceIAuthServiceAuthentication — login, verify, logout, session management
Security ServiceISecurityServiceQuery surface for access decisions — row-level read scope, readable-field projection, effective permission sets, access explanation
Sharing ServiceISharingServiceRecord sharing rules and access grants

Storage & Caching Contracts

ContractInterfaceDescription
Storage ServiceIStorageServiceFile upload, download, and management
Cache ServiceICacheServiceKey-value caching with TTL support

System Contracts

ContractInterfaceDescription
Realtime ServiceIRealtimeServicePublish/subscribe event system for triggers and realtime
Lifecycle EventsIPluginLifecycleEventsTyped plugin and kernel lifecycle events
LoggerLoggerStructured logging with levels and context
Service RegistryIServiceRegistryService registration and resolution

Integration Contracts

ContractInterfaceDescription
Email ServiceIEmailServiceTransactional and bulk email delivery
Notification ServiceINotificationServiceMulti-channel notifications (email, push, in-app)
Job ServiceIJobServiceCron-based background job scheduling and execution

AI Contracts

ContractInterfaceDescription
AI ServiceIAIServiceLLM inference — chat, completion, embedding
Knowledge ServiceIKnowledgeServiceDocument indexing, retrieval, and augmented generation

Contract Structure

Every contract is a plain TypeScript interface that depends only on shared types — never on a concrete implementation:

// packages/spec/src/contracts/data-engine.ts
import 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).


Using Contracts in Plugins

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');
  },
};

Contract Lifecycle

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]
PhaseDescription
RegisteredPlugin is added to the kernel with its name and dependencies
ResolvedKernel resolves the dependency graph across all plugins
InitializedPlugin init(ctx) hook runs — services are registered
StartedPlugin start(ctx) hook runs — connections established, servers started
ReadyContract methods are available to the Kernel and other plugins
TeardownPlugin destroy() hook runs — connections closed, resources freed

See also:

On this page