diff --git a/OBJECTQL_PLUGIN_QUICKSTART.md b/OBJECTQL_PLUGIN_QUICKSTART.md new file mode 100644 index 000000000..7d0df3dcb --- /dev/null +++ b/OBJECTQL_PLUGIN_QUICKSTART.md @@ -0,0 +1,225 @@ +# ObjectQL Plugin - Quick Reference + +## Installation + +```bash +npm install @objectstack/runtime +``` + +## Basic Usage + +### Default ObjectQL (Recommended) + +```typescript +import { ObjectStackKernel, ObjectQLPlugin } from '@objectstack/runtime'; + +const kernel = new ObjectStackKernel([ + new ObjectQLPlugin(), + // ... other plugins +]); + +await kernel.start(); +``` + +### Custom ObjectQL Instance + +```typescript +import { ObjectStackKernel, ObjectQLPlugin, ObjectQL } from '@objectstack/runtime'; + +// Create custom instance +const customQL = new ObjectQL({ + env: 'production', + // custom options +}); + +// Configure as needed +customQL.registerHook('beforeInsert', async (ctx) => { + console.log(`Inserting into ${ctx.object}`); +}); + +// Use in kernel +const kernel = new ObjectStackKernel([ + new ObjectQLPlugin(customQL), + // ... other plugins +]); + +await kernel.start(); +``` + +### Backward Compatible (Legacy) + +```typescript +// Still works without ObjectQLPlugin, but shows warning +const kernel = new ObjectStackKernel([ + // ... plugins +]); +``` + +## API Reference + +### ObjectQLPlugin Constructor + +```typescript +new ObjectQLPlugin(ql?: ObjectQL, hostContext?: Record) +``` + +**Parameters:** +- `ql` (optional): Custom ObjectQL instance to use +- `hostContext` (optional): Configuration for new ObjectQL instance (ignored if `ql` provided) + +**Note:** If both parameters are provided, `hostContext` is ignored with a warning. + +### Examples + +```typescript +// Create with default settings +new ObjectQLPlugin() + +// Use custom instance +const custom = new ObjectQL({ env: 'prod' }); +new ObjectQLPlugin(custom) + +// Create with custom context +new ObjectQLPlugin(undefined, { env: 'prod', debug: true }) +``` + +## Migration Guide + +### From Hardcoded to Plugin-Based + +**Before:** +```typescript +const kernel = new ObjectStackKernel([appConfig, driver]); +``` + +**After:** +```typescript +const kernel = new ObjectStackKernel([ + new ObjectQLPlugin(), // Add this line + appConfig, + driver +]); +``` + +## Common Patterns + +### Testing with Mock ObjectQL + +```typescript +import { ObjectQLPlugin } from '@objectstack/runtime'; +import { MockObjectQL } from './mocks'; + +const mockQL = new MockObjectQL(); +const kernel = new ObjectStackKernel([ + new ObjectQLPlugin(mockQL), + // ... test config +]); +``` + +### Multiple Environments + +```typescript +// Production +const prodQL = new ObjectQL({ env: 'production', cache: true }); +const prodKernel = new ObjectStackKernel([ + new ObjectQLPlugin(prodQL), + // ... production plugins +]); + +// Development +const devKernel = new ObjectStackKernel([ + new ObjectQLPlugin(undefined, { env: 'development', debug: true }), + // ... dev plugins +]); +``` + +### Custom ObjectQL from Separate Project + +```typescript +// Your custom implementation +import { MyCustomObjectQL } from '@mycompany/custom-objectql'; + +const customQL = new MyCustomObjectQL({ + specialFeature: true, + // custom options +}); + +const kernel = new ObjectStackKernel([ + new ObjectQLPlugin(customQL), + // ... other plugins +]); +``` + +## Troubleshooting + +### Error: "ObjectQL engine not initialized" + +This means the kernel tried to use ObjectQL before it was set up. Make sure: +1. You include `ObjectQLPlugin` in your plugins array, OR +2. The kernel has backward compatibility enabled + +### Warning: "No ObjectQL plugin found..." + +This is a deprecation warning. Your code will work, but consider migrating to: + +```typescript +new ObjectStackKernel([ + new ObjectQLPlugin(), // Add this + // ... other plugins +]); +``` + +### Warning: "Both ql and hostContext provided..." + +You passed both a custom ObjectQL instance and host context: + +```typescript +// ❌ Don't do this +new ObjectQLPlugin(customQL, { env: 'prod' }) // hostContext ignored + +// ✅ Do this instead +new ObjectQLPlugin(customQL) // Use custom instance + +// ✅ Or this +new ObjectQLPlugin(undefined, { env: 'prod' }) // Create with context +``` + +## Advanced + +### Type-Based Detection + +ObjectQLPlugin uses a `type` field for reliable detection: + +```typescript +// Check if a plugin is an ObjectQL plugin +const isObjectQLPlugin = plugin && plugin.type === 'objectql'; +``` + +The plugin sets `type = 'objectql'` which aligns with the manifest schema that supports package types: 'app', 'plugin', 'driver', 'module', 'objectql', 'gateway', 'adapter'. + +### Type Safety + +The kernel's `ql` property is typed as optional: + +```typescript +export class ObjectStackKernel { + public ql?: ObjectQL; + + private ensureObjectQL(): ObjectQL { + if (!this.ql) { + throw new Error('ObjectQL engine not initialized'); + } + return this.ql; + } +} +``` + +## Resources + +- [Full Documentation](./packages/runtime/README.md) +- [Implementation Summary](./OBJECTQL_PLUGIN_SUMMARY.md) +- [Custom ObjectQL Example](./examples/custom-objectql-example.ts) + +## License + +Apache-2.0 diff --git a/content/docs/references/system/manifest/Manifest.mdx b/content/docs/references/system/manifest/Manifest.mdx index 2d411d92f..f4633e993 100644 --- a/content/docs/references/system/manifest/Manifest.mdx +++ b/content/docs/references/system/manifest/Manifest.mdx @@ -9,7 +9,7 @@ description: Manifest Schema Reference | :--- | :--- | :--- | :--- | | **id** | `string` | ✅ | Unique package identifier (reverse domain style) | | **version** | `string` | ✅ | Package version (semantic versioning) | -| **type** | `Enum<'app' \| 'plugin' \| 'driver' \| 'module'>` | ✅ | Type of package | +| **type** | `Enum<'app' \| 'plugin' \| 'driver' \| 'module' \| 'objectql' \| 'gateway' \| 'adapter'>` | ✅ | Type of package | | **name** | `string` | ✅ | Human-readable package name | | **description** | `string` | optional | Package description | | **permissions** | `string[]` | optional | Array of required permission strings | diff --git a/examples/custom-objectql-example.ts b/examples/custom-objectql-example.ts new file mode 100644 index 000000000..07b46309b --- /dev/null +++ b/examples/custom-objectql-example.ts @@ -0,0 +1,44 @@ +/** + * Example: Custom ObjectQL Instance + * + * This demonstrates how to use a custom ObjectQL instance with the kernel. + * This is useful when you have a separate ObjectQL implementation or need + * custom configuration. + */ + +import { ObjectStackKernel, ObjectQLPlugin, ObjectQL } from '@objectstack/runtime'; +import { InMemoryDriver } from '@objectstack/driver-memory'; + +(async () => { + console.log('🚀 Example: Custom ObjectQL Instance...'); + + // Create a custom ObjectQL instance with specific configuration + const customQL = new ObjectQL({ + env: 'development', + // Add any custom host context here + customFeature: true, + debug: true + }); + + // You can also pre-configure the ObjectQL instance + // For example, register custom hooks + customQL.registerHook('beforeInsert', async (ctx) => { + console.log(`[Custom Hook] Before inserting into ${ctx.object}`); + }); + + // Create kernel with the custom ObjectQL instance + const kernel = new ObjectStackKernel([ + // Register your custom ObjectQL instance + new ObjectQLPlugin(customQL), + + // Add your driver + new InMemoryDriver(), + + // Add other plugins and app configs as needed + ]); + + await kernel.start(); + + console.log('✅ Kernel started with custom ObjectQL instance'); + console.log('ObjectQL instance:', kernel.ql); +})(); diff --git a/examples/host/debug-registry.ts b/examples/host/debug-registry.ts index 34c5031db..a625db048 100644 --- a/examples/host/debug-registry.ts +++ b/examples/host/debug-registry.ts @@ -1,5 +1,5 @@ -import { ObjectStackKernel } from '@objectstack/runtime'; +import { ObjectStackKernel, ObjectQLPlugin } from '@objectstack/runtime'; import { SchemaRegistry, ObjectQL } from '@objectstack/objectql'; import { InMemoryDriver } from '@objectstack/driver-memory'; @@ -11,6 +11,7 @@ import TodoApp from '@objectstack/example-todo/objectstack.config'; console.log('Objects inside App:', TodoApp.objects?.map((o: any) => o.name)); const kernel = new ObjectStackKernel([ + new ObjectQLPlugin(), TodoApp, new InMemoryDriver() ]); diff --git a/examples/host/src/index.ts b/examples/host/src/index.ts index c72621005..2d7d5e4f7 100644 --- a/examples/host/src/index.ts +++ b/examples/host/src/index.ts @@ -1,4 +1,4 @@ -import { ObjectStackKernel } from '@objectstack/runtime'; +import { ObjectStackKernel, ObjectQLPlugin, ObjectQL } from '@objectstack/runtime'; import { InMemoryDriver } from '@objectstack/driver-memory'; import { HonoServerPlugin } from '@objectstack/plugin-hono-server'; @@ -9,10 +9,17 @@ 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, + + // Database driver new InMemoryDriver(), // Load the Hono Server Plugin @@ -22,5 +29,12 @@ import BiPluginManifest from '@objectstack/plugin-bi/objectstack.config'; }) ]); + // 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(); })(); diff --git a/examples/msw-react-crud/src/mocks/browser.ts b/examples/msw-react-crud/src/mocks/browser.ts index 46eb12040..79bb5dea5 100644 --- a/examples/msw-react-crud/src/mocks/browser.ts +++ b/examples/msw-react-crud/src/mocks/browser.ts @@ -5,7 +5,7 @@ * and the MSW Plugin which automatically exposes the API. */ -import { ObjectStackKernel } from '@objectstack/runtime'; +import { ObjectStackKernel, ObjectQLPlugin } from '@objectstack/runtime'; import { InMemoryDriver } from '@objectstack/driver-memory'; import { MSWPlugin } from '@objectstack/plugin-msw'; // import appConfig from '../../objectstack.config'; @@ -24,6 +24,9 @@ export async function startMockServer() { // We use the data defined in the Todo App config kernel = new ObjectStackKernel([ + // Register ObjectQL engine explicitly + new ObjectQLPlugin(), + // Todo App Config (contains objects and data) todoConfig, diff --git a/packages/runtime/README.md b/packages/runtime/README.md new file mode 100644 index 000000000..b806bfeb5 --- /dev/null +++ b/packages/runtime/README.md @@ -0,0 +1,162 @@ +# @objectstack/runtime + +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. + +## Installation + +```bash +npm install @objectstack/runtime +``` + +## Usage + +### Basic Setup + +```typescript +import { ObjectStackKernel, ObjectQLPlugin } from '@objectstack/runtime'; +import { InMemoryDriver } from '@objectstack/driver-memory'; + +const kernel = new ObjectStackKernel([ + // Register ObjectQL engine + new ObjectQLPlugin(), + + // Add database driver + new InMemoryDriver(), + + // Add your app configurations + // appConfig, +]); + +await kernel.start(); +``` + +### Custom ObjectQL Instance + +If you have a separate ObjectQL implementation or need custom configuration: + +```typescript +import { ObjectStackKernel, ObjectQLPlugin, ObjectQL } from '@objectstack/runtime'; + +// 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 ObjectStackKernel([ + // Use your custom ObjectQL instance + new ObjectQLPlugin(customQL), + + // ... other plugins +]); + +await kernel.start(); +``` + +### Backward Compatibility + +For backward compatibility, the kernel will automatically initialize ObjectQL if no `ObjectQLPlugin` is provided: + +```typescript +// This still works, but will show a deprecation warning +const kernel = new ObjectStackKernel([ + new InMemoryDriver(), + // ... other plugins +]); +``` + +## Architecture + +### ObjectStackKernel + +The kernel is responsible for: +- Orchestrating application lifecycle +- Managing plugins +- Coordinating the ObjectQL engine +- Handling data operations + +### ObjectQLPlugin + +The `ObjectQLPlugin` provides: +- Explicit ObjectQL engine registration +- Support for custom ObjectQL instances +- Clean separation of concerns +- Better testability + +## API Reference + +### ObjectStackKernel + +#### Constructor +```typescript +constructor(plugins: any[] = []) +``` + +#### 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 +```typescript +constructor(ql?: ObjectQL, hostContext?: Record) +``` + +#### 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 + +## Migration Guide + +### From Hardcoded ObjectQL to Plugin-Based + +**Before:** +```typescript +const kernel = new ObjectStackKernel([appConfig, driver]); +``` + +**After (Recommended):** +```typescript +const kernel = new ObjectStackKernel([ + new ObjectQLPlugin(), + appConfig, + driver +]); +``` + +**After (Custom Instance):** +```typescript +const customQL = new ObjectQL({ /* config */ }); +const kernel = new ObjectStackKernel([ + new ObjectQLPlugin(customQL), + appConfig, + driver +]); +``` + +## License + +MIT diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 6ca89c784..d8fdb0a3b 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -1,6 +1,7 @@ // Export core engine export { ObjectQL, SchemaRegistry } from '@objectstack/objectql'; export { ObjectStackKernel } from './kernel'; +export { ObjectQLPlugin } from './objectql-plugin'; export { ObjectStackRuntimeProtocol } from './protocol'; export * from './types'; diff --git a/packages/runtime/src/kernel.ts b/packages/runtime/src/kernel.ts index 0d0b4efa4..0a4a6cfd3 100644 --- a/packages/runtime/src/kernel.ts +++ b/packages/runtime/src/kernel.ts @@ -8,15 +8,36 @@ import { SchemaRegistry, ObjectQL } from '@objectstack/objectql'; * plugins, and the core ObjectQL engine. */ export class ObjectStackKernel { - public ql: ObjectQL; + public ql?: ObjectQL; // Will be set by ObjectQLPlugin or fallback initialization private plugins: any[]; constructor(plugins: any[] = []) { - // 1. Initialize Engine with Host Context (Simulated OS services) - this.ql = new ObjectQL({ - env: process.env.NODE_ENV || 'development' - }); this.plugins = plugins; + + // Check if any plugin provides ObjectQL via type: 'objectql' + // This aligns with the manifest schema that supports objectql as a package type + const hasObjectQLPlugin = plugins.some(p => + p && typeof p === 'object' && p.type === 'objectql' + ); + + if (!hasObjectQLPlugin) { + // Backward compatibility: Initialize ObjectQL directly if no plugin provides it + console.warn('[Kernel] No ObjectQL plugin found, using default initialization. Consider using ObjectQLPlugin.'); + this.ql = new ObjectQL({ + env: process.env.NODE_ENV || 'development' + }); + } + } + + /** + * Ensure ObjectQL engine is initialized + * @throws Error if ObjectQL is not available + */ + private ensureObjectQL(): ObjectQL { + if (!this.ql) { + throw new Error('[Kernel] ObjectQL engine not initialized. Ensure ObjectQLPlugin is registered or kernel is properly initialized.'); + } + return this.ql; } async start() { @@ -54,13 +75,13 @@ export class ObjectStackKernel { // @ts-ignore const { InMemoryDriver } = await import('@objectstack/driver-memory'); const driver = new InMemoryDriver(); - this.ql.registerDriver(driver); + this.ensureObjectQL().registerDriver(driver); } catch (e) { // Ignore if not present } // 2. Initialize Engine - await this.ql.init(); + await this.ensureObjectQL().init(); // 3. Seed Data @@ -102,11 +123,11 @@ export class ObjectStackKernel { for (const seed of app.data) { try { // Check if data exists - const existing = await this.ql.find(seed.object, { top: 1 }); + const existing = await this.ensureObjectQL().find(seed.object, { top: 1 }); if (existing.length === 0) { console.log(`[Kernel] Inserting ${seed.records.length} records into ${seed.object}`); for (const record of seed.records) { - await this.ql.insert(seed.object, record); + await this.ensureObjectQL().insert(seed.object, record); } } } catch (e) { @@ -124,30 +145,30 @@ export class ObjectStackKernel { // Forward methods to ObjectQL async find(objectName: string, query: any) { this.ensureSchema(objectName); - const results = await this.ql.find(objectName, { top: 100 }); + const results = await this.ensureObjectQL().find(objectName, { top: 100 }); return { value: results, count: results.length }; } async get(objectName: string, id: string) { this.ensureSchema(objectName); // Find One - const results = await this.ql.find(objectName, { top: 1 }); // Mock implementation + const results = await this.ensureObjectQL().find(objectName, { top: 1 }); // Mock implementation return results[0]; } async create(objectName: string, data: any) { this.ensureSchema(objectName); - return this.ql.insert(objectName, data); + return this.ensureObjectQL().insert(objectName, data); } async update(objectName: string, id: string, data: any) { this.ensureSchema(objectName); - return this.ql.update(objectName, id, data); + return this.ensureObjectQL().update(objectName, id, data); } async delete(objectName: string, id: string) { this.ensureSchema(objectName); - return this.ql.delete(objectName, id); + return this.ensureObjectQL().delete(objectName, id); } // [New Methods for ObjectUI] diff --git a/packages/runtime/src/objectql-plugin.ts b/packages/runtime/src/objectql-plugin.ts new file mode 100644 index 000000000..f8051f84c --- /dev/null +++ b/packages/runtime/src/objectql-plugin.ts @@ -0,0 +1,47 @@ +import { ObjectQL } from '@objectstack/objectql'; +import { RuntimePlugin, RuntimeContext } from '@objectstack/types'; + +/** + * ObjectQL Engine Plugin + * + * Registers the ObjectQL engine instance with the kernel. + * This allows users to provide their own ObjectQL implementation or configuration. + * + * Usage: + * - new ObjectQLPlugin() - Creates new ObjectQL with default settings + * - new ObjectQLPlugin(existingQL) - Uses existing ObjectQL instance + * - new ObjectQLPlugin(undefined, { custom: 'context' }) - Creates new ObjectQL with custom context + */ +export class ObjectQLPlugin implements RuntimePlugin { + name = 'com.objectstack.engine.objectql'; + type = 'objectql' as const; + + private ql: ObjectQL; + + /** + * @param ql - Existing ObjectQL instance to use (optional) + * @param hostContext - Host context for new ObjectQL instance (ignored if ql is provided) + */ + constructor(ql?: ObjectQL, hostContext?: Record) { + if (ql && hostContext) { + console.warn('[ObjectQLPlugin] Both ql and hostContext provided. hostContext will be ignored.'); + } + + if (ql) { + this.ql = ql; + } else { + this.ql = new ObjectQL(hostContext || { + env: process.env.NODE_ENV || 'development' + }); + } + } + + /** + * Install the ObjectQL engine into the kernel + */ + async install(ctx: RuntimeContext) { + // Attach the ObjectQL engine to the kernel + ctx.engine.ql = this.ql; + console.log('[ObjectQLPlugin] ObjectQL engine registered'); + } +} diff --git a/packages/spec/json-schema/hub/ComposerResponse.json b/packages/spec/json-schema/hub/ComposerResponse.json index 003cda741..bc7f6a74a 100644 --- a/packages/spec/json-schema/hub/ComposerResponse.json +++ b/packages/spec/json-schema/hub/ComposerResponse.json @@ -25,7 +25,10 @@ "app", "plugin", "driver", - "module" + "module", + "objectql", + "gateway", + "adapter" ], "description": "Type of package" }, diff --git a/packages/spec/json-schema/system/Manifest.json b/packages/spec/json-schema/system/Manifest.json index ef7dd8499..6e0998b92 100644 --- a/packages/spec/json-schema/system/Manifest.json +++ b/packages/spec/json-schema/system/Manifest.json @@ -19,7 +19,10 @@ "app", "plugin", "driver", - "module" + "module", + "objectql", + "gateway", + "adapter" ], "description": "Type of package" }, diff --git a/packages/spec/src/system/manifest.test.ts b/packages/spec/src/system/manifest.test.ts index 0512bc64b..0b6b3eb20 100644 --- a/packages/spec/src/system/manifest.test.ts +++ b/packages/spec/src/system/manifest.test.ts @@ -39,7 +39,7 @@ describe('ManifestSchema', () => { }); it('should accept all package types', () => { - const types = ['app', 'plugin', 'driver', 'module'] as const; + const types = ['app', 'plugin', 'driver', 'module', 'objectql', 'gateway', 'adapter'] as const; types.forEach(type => { const manifest = { @@ -258,6 +258,84 @@ describe('ManifestSchema', () => { expect(() => ManifestSchema.parse(utilModule)).not.toThrow(); }); + + it('should accept objectql engine manifest', () => { + const objectqlEngine: ObjectStackManifest = { + id: 'com.objectstack.engine.objectql', + version: '2.0.0', + type: 'objectql', + name: 'ObjectQL Engine', + description: 'Core data layer implementation with query AST and validation', + }; + + expect(() => ManifestSchema.parse(objectqlEngine)).not.toThrow(); + }); + + it('should accept gateway manifest for GraphQL', () => { + const graphqlGateway: ObjectStackManifest = { + id: 'com.objectstack.gateway.graphql', + version: '1.0.0', + type: 'gateway', + name: 'GraphQL Gateway', + description: 'GraphQL API protocol gateway for ObjectStack', + permissions: [ + 'system.api.configure', + ], + }; + + expect(() => ManifestSchema.parse(graphqlGateway)).not.toThrow(); + }); + + it('should accept gateway manifest for REST', () => { + const restGateway: ObjectStackManifest = { + id: 'com.objectstack.gateway.rest', + version: '1.0.0', + type: 'gateway', + name: 'REST API Gateway', + description: 'RESTful API protocol gateway for ObjectStack', + }; + + expect(() => ManifestSchema.parse(restGateway)).not.toThrow(); + }); + + it('should accept adapter manifest for Express', () => { + const expressAdapter: ObjectStackManifest = { + id: 'com.objectstack.adapter.express', + version: '4.0.0', + type: 'adapter', + name: 'Express Adapter', + description: 'Express.js HTTP server adapter for ObjectStack runtime', + configuration: { + title: 'Express Server Settings', + properties: { + port: { + type: 'number', + default: 3000, + description: 'HTTP server port', + }, + corsEnabled: { + type: 'boolean', + default: true, + description: 'Enable CORS middleware', + }, + }, + }, + }; + + expect(() => ManifestSchema.parse(expressAdapter)).not.toThrow(); + }); + + it('should accept adapter manifest for Hono', () => { + const honoAdapter: ObjectStackManifest = { + id: 'com.objectstack.adapter.hono', + version: '1.0.0', + type: 'adapter', + name: 'Hono Adapter', + description: 'Hono ultrafast HTTP server adapter for ObjectStack runtime', + }; + + expect(() => ManifestSchema.parse(honoAdapter)).not.toThrow(); + }); }); describe('Reverse Domain Notation', () => { diff --git a/packages/spec/src/system/manifest.zod.ts b/packages/spec/src/system/manifest.zod.ts index dbc5a7b28..c8030a61d 100644 --- a/packages/spec/src/system/manifest.zod.ts +++ b/packages/spec/src/system/manifest.zod.ts @@ -20,12 +20,15 @@ export const ManifestSchema = z.object({ /** * Type of the package in the ObjectStack ecosystem. - * - app: Standalone application - * - plugin: Extension to ObjectOS - * - driver: Low-level integration driver - * - module: Reusable code module + * - app: Business application package + * - plugin: General-purpose functionality extension + * - driver: Southbound interface - Database/external service adapter (Postgres, MongoDB, S3) + * - module: Reusable code library/shared module + * - objectql: Core engine - Data layer implementation + * - gateway: Northbound interface - API protocol entry point (GraphQL, REST, RPC, OData) + * - adapter: Host adapter - Runtime container (Express, Hono, Fastify, Serverless) */ - type: z.enum(['app', 'plugin', 'driver', 'module']).describe('Type of package'), + type: z.enum(['app', 'plugin', 'driver', 'module', 'objectql', 'gateway', 'adapter']).describe('Type of package'), /** * Human-readable name of the package. diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 1028bd891..e70194469 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -3,6 +3,7 @@ export interface IKernel { // We can add specific methods here that plugins are allowed to call // forcing a stricter contract than exposing the whole class. + ql?: any; // ObjectQL instance (optional to support initialization phase) start(): Promise; // ... expose other needed public methods [key: string]: any; diff --git a/test-objectql-plugin.ts b/test-objectql-plugin.ts new file mode 100644 index 000000000..d6d9c7461 --- /dev/null +++ b/test-objectql-plugin.ts @@ -0,0 +1,123 @@ +/** + * Validation Script for ObjectQL Plugin + * + * This script validates: + * 1. Plugin-based ObjectQL registration works + * 2. Backward compatibility (without plugin) works + * 3. Custom ObjectQL instance works + */ + +import { ObjectStackKernel, ObjectQLPlugin, ObjectQL, SchemaRegistry } from '../packages/runtime/src'; + +async function testPluginBasedRegistration() { + console.log('\n=== Test 1: Plugin-based ObjectQL Registration ==='); + + const kernel = new ObjectStackKernel([ + new ObjectQLPlugin() + ]); + + // Verify ObjectQL is set + if (!kernel.ql) { + throw new Error('FAILED: ObjectQL not set via plugin'); + } + + console.log('✅ ObjectQL registered via plugin'); + console.log('ObjectQL instance:', kernel.ql.constructor.name); +} + +async function testBackwardCompatibility() { + console.log('\n=== Test 2: Backward Compatibility (No Plugin) ==='); + + const kernel = new ObjectStackKernel([]); + + // Verify ObjectQL is auto-initialized + if (!kernel.ql) { + throw new Error('FAILED: ObjectQL not auto-initialized'); + } + + console.log('✅ ObjectQL auto-initialized for backward compatibility'); + console.log('ObjectQL instance:', kernel.ql.constructor.name); +} + +async function testCustomObjectQL() { + console.log('\n=== Test 3: Custom ObjectQL Instance ==='); + + // Create a WeakMap to track custom instances in a type-safe way + const customInstances = new WeakMap(); + + const customQL = new ObjectQL({ + env: 'test', + customProperty: 'test-value' + }); + + // Mark this as a custom instance + customInstances.set(customQL, 'custom-instance'); + + const kernel = new ObjectStackKernel([ + new ObjectQLPlugin(customQL) + ]); + + // Verify the custom instance is used + if (!kernel.ql) { + throw new Error('FAILED: ObjectQL not set'); + } + + if (!customInstances.has(kernel.ql)) { + throw new Error('FAILED: Custom ObjectQL instance not used'); + } + + const marker = customInstances.get(kernel.ql); + if (marker !== 'custom-instance') { + throw new Error('FAILED: Custom ObjectQL instance marker mismatch'); + } + + console.log('✅ Custom ObjectQL instance registered correctly'); + console.log('Custom marker:', marker); +} + +async function testMultiplePlugins() { + console.log('\n=== Test 4: Multiple Plugins with ObjectQL ==='); + + // Mock driver + const mockDriver = { + name: 'mock-driver', + version: '1.0.0', + capabilities: {}, + async connect() {}, + async disconnect() {}, + async find() { return []; }, + async findOne() { return null; }, + async create() { return {}; }, + async update() { return {}; }, + async delete() { return {}; } + }; + + const kernel = new ObjectStackKernel([ + new ObjectQLPlugin(), + mockDriver + ]); + + if (!kernel.ql) { + throw new Error('FAILED: ObjectQL not set'); + } + + console.log('✅ Multiple plugins work correctly with ObjectQLPlugin'); +} + +async function runAllTests() { + console.log('🧪 Starting ObjectQL Plugin Validation Tests...\n'); + + try { + await testPluginBasedRegistration(); + await testBackwardCompatibility(); + await testCustomObjectQL(); + await testMultiplePlugins(); + + console.log('\n✅ All tests passed!\n'); + } catch (error) { + console.error('\n❌ Test failed:', error); + process.exit(1); + } +} + +runAllTests();