@objectstack/runtime 0.6.0 → 0.7.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 +20 -0
- package/README.md +10 -8
- package/dist/app-plugin.d.ts +18 -0
- package/dist/app-plugin.js +80 -0
- package/dist/driver-plugin.js +6 -2
- package/dist/http-server.d.ts +84 -0
- package/dist/http-server.js +125 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +7 -5
- package/dist/middleware.d.ts +111 -0
- package/dist/middleware.js +176 -0
- package/dist/rest-server.d.ts +74 -0
- package/dist/rest-server.js +490 -0
- package/dist/route-manager.d.ts +153 -0
- package/dist/route-manager.js +251 -0
- package/package.json +4 -5
- package/src/app-plugin.ts +100 -0
- package/src/driver-plugin.ts +6 -2
- package/src/http-server.ts +140 -0
- package/src/index.ts +9 -6
- package/src/middleware.ts +220 -0
- package/src/rest-server.ts +579 -0
- package/src/route-manager.ts +305 -0
- 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
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { RouteHandler, IHttpServer } from '@objectstack/core';
|
|
2
|
+
import { System, Shared } from '@objectstack/spec';
|
|
3
|
+
|
|
4
|
+
type RouteHandlerMetadata = System.RouteHandlerMetadata;
|
|
5
|
+
type HttpMethod = Shared.HttpMethod;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Route Entry
|
|
9
|
+
* Internal representation of registered routes
|
|
10
|
+
*/
|
|
11
|
+
export interface RouteEntry {
|
|
12
|
+
method: HttpMethod;
|
|
13
|
+
path: string;
|
|
14
|
+
handler: RouteHandler;
|
|
15
|
+
metadata?: RouteHandlerMetadata['metadata'];
|
|
16
|
+
security?: RouteHandlerMetadata['security'];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* RouteManager
|
|
21
|
+
*
|
|
22
|
+
* Manages route registration and organization for HTTP servers.
|
|
23
|
+
* Provides:
|
|
24
|
+
* - Route registration with metadata
|
|
25
|
+
* - Route lookup and querying
|
|
26
|
+
* - Bulk route registration
|
|
27
|
+
* - Route grouping by prefix
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const manager = new RouteManager(server);
|
|
31
|
+
*
|
|
32
|
+
* // Register individual route
|
|
33
|
+
* manager.register({
|
|
34
|
+
* method: 'GET',
|
|
35
|
+
* path: '/api/users/:id',
|
|
36
|
+
* handler: getUserHandler,
|
|
37
|
+
* metadata: {
|
|
38
|
+
* summary: 'Get user by ID',
|
|
39
|
+
* tags: ['users']
|
|
40
|
+
* }
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* // Register route group
|
|
44
|
+
* manager.group('/api/users', (group) => {
|
|
45
|
+
* group.get('/', listUsersHandler);
|
|
46
|
+
* group.post('/', createUserHandler);
|
|
47
|
+
* group.get('/:id', getUserHandler);
|
|
48
|
+
* });
|
|
49
|
+
*/
|
|
50
|
+
export class RouteManager {
|
|
51
|
+
private server: IHttpServer;
|
|
52
|
+
private routes: Map<string, RouteEntry>;
|
|
53
|
+
|
|
54
|
+
constructor(server: IHttpServer) {
|
|
55
|
+
this.server = server;
|
|
56
|
+
this.routes = new Map();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Register a route
|
|
61
|
+
* @param entry - Route entry with method, path, handler, and metadata
|
|
62
|
+
*/
|
|
63
|
+
register(entry: Omit<RouteEntry, 'handler'> & { handler: RouteHandler | string }): void {
|
|
64
|
+
// Validate handler type - string handlers not yet supported
|
|
65
|
+
if (typeof entry.handler === 'string') {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`String-based route handlers are not supported yet. ` +
|
|
68
|
+
`Received handler identifier "${entry.handler}". ` +
|
|
69
|
+
`Please provide a RouteHandler function instead.`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const handler: RouteHandler = entry.handler;
|
|
74
|
+
|
|
75
|
+
const routeEntry: RouteEntry = {
|
|
76
|
+
method: entry.method,
|
|
77
|
+
path: entry.path,
|
|
78
|
+
handler,
|
|
79
|
+
metadata: entry.metadata,
|
|
80
|
+
security: entry.security,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const key = this.getRouteKey(entry.method, entry.path);
|
|
84
|
+
this.routes.set(key, routeEntry);
|
|
85
|
+
|
|
86
|
+
// Register with underlying server
|
|
87
|
+
this.registerWithServer(routeEntry);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Register multiple routes
|
|
92
|
+
* @param entries - Array of route entries
|
|
93
|
+
*/
|
|
94
|
+
registerMany(entries: Array<Omit<RouteEntry, 'handler'> & { handler: RouteHandler | string }>): void {
|
|
95
|
+
entries.forEach(entry => this.register(entry));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Unregister a route
|
|
100
|
+
* @param method - HTTP method
|
|
101
|
+
* @param path - Route path
|
|
102
|
+
*/
|
|
103
|
+
unregister(method: HttpMethod, path: string): void {
|
|
104
|
+
const key = this.getRouteKey(method, path);
|
|
105
|
+
this.routes.delete(key);
|
|
106
|
+
// Note: Most server frameworks don't support unregistering routes at runtime
|
|
107
|
+
// This just removes it from our registry
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get route by method and path
|
|
112
|
+
* @param method - HTTP method
|
|
113
|
+
* @param path - Route path
|
|
114
|
+
*/
|
|
115
|
+
get(method: HttpMethod, path: string): RouteEntry | undefined {
|
|
116
|
+
const key = this.getRouteKey(method, path);
|
|
117
|
+
return this.routes.get(key);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get all routes
|
|
122
|
+
*/
|
|
123
|
+
getAll(): RouteEntry[] {
|
|
124
|
+
return Array.from(this.routes.values());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get routes by method
|
|
129
|
+
* @param method - HTTP method
|
|
130
|
+
*/
|
|
131
|
+
getByMethod(method: HttpMethod): RouteEntry[] {
|
|
132
|
+
return this.getAll().filter(route => route.method === method);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get routes by path prefix
|
|
137
|
+
* @param prefix - Path prefix
|
|
138
|
+
*/
|
|
139
|
+
getByPrefix(prefix: string): RouteEntry[] {
|
|
140
|
+
return this.getAll().filter(route => route.path.startsWith(prefix));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get routes by tag
|
|
145
|
+
* @param tag - Tag name
|
|
146
|
+
*/
|
|
147
|
+
getByTag(tag: string): RouteEntry[] {
|
|
148
|
+
return this.getAll().filter(route =>
|
|
149
|
+
route.metadata?.tags?.includes(tag)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Create a route group with common prefix
|
|
155
|
+
* @param prefix - Common path prefix
|
|
156
|
+
* @param configure - Function to configure routes in the group
|
|
157
|
+
*/
|
|
158
|
+
group(prefix: string, configure: (group: RouteGroupBuilder) => void): void {
|
|
159
|
+
const builder = new RouteGroupBuilder(this, prefix);
|
|
160
|
+
configure(builder);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get route count
|
|
165
|
+
*/
|
|
166
|
+
count(): number {
|
|
167
|
+
return this.routes.size;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Clear all routes
|
|
172
|
+
*/
|
|
173
|
+
clear(): void {
|
|
174
|
+
this.routes.clear();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get route key for storage
|
|
179
|
+
*/
|
|
180
|
+
private getRouteKey(method: HttpMethod, path: string): string {
|
|
181
|
+
return `${method}:${path}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Register route with underlying server
|
|
186
|
+
*/
|
|
187
|
+
private registerWithServer(entry: RouteEntry): void {
|
|
188
|
+
const { method, path, handler } = entry;
|
|
189
|
+
|
|
190
|
+
switch (method) {
|
|
191
|
+
case 'GET':
|
|
192
|
+
this.server.get(path, handler);
|
|
193
|
+
break;
|
|
194
|
+
case 'POST':
|
|
195
|
+
this.server.post(path, handler);
|
|
196
|
+
break;
|
|
197
|
+
case 'PUT':
|
|
198
|
+
this.server.put(path, handler);
|
|
199
|
+
break;
|
|
200
|
+
case 'DELETE':
|
|
201
|
+
this.server.delete(path, handler);
|
|
202
|
+
break;
|
|
203
|
+
case 'PATCH':
|
|
204
|
+
this.server.patch(path, handler);
|
|
205
|
+
break;
|
|
206
|
+
default:
|
|
207
|
+
throw new Error(`Unsupported HTTP method: ${method}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* RouteGroupBuilder
|
|
214
|
+
*
|
|
215
|
+
* Builder for creating route groups with common prefix
|
|
216
|
+
*/
|
|
217
|
+
export class RouteGroupBuilder {
|
|
218
|
+
private manager: RouteManager;
|
|
219
|
+
private prefix: string;
|
|
220
|
+
|
|
221
|
+
constructor(manager: RouteManager, prefix: string) {
|
|
222
|
+
this.manager = manager;
|
|
223
|
+
this.prefix = prefix;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Register GET route in group
|
|
228
|
+
*/
|
|
229
|
+
get(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {
|
|
230
|
+
this.manager.register({
|
|
231
|
+
method: 'GET',
|
|
232
|
+
path: this.resolvePath(path),
|
|
233
|
+
handler,
|
|
234
|
+
metadata,
|
|
235
|
+
});
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Register POST route in group
|
|
241
|
+
*/
|
|
242
|
+
post(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {
|
|
243
|
+
this.manager.register({
|
|
244
|
+
method: 'POST',
|
|
245
|
+
path: this.resolvePath(path),
|
|
246
|
+
handler,
|
|
247
|
+
metadata,
|
|
248
|
+
});
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Register PUT route in group
|
|
254
|
+
*/
|
|
255
|
+
put(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {
|
|
256
|
+
this.manager.register({
|
|
257
|
+
method: 'PUT',
|
|
258
|
+
path: this.resolvePath(path),
|
|
259
|
+
handler,
|
|
260
|
+
metadata,
|
|
261
|
+
});
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Register PATCH route in group
|
|
267
|
+
*/
|
|
268
|
+
patch(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {
|
|
269
|
+
this.manager.register({
|
|
270
|
+
method: 'PATCH',
|
|
271
|
+
path: this.resolvePath(path),
|
|
272
|
+
handler,
|
|
273
|
+
metadata,
|
|
274
|
+
});
|
|
275
|
+
return this;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Register DELETE route in group
|
|
280
|
+
*/
|
|
281
|
+
delete(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {
|
|
282
|
+
this.manager.register({
|
|
283
|
+
method: 'DELETE',
|
|
284
|
+
path: this.resolvePath(path),
|
|
285
|
+
handler,
|
|
286
|
+
metadata,
|
|
287
|
+
});
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Resolve full path with prefix
|
|
293
|
+
*/
|
|
294
|
+
private resolvePath(path: string): string {
|
|
295
|
+
// Normalize slashes
|
|
296
|
+
const normalizedPrefix = this.prefix.endsWith('/')
|
|
297
|
+
? this.prefix.slice(0, -1)
|
|
298
|
+
: this.prefix;
|
|
299
|
+
const normalizedPath = path.startsWith('/')
|
|
300
|
+
? path
|
|
301
|
+
: '/' + path;
|
|
302
|
+
|
|
303
|
+
return normalizedPrefix + normalizedPath;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -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
|
-
}
|