From a49c48874f045f5691b0261a943cf9691bdbf5ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 02:54:51 +0000 Subject: [PATCH 1/5] Initial plan From 92f0485bc695690e4e727420baff2cb48a8bbd8d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 02:59:08 +0000 Subject: [PATCH 2/5] Refactor runtime and examples to use ObjectKernel (MiniKernel) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- examples/custom-objectql-example.ts | 18 ++-- examples/host/debug-registry.ts | 15 +-- examples/host/src/index.ts | 38 +++---- examples/msw-react-crud/src/mocks/browser.ts | 31 +++--- packages/runtime/src/app-manifest-plugin.ts | 68 ++++++++++++ packages/runtime/src/index.ts | 4 +- packages/runtime/src/protocol.ts | 103 ++++++++++++++++--- packages/runtime/src/types.ts | 3 +- 8 files changed, 214 insertions(+), 66 deletions(-) create mode 100644 packages/runtime/src/app-manifest-plugin.ts diff --git a/examples/custom-objectql-example.ts b/examples/custom-objectql-example.ts index 07b46309b..baeae033f 100644 --- a/examples/custom-objectql-example.ts +++ b/examples/custom-objectql-example.ts @@ -6,7 +6,7 @@ * custom configuration. */ -import { ObjectStackKernel, ObjectQLPlugin, ObjectQL } from '@objectstack/runtime'; +import { ObjectKernel, ObjectQLPlugin, DriverPlugin, ObjectQL } from '@objectstack/runtime'; import { InMemoryDriver } from '@objectstack/driver-memory'; (async () => { @@ -27,18 +27,22 @@ import { InMemoryDriver } from '@objectstack/driver-memory'; }); // Create kernel with the custom ObjectQL instance - const kernel = new ObjectStackKernel([ + const kernel = new ObjectKernel(); + + kernel // Register your custom ObjectQL instance - new ObjectQLPlugin(customQL), + .use(new ObjectQLPlugin(customQL)) // Add your driver - new InMemoryDriver(), + .use(new DriverPlugin(new InMemoryDriver(), 'memory')); // Add other plugins and app configs as needed - ]); - await kernel.start(); + await kernel.bootstrap(); console.log('✅ Kernel started with custom ObjectQL instance'); - console.log('ObjectQL instance:', kernel.ql); + + // Access ObjectQL via service registry + const objectql = kernel.getService('objectql'); + console.log('ObjectQL instance:', objectql); })(); diff --git a/examples/host/debug-registry.ts b/examples/host/debug-registry.ts index a625db048..31ca44788 100644 --- a/examples/host/debug-registry.ts +++ b/examples/host/debug-registry.ts @@ -1,5 +1,5 @@ -import { ObjectStackKernel, ObjectQLPlugin } from '@objectstack/runtime'; +import { ObjectKernel, ObjectQLPlugin, DriverPlugin, AppManifestPlugin } from '@objectstack/runtime'; import { SchemaRegistry, ObjectQL } from '@objectstack/objectql'; import { InMemoryDriver } from '@objectstack/driver-memory'; @@ -10,13 +10,14 @@ import TodoApp from '@objectstack/example-todo/objectstack.config'; console.log('Apps:', [TodoApp.name]); console.log('Objects inside App:', TodoApp.objects?.map((o: any) => o.name)); - const kernel = new ObjectStackKernel([ - new ObjectQLPlugin(), - TodoApp, - new InMemoryDriver() - ]); + const kernel = new ObjectKernel(); - await kernel.start(); + kernel + .use(new ObjectQLPlugin()) + .use(new DriverPlugin(new InMemoryDriver(), 'memory')) + .use(new AppManifestPlugin(TodoApp)); + + await kernel.bootstrap(); console.log('--- Post Start ---'); diff --git a/examples/host/src/index.ts b/examples/host/src/index.ts index 2d7d5e4f7..9e4730f9c 100644 --- a/examples/host/src/index.ts +++ b/examples/host/src/index.ts @@ -1,4 +1,4 @@ -import { ObjectStackKernel, ObjectQLPlugin, ObjectQL } from '@objectstack/runtime'; +import { ObjectKernel, ObjectQLPlugin, DriverPlugin, AppManifestPlugin, ObjectQL } from '@objectstack/runtime'; import { InMemoryDriver } from '@objectstack/driver-memory'; import { HonoServerPlugin } from '@objectstack/plugin-hono-server'; @@ -9,32 +9,26 @@ import BiPluginManifest from '@objectstack/plugin-bi/objectstack.config'; (async () => { console.log('🚀 Booting Kernel...'); - // Option 1: Use default ObjectQL via plugin (recommended) - const kernel = new ObjectStackKernel([ - // Register ObjectQL engine explicitly via plugin - new ObjectQLPlugin(), - - // App manifests - CrmApp, - TodoApp, - BiPluginManifest, + // Use MiniKernel architecture + const kernel = new ObjectKernel(); + + kernel + // Register ObjectQL engine + .use(new ObjectQLPlugin()) // Database driver - new InMemoryDriver(), + .use(new DriverPlugin(new InMemoryDriver(), 'memory')) + + // App manifests + .use(new AppManifestPlugin(CrmApp)) + .use(new AppManifestPlugin(TodoApp)) + .use(new AppManifestPlugin(BiPluginManifest)) // Load the Hono Server Plugin - new HonoServerPlugin({ + .use(new HonoServerPlugin({ port: 3004, staticRoot: './public' - }) - ]); - - // Option 2: Use custom ObjectQL instance - // const customQL = new ObjectQL({ env: 'production', customConfig: true }); - // const kernel = new ObjectStackKernel([ - // new ObjectQLPlugin(customQL), - // ...other plugins - // ]); + })); - await kernel.start(); + await kernel.bootstrap(); })(); diff --git a/examples/msw-react-crud/src/mocks/browser.ts b/examples/msw-react-crud/src/mocks/browser.ts index 79bb5dea5..a050018b8 100644 --- a/examples/msw-react-crud/src/mocks/browser.ts +++ b/examples/msw-react-crud/src/mocks/browser.ts @@ -5,13 +5,13 @@ * and the MSW Plugin which automatically exposes the API. */ -import { ObjectStackKernel, ObjectQLPlugin } from '@objectstack/runtime'; +import { ObjectKernel, ObjectQLPlugin, DriverPlugin, AppManifestPlugin } from '@objectstack/runtime'; import { InMemoryDriver } from '@objectstack/driver-memory'; import { MSWPlugin } from '@objectstack/plugin-msw'; // import appConfig from '../../objectstack.config'; import todoConfig from '@objectstack/example-todo/objectstack.config'; -let kernel: ObjectStackKernel | null = null; +let kernel: ObjectKernel | null = null; export async function startMockServer() { if (kernel) return; @@ -20,28 +20,27 @@ export async function startMockServer() { const driver = new InMemoryDriver(); - // Define Seed Data using the Dataset Protocol - // We use the data defined in the Todo App config + // Create kernel with MiniKernel architecture + kernel = new ObjectKernel(); - kernel = new ObjectStackKernel([ - // Register ObjectQL engine explicitly - new ObjectQLPlugin(), + kernel + // Register ObjectQL engine + .use(new ObjectQLPlugin()) - // Todo App Config (contains objects and data) - todoConfig, + // Register the driver + .use(new DriverPlugin(driver, 'memory')) - // In-Memory Database (runs in browser) - driver, + // Load todo app config as a plugin + .use(new AppManifestPlugin(todoConfig)) // MSW Plugin (intercepts network requests) - new MSWPlugin({ + .use(new MSWPlugin({ enableBrowser: true, baseUrl: '/api/v1', logRequests: true - }) - ]); - - await kernel.start(); + })); + + await kernel.bootstrap(); return kernel; } diff --git a/packages/runtime/src/app-manifest-plugin.ts b/packages/runtime/src/app-manifest-plugin.ts new file mode 100644 index 000000000..65ec819c4 --- /dev/null +++ b/packages/runtime/src/app-manifest-plugin.ts @@ -0,0 +1,68 @@ +import { Plugin, PluginContext } from './types.js'; +import { SchemaRegistry, ObjectQL } from '@objectstack/objectql'; + +/** + * AppManifestPlugin + * + * Wraps an ObjectStack app manifest (objectstack.config.ts export) + * as a Plugin so it can be loaded in the MiniKernel architecture. + * + * Handles: + * - Registering the app/plugin in SchemaRegistry + * - Registering all objects defined in the manifest + * - Seeding data if provided + * + * Dependencies: ['com.objectstack.engine.objectql'] + */ +export class AppManifestPlugin implements Plugin { + name: string; + version?: string; + dependencies = ['com.objectstack.engine.objectql']; + + private manifest: any; + + constructor(manifest: any) { + this.manifest = manifest; + this.name = manifest.id || manifest.name || 'unnamed-app'; + this.version = manifest.version; + } + + async init(ctx: PluginContext) { + ctx.logger.log(`[AppManifestPlugin] Loading App Manifest: ${this.manifest.id || this.manifest.name}`); + + // Register the app/plugin in the schema registry + SchemaRegistry.registerPlugin(this.manifest); + + // Register all objects defined in the manifest + if (this.manifest.objects) { + for (const obj of this.manifest.objects) { + SchemaRegistry.registerObject(obj); + ctx.logger.log(`[AppManifestPlugin] Registered Object: ${obj.name}`); + } + } + } + + async start(ctx: PluginContext) { + // Seed data if provided + if (this.manifest.data && Array.isArray(this.manifest.data)) { + ctx.logger.log(`[AppManifestPlugin] Seeding data for ${this.manifest.name || this.manifest.id}...`); + + const objectql = ctx.getService('objectql'); + + for (const seed of this.manifest.data) { + try { + // Check if data already exists + const existing = await objectql.find(seed.object, { top: 1 }); + if (existing.length === 0) { + ctx.logger.log(`[AppManifestPlugin] Inserting ${seed.records.length} records into ${seed.object}`); + for (const record of seed.records) { + await objectql.insert(seed.object, record); + } + } + } catch (e) { + ctx.logger.warn(`[AppManifestPlugin] Failed to seed ${seed.object}`, e); + } + } + } + } +} diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 96cdcc576..ef5d9042b 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -2,12 +2,14 @@ export { ObjectQL, SchemaRegistry } from '@objectstack/objectql'; // Export Kernels -export { ObjectStackKernel } from './kernel.js'; export { ObjectKernel } from './mini-kernel.js'; +// @deprecated Use ObjectKernel instead +export { ObjectStackKernel } from './kernel.js'; // Export Plugins export { ObjectQLPlugin } from './objectql-plugin.js'; export { DriverPlugin } from './driver-plugin.js'; +export { AppManifestPlugin } from './app-manifest-plugin.js'; // Export Protocol export { ObjectStackRuntimeProtocol } from './protocol.js'; diff --git a/packages/runtime/src/protocol.ts b/packages/runtime/src/protocol.ts index 76d8614f3..e7213496d 100644 --- a/packages/runtime/src/protocol.ts +++ b/packages/runtime/src/protocol.ts @@ -1,5 +1,6 @@ -import { SchemaRegistry } from '@objectstack/objectql'; +import { SchemaRegistry, ObjectQL } from '@objectstack/objectql'; import { ObjectStackKernel } from './kernel.js'; +import { ObjectKernel } from './mini-kernel.js'; export interface ApiRequest { params: Record; @@ -8,10 +9,40 @@ export interface ApiRequest { } export class ObjectStackRuntimeProtocol { - private engine: ObjectStackKernel; + private engine: ObjectStackKernel | ObjectKernel; + private legacyKernel?: ObjectStackKernel; + private objectql?: ObjectQL; - constructor(engine: ObjectStackKernel) { + constructor(engine: ObjectStackKernel | ObjectKernel) { this.engine = engine; + + // Detect which kernel type we're using + if (engine instanceof ObjectStackKernel) { + this.legacyKernel = engine; + } else if (engine instanceof ObjectKernel) { + // Get ObjectQL service from kernel + try { + this.objectql = engine.getService('objectql'); + } catch (e) { + console.warn('[Protocol] ObjectQL service not found in kernel'); + } + } + } + + /** + * Get ObjectQL instance - works with both kernel types + */ + private getObjectQL(): ObjectQL { + if (this.legacyKernel) { + if (!this.legacyKernel.ql) { + throw new Error('[Protocol] ObjectQL not initialized in legacy kernel'); + } + return this.legacyKernel.ql; + } + if (!this.objectql) { + throw new Error('[Protocol] ObjectQL service not available'); + } + return this.objectql; } // 1. Discovery @@ -92,38 +123,86 @@ export class ObjectStackRuntimeProtocol { // 5. UI: View Definition getUiView(objectName: string, type: 'list' | 'form') { - const view = this.engine.getView(objectName, type); - if (!view) throw new Error('View not generated'); - return view; + // Use legacy kernel method if available + if (this.legacyKernel) { + const view = this.legacyKernel.getView(objectName, type); + if (!view) throw new Error('View not generated'); + return view; + } + + // Otherwise generate view from schema + const schema = SchemaRegistry.getObject(objectName); + if (!schema) throw new Error(`Unknown object: ${objectName}`); + + if (type === 'list') { + return { + type: 'datagrid', + title: `${schema.label || objectName} List`, + columns: Object.keys(schema.fields || {}).map(key => ({ + field: key, + label: schema.fields?.[key]?.label || key, + width: 150 + })), + actions: ['create', 'edit', 'delete'] + }; + } + throw new Error('View not generated'); } // 6. Data: Find async findData(objectName: string, query: any) { - return await this.engine.find(objectName, query); + if (this.legacyKernel) { + return await this.legacyKernel.find(objectName, query); + } + const ql = this.getObjectQL(); + const results = await ql.find(objectName, { top: 100 }); + return { value: results, count: results.length }; } // 7. Data: Query (Advanced AST) async queryData(objectName: string, body: any) { - return await this.engine.find(objectName, body); + if (this.legacyKernel) { + return await this.legacyKernel.find(objectName, body); + } + const ql = this.getObjectQL(); + const results = await ql.find(objectName, { top: 100 }); + return { value: results, count: results.length }; } // 8. Data: Get async getData(objectName: string, id: string) { - return await this.engine.get(objectName, id); + if (this.legacyKernel) { + return await this.legacyKernel.get(objectName, id); + } + const ql = this.getObjectQL(); + const results = await ql.find(objectName, { top: 1 }); + return results[0]; } // 9. Data: Create async createData(objectName: string, body: any) { - return await this.engine.create(objectName, body); + if (this.legacyKernel) { + return await this.legacyKernel.create(objectName, body); + } + const ql = this.getObjectQL(); + return await ql.insert(objectName, body); } // 10. Data: Update async updateData(objectName: string, id: string, body: any) { - return await this.engine.update(objectName, id, body); + if (this.legacyKernel) { + return await this.legacyKernel.update(objectName, id, body); + } + const ql = this.getObjectQL(); + return await ql.update(objectName, id, body); } // 11. Data: Delete async deleteData(objectName: string, id: string) { - return await this.engine.delete(objectName, id); + if (this.legacyKernel) { + return await this.legacyKernel.delete(objectName, id); + } + const ql = this.getObjectQL(); + return await ql.delete(objectName, id); } } diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 69bbb7c4f..68c7d3879 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -1,11 +1,12 @@ import { ObjectStackKernel } from './kernel.js'; +import { ObjectKernel } from './mini-kernel.js'; /** * Legacy RuntimeContext (Backward Compatibility) * @deprecated Use PluginContext instead */ export interface RuntimeContext { - engine: ObjectStackKernel; + engine: ObjectStackKernel | ObjectKernel; } /** From 8e005e5390d072e63f7648786be1a71db2ad4db8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 03:02:29 +0000 Subject: [PATCH 3/5] Update MSW plugin and add getKernel to PluginContext Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/plugin-msw/src/msw-plugin.ts | 98 +++++++++++++++++++++++-- packages/runtime/src/mini-kernel.ts | 1 + packages/runtime/src/objectql-plugin.ts | 9 ++- packages/runtime/src/types.ts | 7 ++ 4 files changed, 107 insertions(+), 8 deletions(-) diff --git a/packages/plugin-msw/src/msw-plugin.ts b/packages/plugin-msw/src/msw-plugin.ts index a12786bda..0b3cb64f2 100644 --- a/packages/plugin-msw/src/msw-plugin.ts +++ b/packages/plugin-msw/src/msw-plugin.ts @@ -1,6 +1,13 @@ import { http, HttpResponse } from 'msw'; import { setupWorker } from 'msw/browser'; -import { RuntimePlugin, RuntimeContext, ObjectStackRuntimeProtocol } from '@objectstack/runtime'; +import { + RuntimePlugin, + RuntimeContext, + Plugin, + PluginContext, + ObjectStackRuntimeProtocol, + ObjectKernel +} from '@objectstack/runtime'; export interface MSWPluginOptions { /** @@ -149,10 +156,20 @@ export class ObjectStackServer { * This plugin enables Mock Service Worker integration for testing and development. * It automatically mocks API endpoints using the ObjectStack runtime protocol. * + * Supports both legacy RuntimePlugin and new Plugin interfaces. + * * @example * ```typescript * import { MSWPlugin } from '@objectstack/plugin-msw'; * + * // With new ObjectKernel + * const kernel = new ObjectKernel(); + * kernel.use(new MSWPlugin({ + * enableBrowser: true, + * baseUrl: '/api/v1' + * })); + * + * // With legacy ObjectStackKernel * const runtime = new ObjectStackRuntime({ * plugins: [ * new MSWPlugin({ @@ -163,11 +180,14 @@ export class ObjectStackServer { * }); * ``` */ -export class MSWPlugin implements RuntimePlugin { - name = 'msw'; +export class MSWPlugin implements Plugin, RuntimePlugin { + name = 'com.objectstack.plugin.msw'; + version = '1.0.0'; + private options: MSWPluginOptions; private worker: any; private handlers: Array = []; + private protocol?: ObjectStackRuntimeProtocol; constructor(options: MSWPluginOptions = {}) { this.options = { @@ -178,9 +198,69 @@ export class MSWPlugin implements RuntimePlugin { }; } + /** + * New Plugin interface - init phase + */ + async init(ctx: PluginContext) { + // Protocol will be created in start phase + ctx.logger.log('[MSWPlugin] Initialized'); + } + + /** + * New Plugin interface - start phase + */ + async start(ctx: PluginContext) { + // Get the kernel and create protocol + if (ctx.getKernel) { + const kernel = ctx.getKernel(); + this.protocol = new ObjectStackRuntimeProtocol(kernel); + } else { + ctx.logger.warn('[MSWPlugin] Cannot access kernel from context'); + } + + this.setupHandlers(); + await this.startWorker(); + } + + /** + * New Plugin interface - destroy phase + */ + async destroy() { + await this.stopWorker(); + } + + /** + * Legacy RuntimePlugin interface - install + */ install(ctx: RuntimeContext) { const { engine } = ctx; - const protocol = new ObjectStackRuntimeProtocol(engine); + this.protocol = new ObjectStackRuntimeProtocol(engine); + this.setupHandlers(); + } + + /** + * Legacy RuntimePlugin interface - onStart + */ + async onStart(ctx: RuntimeContext) { + await this.startWorker(); + } + + /** + * Legacy RuntimePlugin interface - onStop + */ + async onStop() { + await this.stopWorker(); + } + + /** + * Setup MSW handlers + */ + private setupHandlers() { + if (!this.protocol) { + throw new Error('[MSWPlugin] Protocol not initialized'); + } + + const protocol = this.protocol; // Initialize ObjectStackServer ObjectStackServer.init( @@ -312,7 +392,10 @@ export class MSWPlugin implements RuntimePlugin { console.log(`[MSWPlugin] Installed ${this.handlers.length} request handlers.`); } - async onStart(ctx: RuntimeContext) { + /** + * Start the MSW worker + */ + private async startWorker() { if (this.options.enableBrowser && typeof window !== 'undefined') { // Browser environment this.worker = setupWorker(...this.handlers); @@ -325,7 +408,10 @@ export class MSWPlugin implements RuntimePlugin { } } - async onStop() { + /** + * Stop the MSW worker + */ + private async stopWorker() { if (this.worker) { this.worker.stop(); console.log(`[MSWPlugin] Stopped MSW worker.`); diff --git a/packages/runtime/src/mini-kernel.ts b/packages/runtime/src/mini-kernel.ts index e14964f19..c103ffd66 100644 --- a/packages/runtime/src/mini-kernel.ts +++ b/packages/runtime/src/mini-kernel.ts @@ -52,6 +52,7 @@ export class ObjectKernel { } }, logger: console, + getKernel: () => this, }; /** diff --git a/packages/runtime/src/objectql-plugin.ts b/packages/runtime/src/objectql-plugin.ts index c1ef16f2a..c0404f144 100644 --- a/packages/runtime/src/objectql-plugin.ts +++ b/packages/runtime/src/objectql-plugin.ts @@ -72,8 +72,13 @@ export class ObjectQLPlugin implements Plugin, RuntimePlugin { */ async install(ctx: RuntimeContext) { // Attach the ObjectQL engine to the kernel for backward compatibility - ctx.engine.ql = this.ql; - console.log('[ObjectQLPlugin] ObjectQL engine registered (legacy mode)'); + // Only works with ObjectStackKernel (legacy kernel) + if ('ql' in ctx.engine) { + (ctx.engine as any).ql = this.ql; + console.log('[ObjectQLPlugin] ObjectQL engine registered (legacy mode)'); + } else { + console.log('[ObjectQLPlugin] Legacy install called on new kernel - skipping ql property assignment'); + } } /** diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 68c7d3879..34e8780d2 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -26,6 +26,7 @@ export interface RuntimePlugin { * - Service registry (registerService/getService) * - Event/Hook system (hook/trigger) * - Logger + * - Kernel instance (for advanced use cases) */ export interface PluginContext { /** @@ -61,6 +62,12 @@ export interface PluginContext { * Logger instance */ logger: Console; + + /** + * Get the kernel instance (for advanced use cases) + * @returns Kernel instance + */ + getKernel?(): any; } /** From b8a22bf518d8e563bbb19fc44a064b492b788c30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 03:04:11 +0000 Subject: [PATCH 4/5] Update documentation and deprecate ObjectStackKernel Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/runtime/README.md | 209 +++++++++++++++++++++------------ packages/runtime/src/kernel.ts | 32 ++++- 2 files changed, 166 insertions(+), 75 deletions(-) diff --git a/packages/runtime/README.md b/packages/runtime/README.md index b806bfeb5..caacde707 100644 --- a/packages/runtime/README.md +++ b/packages/runtime/README.md @@ -4,7 +4,15 @@ ObjectStack Core Runtime & Query Engine ## Overview -The runtime package provides the `ObjectStackKernel` - the central orchestrator for ObjectStack applications. It manages the application lifecycle, plugins, and the ObjectQL data engine. +The runtime package provides the `ObjectKernel` (MiniKernel) - a highly modular, plugin-based microkernel that orchestrates ObjectStack applications. It manages the application lifecycle through a standardized plugin system with dependency injection and event hooks. + +### Architecture Highlights + +- **MiniKernel Design**: 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 ## Installation @@ -12,26 +20,27 @@ The runtime package provides the `ObjectStackKernel` - the central orchestrator npm install @objectstack/runtime ``` -## Usage +## Quick Start -### Basic Setup +### Basic Setup (Recommended) ```typescript -import { ObjectStackKernel, ObjectQLPlugin } from '@objectstack/runtime'; +import { ObjectKernel, ObjectQLPlugin, DriverPlugin } from '@objectstack/runtime'; import { InMemoryDriver } from '@objectstack/driver-memory'; -const kernel = new ObjectStackKernel([ +const kernel = new ObjectKernel(); + +kernel // Register ObjectQL engine - new ObjectQLPlugin(), + .use(new ObjectQLPlugin()) // Add database driver - new InMemoryDriver(), + .use(new DriverPlugin(new InMemoryDriver(), 'memory')) // Add your app configurations - // appConfig, -]); + // .use(new AppManifestPlugin(appConfig)); -await kernel.start(); +await kernel.bootstrap(); ``` ### Custom ObjectQL Instance @@ -39,7 +48,7 @@ await kernel.start(); If you have a separate ObjectQL implementation or need custom configuration: ```typescript -import { ObjectStackKernel, ObjectQLPlugin, ObjectQL } from '@objectstack/runtime'; +import { ObjectKernel, ObjectQLPlugin, DriverPlugin, ObjectQL } from '@objectstack/runtime'; // Create custom ObjectQL instance const customQL = new ObjectQL({ @@ -52,111 +61,167 @@ customQL.registerHook('beforeInsert', async (ctx) => { console.log(`Inserting into ${ctx.object}`); }); -const kernel = new ObjectStackKernel([ +const kernel = new ObjectKernel(); + +kernel // Use your custom ObjectQL instance - new ObjectQLPlugin(customQL), + .use(new ObjectQLPlugin(customQL)) - // ... other plugins -]); + // Add driver + .use(new DriverPlugin(new InMemoryDriver(), 'memory')); -await kernel.start(); +await kernel.bootstrap(); + +// Access ObjectQL via service registry +const objectql = kernel.getService('objectql'); ``` -### Backward Compatibility +## Architecture -For backward compatibility, the kernel will automatically initialize ObjectQL if no `ObjectQLPlugin` is provided: +### 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. ```typescript -// This still works, but will show a deprecation warning -const kernel = new ObjectStackKernel([ - new InMemoryDriver(), - // ... other plugins -]); +new ObjectQLPlugin() // Default instance +new ObjectQLPlugin(customQL) // Custom instance +new ObjectQLPlugin(undefined, { env: 'prod' }) // With context ``` -## Architecture +**Services**: `'objectql'` -### ObjectStackKernel +#### DriverPlugin +Registers a data driver with ObjectQL. -The kernel is responsible for: -- Orchestrating application lifecycle -- Managing plugins -- Coordinating the ObjectQL engine -- Handling data operations +```typescript +new DriverPlugin(driver, 'driver-name') +``` + +**Dependencies**: `['com.objectstack.engine.objectql']` + +#### AppManifestPlugin +Wraps ObjectStack app manifests (objectstack.config.ts) as plugins. -### ObjectQLPlugin +```typescript +new AppManifestPlugin(appConfig) +``` -The `ObjectQLPlugin` provides: -- Explicit ObjectQL engine registration -- Support for custom ObjectQL instances -- Clean separation of concerns -- Better testability +**Dependencies**: `['com.objectstack.engine.objectql']` ## API Reference -### ObjectStackKernel +### ObjectKernel + +#### Methods +- `use(plugin: Plugin)`: Register a plugin +- `bootstrap()`: Initialize and start all plugins +- `shutdown()`: Stop all plugins in reverse order +- `getService(name: string)`: Get a service from registry +- `isRunning()`: Check if kernel is running +- `getState()`: Get current kernel state + +### Plugin Interface -#### Constructor ```typescript -constructor(plugins: any[] = []) +interface Plugin { + name: string; // Unique identifier + version?: string; // Plugin version + dependencies?: string[]; // Required plugin names + + init(ctx: PluginContext): Promise; // Register services + start?(ctx: PluginContext): Promise; // Execute business logic + destroy?(): Promise; // Cleanup +} ``` -#### Methods -- `start()`: Initialize and start the kernel -- `find(objectName, query)`: Query data -- `get(objectName, id)`: Get single record -- `create(objectName, data)`: Create record -- `update(objectName, id, data)`: Update record -- `delete(objectName, id)`: Delete record -- `getMetadata(objectName)`: Get object metadata -- `getView(objectName, viewType)`: Get UI view definition - -### ObjectQLPlugin - -#### Constructor +### PluginContext + ```typescript -constructor(ql?: ObjectQL, hostContext?: Record) +interface PluginContext { + registerService(name: string, service: any): void; + getService(name: string): T; + hook(name: string, handler: Function): void; + trigger(name: string, ...args: any[]): Promise; + logger: Console; + getKernel?(): any; +} ``` -#### Parameters -- `ql` (optional): Custom ObjectQL instance -- `hostContext` (optional): Host context configuration - ## Examples See the `examples/` directory for complete examples: - `examples/host/` - Full server setup with Hono - `examples/msw-react-crud/` - Browser-based setup with MSW - `examples/custom-objectql-example.ts` - Custom ObjectQL instance +- `test-mini-kernel.ts` - Comprehensive test suite ## Migration Guide -### From Hardcoded ObjectQL to Plugin-Based +### From ObjectStackKernel to ObjectKernel -**Before:** +**Before (Legacy):** ```typescript -const kernel = new ObjectStackKernel([appConfig, driver]); -``` +import { ObjectStackKernel, ObjectQLPlugin } from '@objectstack/runtime'; -**After (Recommended):** -```typescript const kernel = new ObjectStackKernel([ new ObjectQLPlugin(), - appConfig, + appConfig, driver ]); + +await kernel.start(); ``` -**After (Custom Instance):** +**After (Recommended):** ```typescript -const customQL = new ObjectQL({ /* config */ }); -const kernel = new ObjectStackKernel([ - new ObjectQLPlugin(customQL), - appConfig, - driver -]); +import { ObjectKernel, ObjectQLPlugin, DriverPlugin, AppManifestPlugin } from '@objectstack/runtime'; + +const kernel = new ObjectKernel(); + +kernel + .use(new ObjectQLPlugin()) + .use(new DriverPlugin(driver, 'memory')) + .use(new AppManifestPlugin(appConfig)); + +await kernel.bootstrap(); ``` +## Benefits of MiniKernel + +1. **True Modularity**: Each plugin is independent and reusable +2. **Testability**: Mock services easily in tests +3. **Flexibility**: Load plugins conditionally +4. **Extensibility**: Add new plugins without modifying kernel +5. **Clear Dependencies**: Explicit dependency declarations +6. **Better Architecture**: Separation of concerns + +## Best Practices + +1. **Keep plugins focused**: One responsibility per plugin +2. **Use services**: Share functionality via service registry +3. **Declare dependencies**: Make plugin requirements explicit +4. **Use hooks**: Decouple plugins with event system +5. **Handle errors**: Implement proper error handling in lifecycle methods + +## Documentation + +- [MiniKernel Guide](../../MINI_KERNEL_GUIDE.md) - Complete API documentation and patterns +- [MiniKernel Architecture](../../MINI_KERNEL_ARCHITECTURE.md) - Architecture diagrams and flows +- [MiniKernel Implementation](../../MINI_KERNEL_IMPLEMENTATION.md) - Implementation details + +## Legacy Support + +The `ObjectStackKernel` is still available for backward compatibility but is deprecated. New projects should use `ObjectKernel`. + ## License -MIT +Apache-2.0 diff --git a/packages/runtime/src/kernel.ts b/packages/runtime/src/kernel.ts index 0a4a6cfd3..dc530b25b 100644 --- a/packages/runtime/src/kernel.ts +++ b/packages/runtime/src/kernel.ts @@ -2,10 +2,36 @@ import { ServiceObject } from '@objectstack/spec/data'; import { SchemaRegistry, ObjectQL } from '@objectstack/objectql'; /** - * ObjectStack Kernel (Microkernel) + * ObjectStack Kernel (Legacy) * - * The central container orchestrating the application lifecycle, - * plugins, and the core ObjectQL engine. + * @deprecated Use ObjectKernel (MiniKernel) instead for better modularity and plugin architecture. + * This class is kept for backward compatibility but new projects should use ObjectKernel. + * + * @see ObjectKernel - The new MiniKernel implementation + * @see MINI_KERNEL_GUIDE.md - Complete guide to the new architecture + * + * The legacy kernel orchestrates the application lifecycle, + * plugins, and the core ObjectQL engine, but with less modularity + * than the new ObjectKernel implementation. + * + * Migration example: + * ```typescript + * // Old (deprecated): + * const kernel = new ObjectStackKernel([ + * new ObjectQLPlugin(), + * appConfig, + * driver + * ]); + * await kernel.start(); + * + * // New (recommended): + * const kernel = new ObjectKernel(); + * kernel + * .use(new ObjectQLPlugin()) + * .use(new DriverPlugin(driver, 'memory')) + * .use(new AppManifestPlugin(appConfig)); + * await kernel.bootstrap(); + * ``` */ export class ObjectStackKernel { public ql?: ObjectQL; // Will be set by ObjectQLPlugin or fallback initialization From 068c40f8e40d6a1fb29abfff0cab4c773e3ffe6f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 03:07:34 +0000 Subject: [PATCH 5/5] Fix code review issues: improve error handling and type safety Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/plugin-msw/src/msw-plugin.ts | 2 +- packages/runtime/src/index.ts | 7 ++++++- packages/runtime/src/protocol.ts | 17 +++++++++++------ packages/runtime/src/types.ts | 2 +- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/plugin-msw/src/msw-plugin.ts b/packages/plugin-msw/src/msw-plugin.ts index 0b3cb64f2..a15e147b0 100644 --- a/packages/plugin-msw/src/msw-plugin.ts +++ b/packages/plugin-msw/src/msw-plugin.ts @@ -215,7 +215,7 @@ export class MSWPlugin implements Plugin, RuntimePlugin { const kernel = ctx.getKernel(); this.protocol = new ObjectStackRuntimeProtocol(kernel); } else { - ctx.logger.warn('[MSWPlugin] Cannot access kernel from context'); + throw new Error('[MSWPlugin] Cannot access kernel from context - getKernel() not available'); } this.setupHandlers(); diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index ef5d9042b..8558de084 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -3,7 +3,12 @@ export { ObjectQL, SchemaRegistry } from '@objectstack/objectql'; // Export Kernels export { ObjectKernel } from './mini-kernel.js'; -// @deprecated Use ObjectKernel instead + +/** + * @deprecated Use ObjectKernel instead for better modularity and plugin architecture. + * ObjectStackKernel is kept for backward compatibility only. + * @see {ObjectKernel} - The recommended MiniKernel implementation + */ export { ObjectStackKernel } from './kernel.js'; // Export Plugins diff --git a/packages/runtime/src/protocol.ts b/packages/runtime/src/protocol.ts index e7213496d..23f108184 100644 --- a/packages/runtime/src/protocol.ts +++ b/packages/runtime/src/protocol.ts @@ -20,17 +20,20 @@ export class ObjectStackRuntimeProtocol { if (engine instanceof ObjectStackKernel) { this.legacyKernel = engine; } else if (engine instanceof ObjectKernel) { - // Get ObjectQL service from kernel + // Get ObjectQL service from kernel - will be validated when needed try { this.objectql = engine.getService('objectql'); } catch (e) { - console.warn('[Protocol] ObjectQL service not found in kernel'); + // Don't fail construction - some protocol methods may still work + // Error will be thrown when getObjectQL() is called + console.warn('[Protocol] ObjectQL service not found in kernel - data operations will fail'); } } } /** * Get ObjectQL instance - works with both kernel types + * @throws Error if ObjectQL is not available */ private getObjectQL(): ObjectQL { if (this.legacyKernel) { @@ -40,7 +43,7 @@ export class ObjectStackRuntimeProtocol { return this.legacyKernel.ql; } if (!this.objectql) { - throw new Error('[Protocol] ObjectQL service not available'); + throw new Error('[Protocol] ObjectQL service not available in kernel. Ensure ObjectQLPlugin is registered and initialized.'); } return this.objectql; } @@ -155,7 +158,7 @@ export class ObjectStackRuntimeProtocol { return await this.legacyKernel.find(objectName, query); } const ql = this.getObjectQL(); - const results = await ql.find(objectName, { top: 100 }); + const results = await ql.find(objectName, query || { top: 100 }); return { value: results, count: results.length }; } @@ -165,7 +168,7 @@ export class ObjectStackRuntimeProtocol { return await this.legacyKernel.find(objectName, body); } const ql = this.getObjectQL(); - const results = await ql.find(objectName, { top: 100 }); + const results = await ql.find(objectName, body || { top: 100 }); return { value: results, count: results.length }; } @@ -175,7 +178,9 @@ export class ObjectStackRuntimeProtocol { return await this.legacyKernel.get(objectName, id); } const ql = this.getObjectQL(); - const results = await ql.find(objectName, { top: 1 }); + // TODO: Implement proper ID-based lookup once ObjectQL supports it + // For now, this is a limitation of the current ObjectQL API + const results = await ql.find(objectName, { top: 1, filter: { id } }); return results[0]; } diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 34e8780d2..f292f2c41 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -67,7 +67,7 @@ export interface PluginContext { * Get the kernel instance (for advanced use cases) * @returns Kernel instance */ - getKernel?(): any; + getKernel?(): ObjectStackKernel | ObjectKernel; } /**