ObjectStack now supports a MiniKernel (Microkernel) Architecture that provides:
- High Modularity: Business logic is completely separated into plugins
- Dependency Injection: Service registry for inter-plugin communication
- Event/Hook System: Publish-subscribe mechanism for loose coupling
- Lifecycle Management: Standardized init/start/destroy phases
- Dependency Resolution: Automatic topological sorting based on plugin dependencies
The ObjectKernel is a minimal orchestrator that:
- Loads and manages plugins
- Provides a shared context (
PluginContext) - Handles lifecycle phases (init → start → running)
- Resolves plugin dependencies automatically
Plugins are independent modules with a standard interface:
interface Plugin {
name: string; // Unique identifier
version?: string; // Plugin version
dependencies?: string[]; // List of required plugin names
init(ctx: PluginContext): Promise<void>; // Register services
start?(ctx: PluginContext): Promise<void>; // Execute business logic
destroy?(): Promise<void>; // Cleanup
}The context provides plugins access to:
- Service Registry:
registerService(),getService() - Event System:
hook(),trigger() - Logger: Console-based logging
1. INIT PHASE
├── Plugin A: init() → Register services
├── Plugin B: init() → Register services
└── Plugin C: init() → Register services
2. START PHASE
├── Plugin A: start() → Execute business logic
├── Plugin B: start() → Execute business logic
└── Plugin C: start() → Execute business logic
3. KERNEL READY
└── Trigger 'kernel:ready' hook
4. DESTROY PHASE (on shutdown)
├── Plugin C: destroy() → Cleanup
├── Plugin B: destroy() → Cleanup
└── Plugin A: destroy() → Cleanup
import { ObjectKernel, Plugin, PluginContext } from '@objectstack/runtime';
class HelloPlugin implements Plugin {
name = 'hello-plugin';
async init(ctx: PluginContext) {
ctx.logger.log('[HelloPlugin] Initialized');
}
async start(ctx: PluginContext) {
ctx.logger.log('[HelloPlugin] Started');
}
}
const kernel = new ObjectKernel();
kernel.use(new HelloPlugin());
await kernel.bootstrap();class DataEnginePlugin implements Plugin {
name = 'data-engine';
async init(ctx: PluginContext) {
const db = {
connect: () => console.log('DB Connected'),
query: (sql: string) => `Result for ${sql}`
};
// Register service for other plugins
ctx.registerService('db', db);
}
async start(ctx: PluginContext) {
const db = ctx.getService<any>('db');
db.connect();
}
}class ApiPlugin implements Plugin {
name = 'api-server';
dependencies = ['data-engine', 'http-server']; // Will load after these
async init(ctx: PluginContext) {
// Dependencies guaranteed to be initialized
}
async start(ctx: PluginContext) {
const db = ctx.getService<any>('db');
const server = ctx.getService<any>('http-server');
// Use services from other plugins
server.get('/api/data', () => db.query('SELECT *'));
}
}class ServerPlugin implements Plugin {
name = 'server';
async start(ctx: PluginContext) {
// Wait for kernel:ready before starting server
ctx.hook('kernel:ready', () => {
console.log('Starting HTTP server on port 3000');
});
}
}ObjectQL is now a first-class plugin:
import { ObjectKernel, ObjectQLPlugin, DriverPlugin } from '@objectstack/runtime';
const kernel = new ObjectKernel();
kernel
.use(new ObjectQLPlugin()) // Register ObjectQL engine
.use(new DriverPlugin(memoryDriver, 'memory')); // Register driver
await kernel.bootstrap();
// Access ObjectQL via service registry
const objectql = kernel.getService('objectql');Registers the ObjectQL engine as a service.
new ObjectQLPlugin() // Default instance
new ObjectQLPlugin(customQL) // Custom instance
new ObjectQLPlugin(undefined, { env: 'prod' }) // With contextServices: objectql
Registers a driver with ObjectQL.
new DriverPlugin(driver, 'driver-name')Dependencies: ['com.objectstack.engine.objectql']
Provides HTTP server using Hono framework.
new HonoServerPlugin({ port: 3000, staticRoot: './public' })Services: http-server
interface KernelConfig {
plugins: Array<{
name: string;
enabled: boolean;
options?: any;
}>;
}
async function loadFromConfig(config: KernelConfig) {
const kernel = new ObjectKernel();
for (const pluginCfg of config.plugins) {
if (pluginCfg.enabled) {
const plugin = await loadPlugin(pluginCfg.name, pluginCfg.options);
kernel.use(plugin);
}
}
return kernel;
}class PluginFactory {
static createDataEngine(driver: any) {
return {
name: 'data-engine',
async init(ctx: PluginContext) {
ctx.registerService('driver', driver);
}
} as Plugin;
}
}
kernel.use(PluginFactory.createDataEngine(myDriver));const kernel = new ObjectKernel();
// Core plugins
kernel.use(new ObjectQLPlugin());
// Optional plugins based on environment
if (process.env.NODE_ENV === 'production') {
kernel.use(new MonitoringPlugin());
kernel.use(new CachingPlugin());
}
if (process.env.ENABLE_API === 'true') {
kernel.use(new HonoServerPlugin());
}import { ObjectKernel, ObjectQLPlugin, DriverPlugin } from '@objectstack/runtime';
const kernel = new ObjectKernel();
kernel
.use(new ObjectQLPlugin())
.use(new DriverPlugin(driver));
// App manifest as plugin
kernel.use(appManifestPlugin);
await kernel.bootstrap();- True Modularity: Each plugin is independent and reusable
- Testability: Mock services easily in tests
- Flexibility: Load plugins conditionally
- Extensibility: Add new plugins without modifying kernel
- Clear Dependencies: Explicit dependency declarations
- Better Architecture: Separation of concerns
- 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
- Document services: Document what services your plugin provides/consumes
Make sure the plugin that provides the service is registered and loaded before plugins that consume it.
Check your plugin dependencies - they form a cycle. Refactor to break the cycle.
You're registering the same plugin twice. Check your plugin registration code.
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
registerService(name: string, service: any): Register a servicegetService<T>(name: string): Get a servicehook(name: string, handler: Function): Register event handlertrigger(name: string, ...args: any[]): Trigger an eventlogger: Console logger instance
See:
/examples/mini-kernel-example.ts- Basic usage/test-objectql-plugin.ts- ObjectQL plugin examples/packages/plugin-hono-server/src/hono-plugin.ts- Real plugin implementation
Apache-2.0