@objectstack/runtime
v3.0.6
Published
ObjectStack Core Runtime & Query Engine
Readme
@objectstack/runtime
ObjectStack Standard System Library
Overview
The runtime package provides the Standard Library for the ObjectStack Operating System. It bridges the pure ObjectKernel (from @objectstack/core) with the Data Engine (@objectstack/objectql) and provides essential infrastructure adapters.
Architecture Highlights
- Standard Library: Contains essential plugins (
AppPlugin,DriverPlugin) - Core Integration: Re-exports
ObjectKernelfor convenience - Capability Contracts: Abstract interfaces for HTTP server and data persistence
🤖 AI Development Context
Role: Server Runtime & REST API Usage:
- Use
RestServerto spawn HTTP endpoints. - Configures the HTTP layer.
Key Concepts:
RestServer: Generates/api/*endpoints from ObjectQL schemas.
Installation
npm install @objectstack/runtimeQuick Start
Basic Setup (Recommended)
import { ObjectKernel } from '@objectstack/core';
import { DriverPlugin, AppPlugin } from '@objectstack/runtime';
import { ObjectQLPlugin } from '@objectstack/objectql';
import { InMemoryDriver } from '@objectstack/driver-memory';
const kernel = new ObjectKernel();
kernel
// Register ObjectQL engine
.use(new ObjectQLPlugin())
// Add database driver
.use(new DriverPlugin(new InMemoryDriver(), 'memory'))
// Add your app configurations
// .use(new AppPlugin(appConfig));
await kernel.bootstrap();Custom ObjectQL Instance
If you have a separate ObjectQL implementation or need custom configuration:
import { ObjectKernel, DriverPlugin } from '@objectstack/runtime';
import { ObjectQLPlugin, ObjectQL } from '@objectstack/objectql';
// Create custom ObjectQL instance
const customQL = new ObjectQL({
env: 'production',
customConfig: true
});
// Pre-configure with custom hooks
customQL.registerHook('beforeInsert', async (ctx) => {
console.log(`Inserting into ${ctx.object}`);
});
const kernel = new ObjectKernel();
kernel
// Use your custom ObjectQL instance
.use(new ObjectQLPlugin(customQL))
// Add driver
.use(new DriverPlugin(new InMemoryDriver(), 'memory'));
await kernel.bootstrap();
// Access ObjectQL via service registry
const objectql = kernel.getService('objectql');Architecture
ObjectKernel (MiniKernel)
The kernel provides:
- Plugin Lifecycle Management: init → start → destroy phases
- Service Registry: Dependency injection container
- Event/Hook System: Inter-plugin communication
- Dependency Resolution: Topological sort for plugin dependencies
Built-in Plugins
ObjectQLPlugin
Registers the ObjectQL data engine as a service.
new ObjectQLPlugin() // Default instance
new ObjectQLPlugin(customQL) // Custom instance
new ObjectQLPlugin(undefined, { env: 'prod' }) // With contextServices: 'objectql'
DriverPlugin
Registers a data driver with ObjectQL.
new DriverPlugin(driver, 'driver-name')Dependencies: ['com.objectstack.engine.objectql']
AppPlugin
Wraps ObjectStack app manifests (objectstack.config.ts) as plugins.
new AppPlugin(appConfig)Services: 'app.{id}'
API Reference
Capability Contract Interfaces
IHttpServer
Abstract interface for HTTP server capabilities. Allows plugins to work with any HTTP framework (Express, Fastify, Hono, etc.) without tight coupling.
import { IHttpServer, IHttpRequest, IHttpResponse } from '@objectstack/runtime';
// In your HTTP server plugin
class MyHttpServerPlugin implements Plugin {
name = 'http-server';
async init(ctx: PluginContext) {
const server: IHttpServer = createMyServer(); // Express, Hono, etc.
ctx.registerService('http-server', server);
}
}
// In your API plugin
class MyApiPlugin implements Plugin {
name = 'api';
dependencies = ['http-server'];
async start(ctx: PluginContext) {
const server = ctx.getService<IHttpServer>('http-server');
// Register routes - works with any HTTP framework
server.get('/api/users', async (req, res) => {
res.json({ users: [] });
});
}
}Interface Methods:
get(path, handler)- Register GET routepost(path, handler)- Register POST routeput(path, handler)- Register PUT routedelete(path, handler)- Register DELETE routepatch(path, handler)- Register PATCH routeuse(path, handler?)- Register middlewarelisten(port)- Start serverclose()- Stop server (optional)
IDataEngine
Abstract interface for data persistence. Allows plugins to work with any data layer (ObjectQL, Prisma, TypeORM, etc.) without tight coupling.
import { IDataEngine } from '@objectstack/runtime';
// In your data plugin
class MyDataPlugin implements Plugin {
name = 'data';
async init(ctx: PluginContext) {
const engine: IDataEngine = createMyDataEngine(); // ObjectQL, Prisma, etc.
ctx.registerService('data-engine', engine);
}
}
// In your business logic plugin
class MyBusinessPlugin implements Plugin {
name = 'business';
dependencies = ['data'];
async start(ctx: PluginContext) {
const engine = ctx.getService<IDataEngine>('data-engine');
// CRUD operations - works with any data layer
const user = await engine.insert('user', { name: 'John' });
const users = await engine.find('user', { filter: { active: true } });
await engine.update('user', user.id, { name: 'Jane' });
await engine.delete('user', user.id);
}
}Interface Methods:
insert(objectName, data)- Create a recordfind(objectName, query?)- Query recordsupdate(objectName, id, data)- Update a recorddelete(objectName, id)- Delete a record
ObjectKernel
Methods
use(plugin: Plugin): Register a pluginbootstrap(): Initialize and start all pluginsshutdown(): Stop all plugins in reverse ordergetService<T>(name: string): Get a service from registryisRunning(): Check if kernel is runninggetState(): Get current kernel state
Plugin Interface
interface Plugin {
name: string; // Unique identifier
version?: string; // Plugin version
dependencies?: string[]; // Required plugin names
init(ctx: PluginContext): Promise<void>; // Register services
start?(ctx: PluginContext): Promise<void>; // Execute business logic
destroy?(): Promise<void>; // Cleanup
}PluginContext
interface PluginContext {
registerService(name: string, service: any): void;
getService<T>(name: string): T;
hook(name: string, handler: Function): void;
trigger(name: string, ...args: any[]): Promise<void>;
logger: Console;
getKernel?(): any;
}Examples
See the examples/ directory for complete examples:
examples/host/- Full server setup with Honoexamples/msw-react-crud/- Browser-based setup with MSWtest-mini-kernel.ts- Comprehensive kernel test suite- `packages/runtime/src/
Benefits of MiniKernel
- True Modularity: Each plugin is independent and reusable
- Capability Contracts: Plugins depend on interfaces, not implementations
- Testability: Mock services easily in tests
- Flexibility: Load plugins conditionally, swap implementations
- Extensibility: Add new plugins without modifying kernel
- Clear Dependencies: Explicit dependency declarations
- Better Architecture: Separation of concerns with Dependency Inversion
Best Practices
- Keep plugins focused: One responsibility per plugin
- Use services: Share functionality via service registry
- Declare dependencies: Make plugin requirements explicit
- Use hooks: Decouple plugins with event system
- Handle errors: Implement proper error handling in lifecycle methods
Common Plugin Patterns
Service Provider Pattern
import { Plugin, PluginContext } from '@objectstack/core';
export class DatabasePlugin implements Plugin {
name = 'database';
private connection: any;
async init(ctx: PluginContext) {
// Initialize connection
this.connection = await createConnection({
host: 'localhost',
database: 'myapp'
});
// Register as service
ctx.registerService('database', this.connection);
ctx.logger.info('Database connected');
}
async destroy() {
// Cleanup
await this.connection.close();
}
}Service Consumer Pattern
import { Plugin, PluginContext } from '@objectstack/core';
export class RepositoryPlugin implements Plugin {
name = 'repository';
dependencies = ['database']; // Ensure database loads first
async init(ctx: PluginContext) {
// Get dependency
const db = ctx.getService('database');
// Create and register repository
const repo = new UserRepository(db);
ctx.registerService('user-repository', repo);
}
}Event-Driven Pattern
import { Plugin, PluginContext } from '@objectstack/core';
// Publisher
export class DataPlugin implements Plugin {
name = 'data';
async init(ctx: PluginContext) {
const service = {
async create(entity: string, data: any) {
const result = await db.insert(entity, data);
// Trigger event
await ctx.trigger('data:created', { entity, data: result });
return result;
}
};
ctx.registerService('data', service);
}
}
// Subscriber
export class AuditPlugin implements Plugin {
name = 'audit';
dependencies = ['data'];
async init(ctx: PluginContext) {
// Listen to events
ctx.hook('data:created', async ({ entity, data }) => {
ctx.logger.info(`Audit: ${entity} created`, { id: data.id });
await auditLog.write({
action: 'create',
entity,
entityId: data.id,
timestamp: new Date()
});
});
}
}Configuration Pattern
import { Plugin, PluginContext } from '@objectstack/core';
import { z } from 'zod';
const ConfigSchema = z.object({
apiKey: z.string(),
endpoint: z.string().url(),
timeout: z.number().default(5000)
});
export class ApiPlugin implements Plugin {
name = 'api-client';
constructor(private config: z.infer<typeof ConfigSchema>) {
// Validate config
ConfigSchema.parse(config);
}
async init(ctx: PluginContext) {
const client = new ApiClient({
apiKey: this.config.apiKey,
endpoint: this.config.endpoint,
timeout: this.config.timeout
});
ctx.registerService('api-client', client);
}
}
// Usage
kernel.use(new ApiPlugin({
apiKey: process.env.API_KEY,
endpoint: 'https://api.example.com',
timeout: 10000
}));Factory Pattern
import { Plugin, PluginContext } from '@objectstack/core';
export class ConnectionPoolPlugin implements Plugin {
name = 'connection-pool';
private pool: any;
async init(ctx: PluginContext) {
this.pool = {
connections: new Map(),
getConnection(name: string) {
if (!this.connections.has(name)) {
this.connections.set(name, createConnection(name));
}
return this.connections.get(name);
},
closeAll() {
for (const conn of this.connections.values()) {
conn.close();
}
this.connections.clear();
}
};
ctx.registerService('connection-pool', this.pool);
}
async destroy() {
// Use stored reference from init phase
if (this.pool) {
this.pool.closeAll();
}
}
}Middleware Pattern
import { Plugin, PluginContext } from '@objectstack/core';
export class LoggingMiddleware implements Plugin {
name = 'logging-middleware';
dependencies = ['http-server'];
async start(ctx: PluginContext) {
const server = ctx.getService('http-server');
// Register middleware
server.use(async (req, res, next) => {
const start = Date.now();
ctx.logger.info('Request', {
method: req.method,
path: req.path
});
await next();
const duration = Date.now() - start;
ctx.logger.info('Response', {
method: req.method,
path: req.path,
status: res.statusCode,
duration
});
});
}
}Lazy Loading Pattern
import { Plugin, PluginContext } from '@objectstack/core';
export class HeavyServicePlugin implements Plugin {
name = 'heavy-service';
private instance: any = null;
async init(ctx: PluginContext) {
// Register factory instead of instance
const factory = {
async getInstance() {
if (!this.instance) {
ctx.logger.info('Lazy loading heavy service...');
this.instance = await loadHeavyService();
}
return this.instance;
}
};
ctx.registerService('heavy-service', factory);
}
}
// Usage
const factory = kernel.getService('heavy-service');
const service = await factory.getInstance(); // Loaded only when neededHealth Check Pattern
import { Plugin, PluginContext } from '@objectstack/core';
export class HealthCheckPlugin implements Plugin {
name = 'health-check';
dependencies = ['http-server', 'database', 'cache'];
async start(ctx: PluginContext) {
const server = ctx.getService('http-server');
server.get('/health', async (req, res) => {
const checks = await Promise.all([
this.checkDatabase(ctx),
this.checkCache(ctx),
this.checkDiskSpace()
]);
const healthy = checks.every(c => c.healthy);
res.status(healthy ? 200 : 503).json({
status: healthy ? 'healthy' : 'unhealthy',
checks
});
});
}
private async checkDatabase(ctx: PluginContext) {
try {
const db = ctx.getService('database');
await db.ping();
return { name: 'database', healthy: true };
} catch (error) {
return { name: 'database', healthy: false, error: error.message };
}
}
private async checkCache(ctx: PluginContext) {
try {
const cache = ctx.getService('cache');
await cache.ping();
return { name: 'cache', healthy: true };
} catch (error) {
return { name: 'cache', healthy: false, error: error.message };
}
}
private async checkDiskSpace() {
// Implementation
return { name: 'disk', healthy: true };
}
}Documentation
- MiniKernel Guide - Complete API documentation and patterns
- MiniKernel Architecture - Architecture diagrams and flows
- MiniKernel Implementation - Implementation details
License
Apache-2.0
