@objectstack/runtime 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @objectstack/runtime
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b2df5f7: Unified version bump to 0.5.0
8
+
9
+ - Standardized all package versions to 0.5.0 across the monorepo
10
+ - Fixed driver-memory package.json paths for proper module resolution
11
+ - Ensured all packages are in sync for the 0.5.0 release
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [b2df5f7]
16
+ - @objectstack/spec@0.6.0
17
+ - @objectstack/objectql@0.6.0
18
+ - @objectstack/types@0.6.0
19
+ - @objectstack/core@0.6.0
20
+
21
+ ## 0.4.2
22
+
23
+ ### Patch Changes
24
+
25
+ - Unify all package versions to 0.4.2
26
+ - Updated dependencies
27
+ - @objectstack/spec@0.4.2
28
+ - @objectstack/objectql@0.4.2
29
+ - @objectstack/types@0.4.2
30
+
3
31
  ## 0.4.1
4
32
 
5
33
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # @objectstack/runtime
2
+
3
+ ObjectStack Standard System Library
4
+
5
+ ## Overview
6
+
7
+ 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.
8
+
9
+ ### Architecture Highlights
10
+
11
+ - **Standard Library**: Contains essential plugins (`AppManifestPlugin`, `DriverPlugin`)
12
+ - **Core Integration**: Re-exports `ObjectKernel` for convenience
13
+ - **Capability Contracts**: Abstract interfaces for HTTP server and data persistence
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @objectstack/runtime
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### Basic Setup (Recommended)
24
+
25
+ ```typescript
26
+ import { ObjectKernel } from '@objectstack/core';
27
+ import { ObjectQLPlugin, DriverPlugin, AppManifestPlugin } from '@objectstack/runtime';
28
+ import { InMemoryDriver } from '@objectstack/driver-memory';
29
+
30
+ const kernel = new ObjectKernel();
31
+
32
+ kernel
33
+ // Register ObjectQL engine
34
+ .use(new ObjectQLPlugin())
35
+
36
+ // Add database driver
37
+ .use(new DriverPlugin(new InMemoryDriver(), 'memory'))
38
+
39
+ // Add your app configurations
40
+ // .use(new AppManifestPlugin(appConfig));
41
+
42
+ await kernel.bootstrap();
43
+ ```
44
+
45
+ ### Custom ObjectQL Instance
46
+
47
+ If you have a separate ObjectQL implementation or need custom configuration:
48
+
49
+ ```typescript
50
+ import { ObjectKernel, ObjectQLPlugin, DriverPlugin, ObjectQL } from '@objectstack/runtime';
51
+
52
+ // Create custom ObjectQL instance
53
+ const customQL = new ObjectQL({
54
+ env: 'production',
55
+ customConfig: true
56
+ });
57
+
58
+ // Pre-configure with custom hooks
59
+ customQL.registerHook('beforeInsert', async (ctx) => {
60
+ console.log(`Inserting into ${ctx.object}`);
61
+ });
62
+
63
+ const kernel = new ObjectKernel();
64
+
65
+ kernel
66
+ // Use your custom ObjectQL instance
67
+ .use(new ObjectQLPlugin(customQL))
68
+
69
+ // Add driver
70
+ .use(new DriverPlugin(new InMemoryDriver(), 'memory'));
71
+
72
+ await kernel.bootstrap();
73
+
74
+ // Access ObjectQL via service registry
75
+ const objectql = kernel.getService('objectql');
76
+ ```
77
+
78
+ ## Architecture
79
+
80
+ ### ObjectKernel (MiniKernel)
81
+
82
+ The kernel provides:
83
+ - **Plugin Lifecycle Management**: init → start → destroy phases
84
+ - **Service Registry**: Dependency injection container
85
+ - **Event/Hook System**: Inter-plugin communication
86
+ - **Dependency Resolution**: Topological sort for plugin dependencies
87
+
88
+ ### Built-in Plugins
89
+
90
+ #### ObjectQLPlugin
91
+ Registers the ObjectQL data engine as a service.
92
+
93
+ ```typescript
94
+ new ObjectQLPlugin() // Default instance
95
+ new ObjectQLPlugin(customQL) // Custom instance
96
+ new ObjectQLPlugin(undefined, { env: 'prod' }) // With context
97
+ ```
98
+
99
+ **Services**: `'objectql'`
100
+
101
+ #### DriverPlugin
102
+ Registers a data driver with ObjectQL.
103
+
104
+ ```typescript
105
+ new DriverPlugin(driver, 'driver-name')
106
+ ```
107
+
108
+ **Dependencies**: `['com.objectstack.engine.objectql']`
109
+
110
+ #### AppManifestPlugin
111
+ Wraps ObjectStack app manifests (objectstack.config.ts) as plugins.
112
+
113
+ ```typescript
114
+ new AppManifestPlugin(appConfig)
115
+ ```
116
+
117
+ **Dependencies**: `['com.objectstack.engine.objectql']`
118
+
119
+ ## API Reference
120
+
121
+ ### Capability Contract Interfaces
122
+
123
+ #### IHttpServer
124
+
125
+ Abstract interface for HTTP server capabilities. Allows plugins to work with any HTTP framework (Express, Fastify, Hono, etc.) without tight coupling.
126
+
127
+ ```typescript
128
+ import { IHttpServer, IHttpRequest, IHttpResponse } from '@objectstack/runtime';
129
+
130
+ // In your HTTP server plugin
131
+ class MyHttpServerPlugin implements Plugin {
132
+ name = 'http-server';
133
+
134
+ async init(ctx: PluginContext) {
135
+ const server: IHttpServer = createMyServer(); // Express, Hono, etc.
136
+ ctx.registerService('http-server', server);
137
+ }
138
+ }
139
+
140
+ // In your API plugin
141
+ class MyApiPlugin implements Plugin {
142
+ name = 'api';
143
+ dependencies = ['http-server'];
144
+
145
+ async start(ctx: PluginContext) {
146
+ const server = ctx.getService<IHttpServer>('http-server');
147
+
148
+ // Register routes - works with any HTTP framework
149
+ server.get('/api/users', async (req, res) => {
150
+ res.json({ users: [] });
151
+ });
152
+ }
153
+ }
154
+ ```
155
+
156
+ **Interface Methods:**
157
+ - `get(path, handler)` - Register GET route
158
+ - `post(path, handler)` - Register POST route
159
+ - `put(path, handler)` - Register PUT route
160
+ - `delete(path, handler)` - Register DELETE route
161
+ - `patch(path, handler)` - Register PATCH route
162
+ - `use(path, handler?)` - Register middleware
163
+ - `listen(port)` - Start server
164
+ - `close()` - Stop server (optional)
165
+
166
+ #### IDataEngine
167
+
168
+ Abstract interface for data persistence. Allows plugins to work with any data layer (ObjectQL, Prisma, TypeORM, etc.) without tight coupling.
169
+
170
+ ```typescript
171
+ import { IDataEngine } from '@objectstack/runtime';
172
+
173
+ // In your data plugin
174
+ class MyDataPlugin implements Plugin {
175
+ name = 'data';
176
+
177
+ async init(ctx: PluginContext) {
178
+ const engine: IDataEngine = createMyDataEngine(); // ObjectQL, Prisma, etc.
179
+ ctx.registerService('data-engine', engine);
180
+ }
181
+ }
182
+
183
+ // In your business logic plugin
184
+ class MyBusinessPlugin implements Plugin {
185
+ name = 'business';
186
+ dependencies = ['data'];
187
+
188
+ async start(ctx: PluginContext) {
189
+ const engine = ctx.getService<IDataEngine>('data-engine');
190
+
191
+ // CRUD operations - works with any data layer
192
+ const user = await engine.insert('user', { name: 'John' });
193
+ const users = await engine.find('user', { filter: { active: true } });
194
+ await engine.update('user', user.id, { name: 'Jane' });
195
+ await engine.delete('user', user.id);
196
+ }
197
+ }
198
+ ```
199
+
200
+ **Interface Methods:**
201
+ - `insert(objectName, data)` - Create a record
202
+ - `find(objectName, query?)` - Query records
203
+ - `update(objectName, id, data)` - Update a record
204
+ - `delete(objectName, id)` - Delete a record
205
+
206
+ ### ObjectKernel
207
+
208
+ #### Methods
209
+ - `use(plugin: Plugin)`: Register a plugin
210
+ - `bootstrap()`: Initialize and start all plugins
211
+ - `shutdown()`: Stop all plugins in reverse order
212
+ - `getService<T>(name: string)`: Get a service from registry
213
+ - `isRunning()`: Check if kernel is running
214
+ - `getState()`: Get current kernel state
215
+
216
+ ### Plugin Interface
217
+
218
+ ```typescript
219
+ interface Plugin {
220
+ name: string; // Unique identifier
221
+ version?: string; // Plugin version
222
+ dependencies?: string[]; // Required plugin names
223
+
224
+ init(ctx: PluginContext): Promise<void>; // Register services
225
+ start?(ctx: PluginContext): Promise<void>; // Execute business logic
226
+ destroy?(): Promise<void>; // Cleanup
227
+ }
228
+ ```
229
+
230
+ ### PluginContext
231
+
232
+ ```typescript
233
+ interface PluginContext {
234
+ registerService(name: string, service: any): void;
235
+ getService<T>(name: string): T;
236
+ hook(name: string, handler: Function): void;
237
+ trigger(name: string, ...args: any[]): Promise<void>;
238
+ logger: Console;
239
+ getKernel?(): any;
240
+ }
241
+ ```
242
+
243
+ ## Examples
244
+
245
+ See the `examples/` directory for complete examples:
246
+ - `examples/host/` - Full server setup with Hono
247
+ - `examples/msw-react-crud/` - Browser-based setup with MSW
248
+ - `test-mini-kernel.ts` - Comprehensive kernel test suite
249
+ - `packages/runtime/src/test-interfaces.ts` - Capability contract interface examples
250
+
251
+ ## Benefits of MiniKernel
252
+
253
+ 1. **True Modularity**: Each plugin is independent and reusable
254
+ 2. **Capability Contracts**: Plugins depend on interfaces, not implementations
255
+ 3. **Testability**: Mock services easily in tests
256
+ 4. **Flexibility**: Load plugins conditionally, swap implementations
257
+ 5. **Extensibility**: Add new plugins without modifying kernel
258
+ 6. **Clear Dependencies**: Explicit dependency declarations
259
+ 7. **Better Architecture**: Separation of concerns with Dependency Inversion
260
+
261
+ ## Best Practices
262
+
263
+ 1. **Keep plugins focused**: One responsibility per plugin
264
+ 2. **Use services**: Share functionality via service registry
265
+ 3. **Declare dependencies**: Make plugin requirements explicit
266
+ 4. **Use hooks**: Decouple plugins with event system
267
+ 5. **Handle errors**: Implement proper error handling in lifecycle methods
268
+
269
+ ## Documentation
270
+
271
+ - [MiniKernel Guide](../../MINI_KERNEL_GUIDE.md) - Complete API documentation and patterns
272
+ - [MiniKernel Architecture](../../MINI_KERNEL_ARCHITECTURE.md) - Architecture diagrams and flows
273
+ - [MiniKernel Implementation](../../MINI_KERNEL_IMPLEMENTATION.md) - Implementation details
274
+
275
+ ## License
276
+
277
+ Apache-2.0
@@ -0,0 +1,19 @@
1
+ import { Plugin, PluginContext } from '@objectstack/core';
2
+ /**
3
+ * AppManifestPlugin
4
+ *
5
+ * Adapts a static Manifest JSON into a dynamic Kernel Service.
6
+ * This allows the ObjectQL Engine to "discover" this app during its start phase.
7
+ *
8
+ * Flow:
9
+ * 1. AppPlugin registers `app.<id>` service (init phase)
10
+ * 2. ObjectQL Engine discovers `app.*` services (start phase)
11
+ */
12
+ export declare class AppManifestPlugin implements Plugin {
13
+ name: string;
14
+ version?: string;
15
+ private manifest;
16
+ constructor(manifest: any);
17
+ init(ctx: PluginContext): Promise<void>;
18
+ start(ctx: PluginContext): Promise<void>;
19
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * AppManifestPlugin
3
+ *
4
+ * Adapts a static Manifest JSON into a dynamic Kernel Service.
5
+ * This allows the ObjectQL Engine to "discover" this app during its start phase.
6
+ *
7
+ * Flow:
8
+ * 1. AppPlugin registers `app.<id>` service (init phase)
9
+ * 2. ObjectQL Engine discovers `app.*` services (start phase)
10
+ */
11
+ export class AppManifestPlugin {
12
+ constructor(manifest) {
13
+ this.manifest = manifest;
14
+ // Support both direct manifest (legacy) and Stack Definition (nested manifest)
15
+ const sys = manifest.manifest || manifest;
16
+ const appId = sys.id || sys.name || 'unnamed-app';
17
+ this.name = `plugin.app.${appId}`; // Unique plugin name
18
+ this.version = sys.version;
19
+ }
20
+ async init(ctx) {
21
+ // Support both direct manifest (legacy) and Stack Definition (nested manifest)
22
+ const sys = this.manifest.manifest || this.manifest;
23
+ const appId = sys.id || sys.name;
24
+ ctx.logger.log(`[AppManifestPlugin] Registering App Service: ${appId}`);
25
+ // Register the app manifest as a service
26
+ const serviceName = `app.${appId}`;
27
+ ctx.registerService(serviceName, this.manifest);
28
+ }
29
+ async start(ctx) {
30
+ // No logic needed here.
31
+ // Logic is inverted: The Engine will pull data from this service.
32
+ }
33
+ }
@@ -0,0 +1,23 @@
1
+ import { Plugin, PluginContext } from '@objectstack/core';
2
+ /**
3
+ * Driver Plugin
4
+ *
5
+ * Generic plugin wrapper for ObjectQL drivers.
6
+ * Registers a driver with the ObjectQL engine.
7
+ *
8
+ * Dependencies: None (Registers service for ObjectQL to discover)
9
+ * Services: driver.{name}
10
+ *
11
+ * @example
12
+ * const memoryDriver = new InMemoryDriver();
13
+ * const driverPlugin = new DriverPlugin(memoryDriver, 'memory');
14
+ * kernel.use(driverPlugin);
15
+ */
16
+ export declare class DriverPlugin implements Plugin {
17
+ name: string;
18
+ version: string;
19
+ private driver;
20
+ constructor(driver: any, driverName?: string);
21
+ init(ctx: PluginContext): Promise<void>;
22
+ start(ctx: PluginContext): Promise<void>;
23
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Driver Plugin
3
+ *
4
+ * Generic plugin wrapper for ObjectQL drivers.
5
+ * Registers a driver with the ObjectQL engine.
6
+ *
7
+ * Dependencies: None (Registers service for ObjectQL to discover)
8
+ * Services: driver.{name}
9
+ *
10
+ * @example
11
+ * const memoryDriver = new InMemoryDriver();
12
+ * const driverPlugin = new DriverPlugin(memoryDriver, 'memory');
13
+ * kernel.use(driverPlugin);
14
+ */
15
+ export class DriverPlugin {
16
+ constructor(driver, driverName) {
17
+ this.version = '1.0.0';
18
+ this.driver = driver;
19
+ this.name = `com.objectstack.driver.${driverName || driver.name || 'unknown'}`;
20
+ }
21
+ async init(ctx) {
22
+ // Register driver as a service instead of directly to objectql
23
+ const serviceName = `driver.${this.driver.name || 'unknown'}`;
24
+ ctx.registerService(serviceName, this.driver);
25
+ ctx.logger.log(`[DriverPlugin] Registered driver service: ${serviceName}`);
26
+ }
27
+ async start(ctx) {
28
+ // Drivers don't need start phase, initialization happens in init
29
+ ctx.logger.log(`[DriverPlugin] Driver ready: ${this.driver.name || 'unknown'}`);
30
+ }
31
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- export { ObjectQL, SchemaRegistry } from '@objectstack/objectql';
2
- export { ObjectStackKernel } from './kernel';
3
- export { ObjectStackRuntimeProtocol } from './protocol';
4
- export * from './types';
1
+ export { ObjectQL, SchemaRegistry, ObjectStackProtocolImplementation } from '@objectstack/objectql';
2
+ export { ObjectKernel } from '@objectstack/core';
3
+ export { ObjectQLPlugin } from '@objectstack/objectql';
4
+ export { DriverPlugin } from './driver-plugin';
5
+ export { AppManifestPlugin } from './app-manifest-plugin';
6
+ export * from '@objectstack/core';
package/dist/index.js CHANGED
@@ -1,5 +1,10 @@
1
1
  // Export core engine
2
- export { ObjectQL, SchemaRegistry } from '@objectstack/objectql';
3
- export { ObjectStackKernel } from './kernel';
4
- export { ObjectStackRuntimeProtocol } from './protocol';
5
- export * from './types';
2
+ export { ObjectQL, SchemaRegistry, ObjectStackProtocolImplementation } from '@objectstack/objectql';
3
+ // Export Kernels
4
+ export { ObjectKernel } from '@objectstack/core';
5
+ // Export Plugins
6
+ export { ObjectQLPlugin } from '@objectstack/objectql';
7
+ export { DriverPlugin } from './driver-plugin';
8
+ export { AppManifestPlugin } from './app-manifest-plugin';
9
+ // Export Types
10
+ export * from '@objectstack/core';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Test file to verify capability contract interfaces
3
+ *
4
+ * This file demonstrates how plugins can implement the IHttpServer
5
+ * and IDataEngine interfaces without depending on concrete implementations.
6
+ */
7
+ export {};
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Test file to verify capability contract interfaces
3
+ *
4
+ * This file demonstrates how plugins can implement the IHttpServer
5
+ * and IDataEngine interfaces without depending on concrete implementations.
6
+ */
7
+ /**
8
+ * Example: Mock HTTP Server Plugin
9
+ *
10
+ * Shows how a plugin can implement the IHttpServer interface
11
+ * without depending on Express, Fastify, or any specific framework.
12
+ */
13
+ class MockHttpServer {
14
+ constructor() {
15
+ this.routes = new Map();
16
+ }
17
+ get(path, handler) {
18
+ this.routes.set(`GET:${path}`, { method: 'GET', handler });
19
+ console.log(`✅ Registered GET ${path}`);
20
+ }
21
+ post(path, handler) {
22
+ this.routes.set(`POST:${path}`, { method: 'POST', handler });
23
+ console.log(`✅ Registered POST ${path}`);
24
+ }
25
+ put(path, handler) {
26
+ this.routes.set(`PUT:${path}`, { method: 'PUT', handler });
27
+ console.log(`✅ Registered PUT ${path}`);
28
+ }
29
+ delete(path, handler) {
30
+ this.routes.set(`DELETE:${path}`, { method: 'DELETE', handler });
31
+ console.log(`✅ Registered DELETE ${path}`);
32
+ }
33
+ patch(path, handler) {
34
+ this.routes.set(`PATCH:${path}`, { method: 'PATCH', handler });
35
+ console.log(`✅ Registered PATCH ${path}`);
36
+ }
37
+ use(path, handler) {
38
+ console.log(`✅ Registered middleware`);
39
+ }
40
+ async listen(port) {
41
+ console.log(`✅ Mock HTTP server listening on port ${port}`);
42
+ }
43
+ async close() {
44
+ console.log(`✅ Mock HTTP server closed`);
45
+ }
46
+ }
47
+ /**
48
+ * Example: Mock Data Engine Plugin
49
+ *
50
+ * Shows how a plugin can implement the IDataEngine interface
51
+ * without depending on ObjectQL, Prisma, or any specific database.
52
+ */
53
+ class MockDataEngine {
54
+ constructor() {
55
+ this.store = new Map();
56
+ this.idCounter = 0;
57
+ }
58
+ async insert(objectName, data) {
59
+ if (!this.store.has(objectName)) {
60
+ this.store.set(objectName, new Map());
61
+ }
62
+ const id = `${objectName}_${++this.idCounter}`;
63
+ const record = { id, ...data };
64
+ this.store.get(objectName).set(id, record);
65
+ console.log(`✅ Inserted into ${objectName}:`, record);
66
+ return record;
67
+ }
68
+ async find(objectName, query) {
69
+ const objectStore = this.store.get(objectName);
70
+ if (!objectStore) {
71
+ return [];
72
+ }
73
+ const results = Array.from(objectStore.values());
74
+ console.log(`✅ Found ${results.length} records in ${objectName}`);
75
+ return results;
76
+ }
77
+ async update(objectName, id, data) {
78
+ const objectStore = this.store.get(objectName);
79
+ if (!objectStore || !objectStore.has(id)) {
80
+ throw new Error(`Record ${id} not found in ${objectName}`);
81
+ }
82
+ const existing = objectStore.get(id);
83
+ const updated = { ...existing, ...data };
84
+ objectStore.set(id, updated);
85
+ console.log(`✅ Updated ${objectName}/${id}:`, updated);
86
+ return updated;
87
+ }
88
+ async delete(objectName, id) {
89
+ const objectStore = this.store.get(objectName);
90
+ if (!objectStore) {
91
+ return false;
92
+ }
93
+ const deleted = objectStore.delete(id);
94
+ console.log(`✅ Deleted ${objectName}/${id}: ${deleted}`);
95
+ return deleted;
96
+ }
97
+ }
98
+ /**
99
+ * Test the interfaces
100
+ */
101
+ async function testInterfaces() {
102
+ console.log('\n=== Testing IHttpServer Interface ===\n');
103
+ const httpServer = new MockHttpServer();
104
+ // Register routes using the interface
105
+ httpServer.get('/api/users', async (req, res) => {
106
+ res.json({ users: [] });
107
+ });
108
+ httpServer.post('/api/users', async (req, res) => {
109
+ res.status(201).json({ id: 1, ...req.body });
110
+ });
111
+ await httpServer.listen(3000);
112
+ console.log('\n=== Testing IDataEngine Interface ===\n');
113
+ const dataEngine = new MockDataEngine();
114
+ // Use the data engine interface
115
+ const user1 = await dataEngine.insert('user', {
116
+ name: 'John Doe',
117
+ email: 'john@example.com'
118
+ });
119
+ const user2 = await dataEngine.insert('user', {
120
+ name: 'Jane Smith',
121
+ email: 'jane@example.com'
122
+ });
123
+ const users = await dataEngine.find('user');
124
+ console.log(`Found ${users.length} users after inserts`);
125
+ const updatedUser = await dataEngine.update('user', user1.id, {
126
+ name: 'John Updated'
127
+ });
128
+ console.log(`Updated user:`, updatedUser);
129
+ const deleted = await dataEngine.delete('user', user2.id);
130
+ console.log(`Delete result: ${deleted}`);
131
+ console.log('\n✅ All interface tests passed!\n');
132
+ if (httpServer.close) {
133
+ await httpServer.close();
134
+ }
135
+ }
136
+ // Run tests
137
+ testInterfaces().catch(console.error);
138
+ export {};
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@objectstack/runtime",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "ObjectStack Core Runtime & Query Engine",
5
+ "type": "module",
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
7
8
  "dependencies": {
8
- "@objectstack/objectql": "0.4.1",
9
- "@objectstack/types": "0.4.1",
10
- "@objectstack/spec": "0.4.1"
9
+ "@objectstack/core": "0.6.0",
10
+ "@objectstack/objectql": "0.6.0",
11
+ "@objectstack/types": "0.6.0",
12
+ "@objectstack/spec": "0.6.0"
11
13
  },
12
14
  "devDependencies": {
13
15
  "typescript": "^5.0.0"
@@ -0,0 +1,48 @@
1
+ import { Plugin, PluginContext } from '@objectstack/core';
2
+
3
+ /**
4
+ * AppManifestPlugin
5
+ *
6
+ * Adapts a static Manifest JSON into a dynamic Kernel Service.
7
+ * This allows the ObjectQL Engine to "discover" this app during its start phase.
8
+ *
9
+ * Flow:
10
+ * 1. AppPlugin registers `app.<id>` service (init phase)
11
+ * 2. ObjectQL Engine discovers `app.*` services (start phase)
12
+ */
13
+ export class AppManifestPlugin implements Plugin {
14
+ name: string;
15
+ version?: string;
16
+
17
+ // Dependencies removed: This plugin produces data. It doesn't need to consume the engine to register itself.
18
+ // Making it dependency-free allows it to initialize BEFORE the engine if needed.
19
+
20
+ private manifest: any;
21
+
22
+ constructor(manifest: any) {
23
+ this.manifest = manifest;
24
+ // Support both direct manifest (legacy) and Stack Definition (nested manifest)
25
+ const sys = manifest.manifest || manifest;
26
+ const appId = sys.id || sys.name || 'unnamed-app';
27
+
28
+ this.name = `plugin.app.${appId}`; // Unique plugin name
29
+ this.version = sys.version;
30
+ }
31
+
32
+ async init(ctx: PluginContext) {
33
+ // Support both direct manifest (legacy) and Stack Definition (nested manifest)
34
+ const sys = this.manifest.manifest || this.manifest;
35
+ const appId = sys.id || sys.name;
36
+
37
+ ctx.logger.log(`[AppManifestPlugin] Registering App Service: ${appId}`);
38
+
39
+ // Register the app manifest as a service
40
+ const serviceName = `app.${appId}`;
41
+ ctx.registerService(serviceName, this.manifest);
42
+ }
43
+
44
+ async start(ctx: PluginContext) {
45
+ // No logic needed here.
46
+ // Logic is inverted: The Engine will pull data from this service.
47
+ }
48
+ }