@objectstack/runtime 0.6.0 → 0.6.1
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 +10 -0
- package/README.md +10 -8
- package/dist/app-plugin.d.ts +18 -0
- package/dist/app-plugin.js +52 -0
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -4
- package/package.json +4 -5
- package/src/app-plugin.ts +69 -0
- package/src/index.ts +1 -5
- package/dist/app-manifest-plugin.d.ts +0 -19
- package/dist/app-manifest-plugin.js +0 -33
- package/dist/test-interfaces.d.ts +0 -7
- package/dist/test-interfaces.js +0 -138
- package/src/app-manifest-plugin.ts +0 -48
- package/src/test-interfaces.ts +0 -170
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @objectstack/runtime
|
|
2
2
|
|
|
3
|
+
## 0.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Patch release for maintenance and stability improvements
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @objectstack/spec@0.6.1
|
|
10
|
+
- @objectstack/types@0.6.1
|
|
11
|
+
- @objectstack/core@0.6.1
|
|
12
|
+
|
|
3
13
|
## 0.6.0
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ The runtime package provides the **Standard Library** for the ObjectStack Operat
|
|
|
8
8
|
|
|
9
9
|
### Architecture Highlights
|
|
10
10
|
|
|
11
|
-
- **Standard Library**: Contains essential plugins (`
|
|
11
|
+
- **Standard Library**: Contains essential plugins (`AppPlugin`, `DriverPlugin`)
|
|
12
12
|
- **Core Integration**: Re-exports `ObjectKernel` for convenience
|
|
13
13
|
- **Capability Contracts**: Abstract interfaces for HTTP server and data persistence
|
|
14
14
|
|
|
@@ -24,7 +24,8 @@ npm install @objectstack/runtime
|
|
|
24
24
|
|
|
25
25
|
```typescript
|
|
26
26
|
import { ObjectKernel } from '@objectstack/core';
|
|
27
|
-
import {
|
|
27
|
+
import { DriverPlugin, AppPlugin } from '@objectstack/runtime';
|
|
28
|
+
import { ObjectQLPlugin } from '@objectstack/objectql';
|
|
28
29
|
import { InMemoryDriver } from '@objectstack/driver-memory';
|
|
29
30
|
|
|
30
31
|
const kernel = new ObjectKernel();
|
|
@@ -37,7 +38,7 @@ kernel
|
|
|
37
38
|
.use(new DriverPlugin(new InMemoryDriver(), 'memory'))
|
|
38
39
|
|
|
39
40
|
// Add your app configurations
|
|
40
|
-
// .use(new
|
|
41
|
+
// .use(new AppPlugin(appConfig));
|
|
41
42
|
|
|
42
43
|
await kernel.bootstrap();
|
|
43
44
|
```
|
|
@@ -47,7 +48,8 @@ await kernel.bootstrap();
|
|
|
47
48
|
If you have a separate ObjectQL implementation or need custom configuration:
|
|
48
49
|
|
|
49
50
|
```typescript
|
|
50
|
-
import { ObjectKernel,
|
|
51
|
+
import { ObjectKernel, DriverPlugin } from '@objectstack/runtime';
|
|
52
|
+
import { ObjectQLPlugin, ObjectQL } from '@objectstack/objectql';
|
|
51
53
|
|
|
52
54
|
// Create custom ObjectQL instance
|
|
53
55
|
const customQL = new ObjectQL({
|
|
@@ -107,14 +109,14 @@ new DriverPlugin(driver, 'driver-name')
|
|
|
107
109
|
|
|
108
110
|
**Dependencies**: `['com.objectstack.engine.objectql']`
|
|
109
111
|
|
|
110
|
-
####
|
|
112
|
+
#### AppPlugin
|
|
111
113
|
Wraps ObjectStack app manifests (objectstack.config.ts) as plugins.
|
|
112
114
|
|
|
113
115
|
```typescript
|
|
114
|
-
new
|
|
116
|
+
new AppPlugin(appConfig)
|
|
115
117
|
```
|
|
116
118
|
|
|
117
|
-
**
|
|
119
|
+
**Services**: `'app.{id}'`
|
|
118
120
|
|
|
119
121
|
## API Reference
|
|
120
122
|
|
|
@@ -246,7 +248,7 @@ See the `examples/` directory for complete examples:
|
|
|
246
248
|
- `examples/host/` - Full server setup with Hono
|
|
247
249
|
- `examples/msw-react-crud/` - Browser-based setup with MSW
|
|
248
250
|
- `test-mini-kernel.ts` - Comprehensive kernel test suite
|
|
249
|
-
- `packages/runtime/src/
|
|
251
|
+
- `packages/runtime/src/
|
|
250
252
|
|
|
251
253
|
## Benefits of MiniKernel
|
|
252
254
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
2
|
+
/**
|
|
3
|
+
* AppPlugin
|
|
4
|
+
*
|
|
5
|
+
* Adapts a generic App Bundle (Manifest + Runtime Code) into a Kernel Plugin.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* 1. Register App Manifest as a service (for ObjectQL discovery)
|
|
9
|
+
* 2. Execute Runtime `onEnable` hook (for code logic)
|
|
10
|
+
*/
|
|
11
|
+
export declare class AppPlugin implements Plugin {
|
|
12
|
+
name: string;
|
|
13
|
+
version?: string;
|
|
14
|
+
private bundle;
|
|
15
|
+
constructor(bundle: any);
|
|
16
|
+
init(ctx: PluginContext): Promise<void>;
|
|
17
|
+
start(ctx: PluginContext): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppPlugin
|
|
3
|
+
*
|
|
4
|
+
* Adapts a generic App Bundle (Manifest + Runtime Code) into a Kernel Plugin.
|
|
5
|
+
*
|
|
6
|
+
* Responsibilities:
|
|
7
|
+
* 1. Register App Manifest as a service (for ObjectQL discovery)
|
|
8
|
+
* 2. Execute Runtime `onEnable` hook (for code logic)
|
|
9
|
+
*/
|
|
10
|
+
export class AppPlugin {
|
|
11
|
+
constructor(bundle) {
|
|
12
|
+
this.bundle = bundle;
|
|
13
|
+
// Support both direct manifest (legacy) and Stack Definition (nested manifest)
|
|
14
|
+
const sys = bundle.manifest || bundle;
|
|
15
|
+
const appId = sys.id || sys.name || 'unnamed-app';
|
|
16
|
+
this.name = `plugin.app.${appId}`;
|
|
17
|
+
this.version = sys.version;
|
|
18
|
+
}
|
|
19
|
+
async init(ctx) {
|
|
20
|
+
const sys = this.bundle.manifest || this.bundle;
|
|
21
|
+
const appId = sys.id || sys.name;
|
|
22
|
+
ctx.logger?.log(`[AppPlugin] Registering App Service: ${appId}`);
|
|
23
|
+
// Register the app manifest as a service
|
|
24
|
+
// ObjectQLPlugin will discover this and call ql.registerApp()
|
|
25
|
+
const serviceName = `app.${appId}`;
|
|
26
|
+
ctx.registerService(serviceName, this.bundle.manifest || this.bundle);
|
|
27
|
+
}
|
|
28
|
+
async start(ctx) {
|
|
29
|
+
// Execute Runtime Step
|
|
30
|
+
// Retrieve ObjectQL engine from services
|
|
31
|
+
// We cast to any/ObjectQL because ctx.getService returns unknown
|
|
32
|
+
const ql = ctx.getService('objectql');
|
|
33
|
+
if (!ql) {
|
|
34
|
+
ctx.logger?.warn(`[AppPlugin] ObjectQL engine service not found for app: ${this.name}`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const runtime = this.bundle.default || this.bundle;
|
|
38
|
+
if (runtime && typeof runtime.onEnable === 'function') {
|
|
39
|
+
ctx.logger?.log(`[AppPlugin] Executing runtime.onEnable for: ${this.name}`);
|
|
40
|
+
// Construct the Host Context (mirroring old ObjectQL.use logic)
|
|
41
|
+
const hostContext = {
|
|
42
|
+
...ctx,
|
|
43
|
+
ql,
|
|
44
|
+
logger: ctx.logger || console,
|
|
45
|
+
drivers: {
|
|
46
|
+
register: (driver) => ql.registerDriver(driver)
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
await runtime.onEnable(hostContext);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export { ObjectQL, SchemaRegistry, ObjectStackProtocolImplementation } from '@objectstack/objectql';
|
|
2
1
|
export { ObjectKernel } from '@objectstack/core';
|
|
3
|
-
export { ObjectQLPlugin } from '@objectstack/objectql';
|
|
4
2
|
export { DriverPlugin } from './driver-plugin';
|
|
5
|
-
export {
|
|
3
|
+
export { AppPlugin } from './app-plugin';
|
|
6
4
|
export * from '@objectstack/core';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
// Export core engine
|
|
2
|
-
export { ObjectQL, SchemaRegistry, ObjectStackProtocolImplementation } from '@objectstack/objectql';
|
|
3
1
|
// Export Kernels
|
|
4
2
|
export { ObjectKernel } from '@objectstack/core';
|
|
5
3
|
// Export Plugins
|
|
6
|
-
export { ObjectQLPlugin } from '@objectstack/objectql';
|
|
7
4
|
export { DriverPlugin } from './driver-plugin';
|
|
8
|
-
export {
|
|
5
|
+
export { AppPlugin } from './app-plugin';
|
|
9
6
|
// Export Types
|
|
10
7
|
export * from '@objectstack/core';
|
package/package.json
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/runtime",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "ObjectStack Core Runtime & Query Engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@objectstack/core": "0.6.
|
|
10
|
-
"@objectstack/
|
|
11
|
-
"@objectstack/
|
|
12
|
-
"@objectstack/spec": "0.6.0"
|
|
9
|
+
"@objectstack/core": "0.6.1",
|
|
10
|
+
"@objectstack/types": "0.6.1",
|
|
11
|
+
"@objectstack/spec": "0.6.1"
|
|
13
12
|
},
|
|
14
13
|
"devDependencies": {
|
|
15
14
|
"typescript": "^5.0.0"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AppPlugin
|
|
5
|
+
*
|
|
6
|
+
* Adapts a generic App Bundle (Manifest + Runtime Code) into a Kernel Plugin.
|
|
7
|
+
*
|
|
8
|
+
* Responsibilities:
|
|
9
|
+
* 1. Register App Manifest as a service (for ObjectQL discovery)
|
|
10
|
+
* 2. Execute Runtime `onEnable` hook (for code logic)
|
|
11
|
+
*/
|
|
12
|
+
export class AppPlugin implements Plugin {
|
|
13
|
+
name: string;
|
|
14
|
+
version?: string;
|
|
15
|
+
|
|
16
|
+
private bundle: any;
|
|
17
|
+
|
|
18
|
+
constructor(bundle: any) {
|
|
19
|
+
this.bundle = bundle;
|
|
20
|
+
// Support both direct manifest (legacy) and Stack Definition (nested manifest)
|
|
21
|
+
const sys = bundle.manifest || bundle;
|
|
22
|
+
const appId = sys.id || sys.name || 'unnamed-app';
|
|
23
|
+
|
|
24
|
+
this.name = `plugin.app.${appId}`;
|
|
25
|
+
this.version = sys.version;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async init(ctx: PluginContext) {
|
|
29
|
+
const sys = this.bundle.manifest || this.bundle;
|
|
30
|
+
const appId = sys.id || sys.name;
|
|
31
|
+
|
|
32
|
+
ctx.logger?.log(`[AppPlugin] Registering App Service: ${appId}`);
|
|
33
|
+
|
|
34
|
+
// Register the app manifest as a service
|
|
35
|
+
// ObjectQLPlugin will discover this and call ql.registerApp()
|
|
36
|
+
const serviceName = `app.${appId}`;
|
|
37
|
+
ctx.registerService(serviceName, this.bundle.manifest || this.bundle);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async start(ctx: PluginContext) {
|
|
41
|
+
// Execute Runtime Step
|
|
42
|
+
// Retrieve ObjectQL engine from services
|
|
43
|
+
// We cast to any/ObjectQL because ctx.getService returns unknown
|
|
44
|
+
const ql = ctx.getService('objectql') as any;
|
|
45
|
+
|
|
46
|
+
if (!ql) {
|
|
47
|
+
ctx.logger?.warn(`[AppPlugin] ObjectQL engine service not found for app: ${this.name}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const runtime = this.bundle.default || this.bundle;
|
|
52
|
+
|
|
53
|
+
if (runtime && typeof runtime.onEnable === 'function') {
|
|
54
|
+
ctx.logger?.log(`[AppPlugin] Executing runtime.onEnable for: ${this.name}`);
|
|
55
|
+
|
|
56
|
+
// Construct the Host Context (mirroring old ObjectQL.use logic)
|
|
57
|
+
const hostContext = {
|
|
58
|
+
...ctx,
|
|
59
|
+
ql,
|
|
60
|
+
logger: ctx.logger || console,
|
|
61
|
+
drivers: {
|
|
62
|
+
register: (driver: any) => ql.registerDriver(driver)
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
await runtime.onEnable(hostContext);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
// Export core engine
|
|
2
|
-
export { ObjectQL, SchemaRegistry, ObjectStackProtocolImplementation } from '@objectstack/objectql';
|
|
3
|
-
|
|
4
1
|
// Export Kernels
|
|
5
2
|
export { ObjectKernel } from '@objectstack/core';
|
|
6
3
|
|
|
7
4
|
// Export Plugins
|
|
8
|
-
export { ObjectQLPlugin } from '@objectstack/objectql';
|
|
9
5
|
export { DriverPlugin } from './driver-plugin';
|
|
10
|
-
export {
|
|
6
|
+
export { AppPlugin } from './app-plugin';
|
|
11
7
|
|
|
12
8
|
// Export Types
|
|
13
9
|
export * from '@objectstack/core';
|
|
@@ -1,19 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
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
|
-
}
|
package/dist/test-interfaces.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
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 {};
|
|
@@ -1,48 +0,0 @@
|
|
|
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
|
-
}
|
package/src/test-interfaces.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
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
|
-
import { IHttpServer, IDataEngine, RouteHandler, IHttpRequest, IHttpResponse, Middleware, DataEngineQueryOptions } from './index.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Example: Mock HTTP Server Plugin
|
|
12
|
-
*
|
|
13
|
-
* Shows how a plugin can implement the IHttpServer interface
|
|
14
|
-
* without depending on Express, Fastify, or any specific framework.
|
|
15
|
-
*/
|
|
16
|
-
class MockHttpServer implements IHttpServer {
|
|
17
|
-
private routes: Map<string, { method: string; handler: RouteHandler }> = new Map();
|
|
18
|
-
|
|
19
|
-
get(path: string, handler: RouteHandler): void {
|
|
20
|
-
this.routes.set(`GET:${path}`, { method: 'GET', handler });
|
|
21
|
-
console.log(`✅ Registered GET ${path}`);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
post(path: string, handler: RouteHandler): void {
|
|
25
|
-
this.routes.set(`POST:${path}`, { method: 'POST', handler });
|
|
26
|
-
console.log(`✅ Registered POST ${path}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
put(path: string, handler: RouteHandler): void {
|
|
30
|
-
this.routes.set(`PUT:${path}`, { method: 'PUT', handler });
|
|
31
|
-
console.log(`✅ Registered PUT ${path}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
delete(path: string, handler: RouteHandler): void {
|
|
35
|
-
this.routes.set(`DELETE:${path}`, { method: 'DELETE', handler });
|
|
36
|
-
console.log(`✅ Registered DELETE ${path}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
patch(path: string, handler: RouteHandler): void {
|
|
40
|
-
this.routes.set(`PATCH:${path}`, { method: 'PATCH', handler });
|
|
41
|
-
console.log(`✅ Registered PATCH ${path}`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
use(path: string | Middleware, handler?: Middleware): void {
|
|
45
|
-
console.log(`✅ Registered middleware`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async listen(port: number): Promise<void> {
|
|
49
|
-
console.log(`✅ Mock HTTP server listening on port ${port}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async close(): Promise<void> {
|
|
53
|
-
console.log(`✅ Mock HTTP server closed`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Example: Mock Data Engine Plugin
|
|
59
|
-
*
|
|
60
|
-
* Shows how a plugin can implement the IDataEngine interface
|
|
61
|
-
* without depending on ObjectQL, Prisma, or any specific database.
|
|
62
|
-
*/
|
|
63
|
-
class MockDataEngine implements IDataEngine {
|
|
64
|
-
private store: Map<string, Map<string, any>> = new Map();
|
|
65
|
-
private idCounter = 0;
|
|
66
|
-
|
|
67
|
-
async insert(objectName: string, data: any): Promise<any> {
|
|
68
|
-
if (!this.store.has(objectName)) {
|
|
69
|
-
this.store.set(objectName, new Map());
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const id = `${objectName}_${++this.idCounter}`;
|
|
73
|
-
const record = { id, ...data };
|
|
74
|
-
this.store.get(objectName)!.set(id, record);
|
|
75
|
-
|
|
76
|
-
console.log(`✅ Inserted into ${objectName}:`, record);
|
|
77
|
-
return record;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async find(objectName: string, query?: DataEngineQueryOptions): Promise<any[]> {
|
|
81
|
-
const objectStore = this.store.get(objectName);
|
|
82
|
-
if (!objectStore) {
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const results = Array.from(objectStore.values());
|
|
87
|
-
console.log(`✅ Found ${results.length} records in ${objectName}`);
|
|
88
|
-
return results;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async update(objectName: string, id: any, data: any): Promise<any> {
|
|
92
|
-
const objectStore = this.store.get(objectName);
|
|
93
|
-
if (!objectStore || !objectStore.has(id)) {
|
|
94
|
-
throw new Error(`Record ${id} not found in ${objectName}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const existing = objectStore.get(id);
|
|
98
|
-
const updated = { ...existing, ...data };
|
|
99
|
-
objectStore.set(id, updated);
|
|
100
|
-
|
|
101
|
-
console.log(`✅ Updated ${objectName}/${id}:`, updated);
|
|
102
|
-
return updated;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async delete(objectName: string, id: any): Promise<boolean> {
|
|
106
|
-
const objectStore = this.store.get(objectName);
|
|
107
|
-
if (!objectStore) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const deleted = objectStore.delete(id);
|
|
112
|
-
console.log(`✅ Deleted ${objectName}/${id}: ${deleted}`);
|
|
113
|
-
return deleted;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Test the interfaces
|
|
119
|
-
*/
|
|
120
|
-
async function testInterfaces() {
|
|
121
|
-
console.log('\n=== Testing IHttpServer Interface ===\n');
|
|
122
|
-
|
|
123
|
-
const httpServer: IHttpServer = new MockHttpServer();
|
|
124
|
-
|
|
125
|
-
// Register routes using the interface
|
|
126
|
-
httpServer.get('/api/users', async (req, res) => {
|
|
127
|
-
res.json({ users: [] });
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
httpServer.post('/api/users', async (req, res) => {
|
|
131
|
-
res.status(201).json({ id: 1, ...req.body });
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
await httpServer.listen(3000);
|
|
135
|
-
|
|
136
|
-
console.log('\n=== Testing IDataEngine Interface ===\n');
|
|
137
|
-
|
|
138
|
-
const dataEngine: IDataEngine = new MockDataEngine();
|
|
139
|
-
|
|
140
|
-
// Use the data engine interface
|
|
141
|
-
const user1 = await dataEngine.insert('user', {
|
|
142
|
-
name: 'John Doe',
|
|
143
|
-
email: 'john@example.com'
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
const user2 = await dataEngine.insert('user', {
|
|
147
|
-
name: 'Jane Smith',
|
|
148
|
-
email: 'jane@example.com'
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
const users = await dataEngine.find('user');
|
|
152
|
-
console.log(`Found ${users.length} users after inserts`);
|
|
153
|
-
|
|
154
|
-
const updatedUser = await dataEngine.update('user', user1.id, {
|
|
155
|
-
name: 'John Updated'
|
|
156
|
-
});
|
|
157
|
-
console.log(`Updated user:`, updatedUser);
|
|
158
|
-
|
|
159
|
-
const deleted = await dataEngine.delete('user', user2.id);
|
|
160
|
-
console.log(`Delete result: ${deleted}`);
|
|
161
|
-
|
|
162
|
-
console.log('\n✅ All interface tests passed!\n');
|
|
163
|
-
|
|
164
|
-
if (httpServer.close) {
|
|
165
|
-
await httpServer.close();
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Run tests
|
|
170
|
-
testInterfaces().catch(console.error);
|