@objectstack/runtime 1.0.4 → 1.0.5
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/.turbo/turbo-build.log +22 -0
- package/CHANGELOG.md +15 -0
- package/dist/index.cjs +1586 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +629 -0
- package/dist/index.d.ts +628 -14
- package/dist/index.js +1549 -16
- package/dist/index.js.map +1 -0
- package/package.json +6 -5
- package/src/api-registry-plugin.ts +15 -3
- package/src/rest-server.ts +3 -5
- package/src/route-manager.ts +2 -1
- package/src/runtime.ts +0 -1
- package/tsconfig.json +4 -9
- package/dist/api-registry-plugin.d.ts +0 -16
- package/dist/api-registry-plugin.js +0 -42
- package/dist/app-plugin.d.ts +0 -18
- package/dist/app-plugin.js +0 -80
- package/dist/app-plugin.test.d.ts +0 -1
- package/dist/app-plugin.test.js +0 -80
- package/dist/driver-plugin.d.ts +0 -23
- package/dist/driver-plugin.js +0 -35
- package/dist/http-dispatcher.d.ts +0 -106
- package/dist/http-dispatcher.js +0 -539
- package/dist/http-dispatcher.test.d.ts +0 -1
- package/dist/http-dispatcher.test.js +0 -79
- package/dist/http-server.d.ts +0 -84
- package/dist/http-server.js +0 -125
- package/dist/middleware.d.ts +0 -111
- package/dist/middleware.js +0 -176
- package/dist/rest-server.d.ts +0 -74
- package/dist/rest-server.js +0 -518
- package/dist/route-manager.d.ts +0 -153
- package/dist/route-manager.js +0 -251
- package/dist/runtime.d.ts +0 -45
- package/dist/runtime.js +0 -50
- package/dist/runtime.test.d.ts +0 -1
- package/dist/runtime.test.js +0 -57
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1586 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
AppPlugin: () => AppPlugin,
|
|
25
|
+
DriverPlugin: () => DriverPlugin,
|
|
26
|
+
HttpDispatcher: () => HttpDispatcher,
|
|
27
|
+
HttpServer: () => HttpServer,
|
|
28
|
+
MiddlewareManager: () => MiddlewareManager,
|
|
29
|
+
ObjectKernel: () => import_core3.ObjectKernel,
|
|
30
|
+
RestServer: () => RestServer,
|
|
31
|
+
RouteGroupBuilder: () => RouteGroupBuilder,
|
|
32
|
+
RouteManager: () => RouteManager,
|
|
33
|
+
Runtime: () => Runtime,
|
|
34
|
+
createApiRegistryPlugin: () => createApiRegistryPlugin
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_core3 = require("@objectstack/core");
|
|
38
|
+
|
|
39
|
+
// src/runtime.ts
|
|
40
|
+
var import_core = require("@objectstack/core");
|
|
41
|
+
|
|
42
|
+
// src/route-manager.ts
|
|
43
|
+
var RouteManager = class {
|
|
44
|
+
constructor(server) {
|
|
45
|
+
this.server = server;
|
|
46
|
+
this.routes = /* @__PURE__ */ new Map();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Register a route
|
|
50
|
+
* @param entry - Route entry with method, path, handler, and metadata
|
|
51
|
+
*/
|
|
52
|
+
register(entry) {
|
|
53
|
+
if (typeof entry.handler === "string") {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`String-based route handlers are not supported yet. Received handler identifier "${entry.handler}". Please provide a RouteHandler function instead.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const handler = entry.handler;
|
|
59
|
+
const routeEntry = {
|
|
60
|
+
method: entry.method,
|
|
61
|
+
path: entry.path,
|
|
62
|
+
handler,
|
|
63
|
+
metadata: entry.metadata,
|
|
64
|
+
security: entry.security
|
|
65
|
+
};
|
|
66
|
+
const key = this.getRouteKey(entry.method, entry.path);
|
|
67
|
+
this.routes.set(key, routeEntry);
|
|
68
|
+
this.registerWithServer(routeEntry);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Register multiple routes
|
|
72
|
+
* @param entries - Array of route entries
|
|
73
|
+
*/
|
|
74
|
+
registerMany(entries) {
|
|
75
|
+
entries.forEach((entry) => this.register(entry));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Unregister a route
|
|
79
|
+
* @param method - HTTP method
|
|
80
|
+
* @param path - Route path
|
|
81
|
+
*/
|
|
82
|
+
unregister(method, path) {
|
|
83
|
+
const key = this.getRouteKey(method, path);
|
|
84
|
+
this.routes.delete(key);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get route by method and path
|
|
88
|
+
* @param method - HTTP method
|
|
89
|
+
* @param path - Route path
|
|
90
|
+
*/
|
|
91
|
+
get(method, path) {
|
|
92
|
+
const key = this.getRouteKey(method, path);
|
|
93
|
+
return this.routes.get(key);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get all routes
|
|
97
|
+
*/
|
|
98
|
+
getAll() {
|
|
99
|
+
return Array.from(this.routes.values());
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get routes by method
|
|
103
|
+
* @param method - HTTP method
|
|
104
|
+
*/
|
|
105
|
+
getByMethod(method) {
|
|
106
|
+
return this.getAll().filter((route) => route.method === method);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get routes by path prefix
|
|
110
|
+
* @param prefix - Path prefix
|
|
111
|
+
*/
|
|
112
|
+
getByPrefix(prefix) {
|
|
113
|
+
return this.getAll().filter((route) => route.path.startsWith(prefix));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get routes by tag
|
|
117
|
+
* @param tag - Tag name
|
|
118
|
+
*/
|
|
119
|
+
getByTag(tag) {
|
|
120
|
+
return this.getAll().filter(
|
|
121
|
+
(route) => route.metadata?.tags?.includes(tag)
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Create a route group with common prefix
|
|
126
|
+
* @param prefix - Common path prefix
|
|
127
|
+
* @param configure - Function to configure routes in the group
|
|
128
|
+
*/
|
|
129
|
+
group(prefix, configure) {
|
|
130
|
+
const builder = new RouteGroupBuilder(this, prefix);
|
|
131
|
+
configure(builder);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get route count
|
|
135
|
+
*/
|
|
136
|
+
count() {
|
|
137
|
+
return this.routes.size;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Clear all routes
|
|
141
|
+
*/
|
|
142
|
+
clear() {
|
|
143
|
+
this.routes.clear();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get route key for storage
|
|
147
|
+
*/
|
|
148
|
+
getRouteKey(method, path) {
|
|
149
|
+
return `${method}:${path}`;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Register route with underlying server
|
|
153
|
+
*/
|
|
154
|
+
registerWithServer(entry) {
|
|
155
|
+
const { method, path, handler } = entry;
|
|
156
|
+
switch (method) {
|
|
157
|
+
case "GET":
|
|
158
|
+
this.server.get(path, handler);
|
|
159
|
+
break;
|
|
160
|
+
case "POST":
|
|
161
|
+
this.server.post(path, handler);
|
|
162
|
+
break;
|
|
163
|
+
case "PUT":
|
|
164
|
+
this.server.put(path, handler);
|
|
165
|
+
break;
|
|
166
|
+
case "DELETE":
|
|
167
|
+
this.server.delete(path, handler);
|
|
168
|
+
break;
|
|
169
|
+
case "PATCH":
|
|
170
|
+
this.server.patch(path, handler);
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
throw new Error(`Unsupported HTTP method: ${method}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var RouteGroupBuilder = class {
|
|
178
|
+
constructor(manager, prefix) {
|
|
179
|
+
this.manager = manager;
|
|
180
|
+
this.prefix = prefix;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Register GET route in group
|
|
184
|
+
*/
|
|
185
|
+
get(path, handler, metadata) {
|
|
186
|
+
this.manager.register({
|
|
187
|
+
method: "GET",
|
|
188
|
+
path: this.resolvePath(path),
|
|
189
|
+
handler,
|
|
190
|
+
metadata
|
|
191
|
+
});
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Register POST route in group
|
|
196
|
+
*/
|
|
197
|
+
post(path, handler, metadata) {
|
|
198
|
+
this.manager.register({
|
|
199
|
+
method: "POST",
|
|
200
|
+
path: this.resolvePath(path),
|
|
201
|
+
handler,
|
|
202
|
+
metadata
|
|
203
|
+
});
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Register PUT route in group
|
|
208
|
+
*/
|
|
209
|
+
put(path, handler, metadata) {
|
|
210
|
+
this.manager.register({
|
|
211
|
+
method: "PUT",
|
|
212
|
+
path: this.resolvePath(path),
|
|
213
|
+
handler,
|
|
214
|
+
metadata
|
|
215
|
+
});
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Register PATCH route in group
|
|
220
|
+
*/
|
|
221
|
+
patch(path, handler, metadata) {
|
|
222
|
+
this.manager.register({
|
|
223
|
+
method: "PATCH",
|
|
224
|
+
path: this.resolvePath(path),
|
|
225
|
+
handler,
|
|
226
|
+
metadata
|
|
227
|
+
});
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Register DELETE route in group
|
|
232
|
+
*/
|
|
233
|
+
delete(path, handler, metadata) {
|
|
234
|
+
this.manager.register({
|
|
235
|
+
method: "DELETE",
|
|
236
|
+
path: this.resolvePath(path),
|
|
237
|
+
handler,
|
|
238
|
+
metadata
|
|
239
|
+
});
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Resolve full path with prefix
|
|
244
|
+
*/
|
|
245
|
+
resolvePath(path) {
|
|
246
|
+
const normalizedPrefix = this.prefix.endsWith("/") ? this.prefix.slice(0, -1) : this.prefix;
|
|
247
|
+
const normalizedPath = path.startsWith("/") ? path : "/" + path;
|
|
248
|
+
return normalizedPrefix + normalizedPath;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// src/rest-server.ts
|
|
253
|
+
var RestServer = class {
|
|
254
|
+
constructor(server, protocol, config = {}) {
|
|
255
|
+
this.protocol = protocol;
|
|
256
|
+
this.config = this.normalizeConfig(config);
|
|
257
|
+
this.routeManager = new RouteManager(server);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Normalize configuration with defaults
|
|
261
|
+
*/
|
|
262
|
+
normalizeConfig(config) {
|
|
263
|
+
const api = config.api ?? {};
|
|
264
|
+
const crud = config.crud ?? {};
|
|
265
|
+
const metadata = config.metadata ?? {};
|
|
266
|
+
const batch = config.batch ?? {};
|
|
267
|
+
const routes = config.routes ?? {};
|
|
268
|
+
return {
|
|
269
|
+
api: {
|
|
270
|
+
version: api.version ?? "v1",
|
|
271
|
+
basePath: api.basePath ?? "/api",
|
|
272
|
+
apiPath: api.apiPath,
|
|
273
|
+
enableCrud: api.enableCrud ?? true,
|
|
274
|
+
enableMetadata: api.enableMetadata ?? true,
|
|
275
|
+
enableBatch: api.enableBatch ?? true,
|
|
276
|
+
enableDiscovery: api.enableDiscovery ?? true,
|
|
277
|
+
documentation: api.documentation,
|
|
278
|
+
responseFormat: api.responseFormat
|
|
279
|
+
},
|
|
280
|
+
crud: {
|
|
281
|
+
operations: crud.operations ?? {
|
|
282
|
+
create: true,
|
|
283
|
+
read: true,
|
|
284
|
+
update: true,
|
|
285
|
+
delete: true,
|
|
286
|
+
list: true
|
|
287
|
+
},
|
|
288
|
+
patterns: crud.patterns,
|
|
289
|
+
dataPrefix: crud.dataPrefix ?? "/data",
|
|
290
|
+
objectParamStyle: crud.objectParamStyle ?? "path"
|
|
291
|
+
},
|
|
292
|
+
metadata: {
|
|
293
|
+
prefix: metadata.prefix ?? "/meta",
|
|
294
|
+
enableCache: metadata.enableCache ?? true,
|
|
295
|
+
cacheTtl: metadata.cacheTtl ?? 3600,
|
|
296
|
+
endpoints: metadata.endpoints ?? {
|
|
297
|
+
types: true,
|
|
298
|
+
items: true,
|
|
299
|
+
item: true,
|
|
300
|
+
schema: true
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
batch: {
|
|
304
|
+
maxBatchSize: batch.maxBatchSize ?? 200,
|
|
305
|
+
enableBatchEndpoint: batch.enableBatchEndpoint ?? true,
|
|
306
|
+
operations: batch.operations ?? {
|
|
307
|
+
createMany: true,
|
|
308
|
+
updateMany: true,
|
|
309
|
+
deleteMany: true,
|
|
310
|
+
upsertMany: true
|
|
311
|
+
},
|
|
312
|
+
defaultAtomic: batch.defaultAtomic ?? true
|
|
313
|
+
},
|
|
314
|
+
routes: {
|
|
315
|
+
includeObjects: routes.includeObjects,
|
|
316
|
+
excludeObjects: routes.excludeObjects,
|
|
317
|
+
nameTransform: routes.nameTransform ?? "none",
|
|
318
|
+
overrides: routes.overrides
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Get the full API base path
|
|
324
|
+
*/
|
|
325
|
+
getApiBasePath() {
|
|
326
|
+
const { api } = this.config;
|
|
327
|
+
return api.apiPath ?? `${api.basePath}/${api.version}`;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Register all REST API routes
|
|
331
|
+
*/
|
|
332
|
+
registerRoutes() {
|
|
333
|
+
const basePath = this.getApiBasePath();
|
|
334
|
+
if (this.config.api.enableDiscovery) {
|
|
335
|
+
this.registerDiscoveryEndpoints(basePath);
|
|
336
|
+
}
|
|
337
|
+
if (this.config.api.enableMetadata) {
|
|
338
|
+
this.registerMetadataEndpoints(basePath);
|
|
339
|
+
}
|
|
340
|
+
if (this.config.api.enableCrud) {
|
|
341
|
+
this.registerCrudEndpoints(basePath);
|
|
342
|
+
}
|
|
343
|
+
if (this.config.api.enableBatch) {
|
|
344
|
+
this.registerBatchEndpoints(basePath);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Register discovery endpoints
|
|
349
|
+
*/
|
|
350
|
+
registerDiscoveryEndpoints(basePath) {
|
|
351
|
+
this.routeManager.register({
|
|
352
|
+
method: "GET",
|
|
353
|
+
path: basePath,
|
|
354
|
+
handler: async (_req, res) => {
|
|
355
|
+
try {
|
|
356
|
+
const discovery = await this.protocol.getDiscovery({});
|
|
357
|
+
res.json(discovery);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
res.status(500).json({ error: error.message });
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
metadata: {
|
|
363
|
+
summary: "Get API discovery information",
|
|
364
|
+
tags: ["discovery"]
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Register metadata endpoints
|
|
370
|
+
*/
|
|
371
|
+
registerMetadataEndpoints(basePath) {
|
|
372
|
+
const { metadata } = this.config;
|
|
373
|
+
const metaPath = `${basePath}${metadata.prefix}`;
|
|
374
|
+
if (metadata.endpoints.types !== false) {
|
|
375
|
+
this.routeManager.register({
|
|
376
|
+
method: "GET",
|
|
377
|
+
path: metaPath,
|
|
378
|
+
handler: async (_req, res) => {
|
|
379
|
+
try {
|
|
380
|
+
const types = await this.protocol.getMetaTypes({});
|
|
381
|
+
res.json(types);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
res.status(500).json({ error: error.message });
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
metadata: {
|
|
387
|
+
summary: "List all metadata types",
|
|
388
|
+
tags: ["metadata"]
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (metadata.endpoints.items !== false) {
|
|
393
|
+
this.routeManager.register({
|
|
394
|
+
method: "GET",
|
|
395
|
+
path: `${metaPath}/:type`,
|
|
396
|
+
handler: async (req, res) => {
|
|
397
|
+
try {
|
|
398
|
+
const items = await this.protocol.getMetaItems({ type: req.params.type });
|
|
399
|
+
res.json(items);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
res.status(404).json({ error: error.message });
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
metadata: {
|
|
405
|
+
summary: "List metadata items of a type",
|
|
406
|
+
tags: ["metadata"]
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
if (metadata.endpoints.item !== false) {
|
|
411
|
+
this.routeManager.register({
|
|
412
|
+
method: "GET",
|
|
413
|
+
path: `${metaPath}/:type/:name`,
|
|
414
|
+
handler: async (req, res) => {
|
|
415
|
+
try {
|
|
416
|
+
if (metadata.enableCache && this.protocol.getMetaItemCached) {
|
|
417
|
+
const cacheRequest = {
|
|
418
|
+
ifNoneMatch: req.headers["if-none-match"],
|
|
419
|
+
ifModifiedSince: req.headers["if-modified-since"]
|
|
420
|
+
};
|
|
421
|
+
const result = await this.protocol.getMetaItemCached({
|
|
422
|
+
type: req.params.type,
|
|
423
|
+
name: req.params.name,
|
|
424
|
+
cacheRequest
|
|
425
|
+
});
|
|
426
|
+
if (result.notModified) {
|
|
427
|
+
res.status(304).send();
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (result.etag) {
|
|
431
|
+
const etagValue = result.etag.weak ? `W/"${result.etag.value}"` : `"${result.etag.value}"`;
|
|
432
|
+
res.header("ETag", etagValue);
|
|
433
|
+
}
|
|
434
|
+
if (result.lastModified) {
|
|
435
|
+
res.header("Last-Modified", new Date(result.lastModified).toUTCString());
|
|
436
|
+
}
|
|
437
|
+
if (result.cacheControl) {
|
|
438
|
+
const directives = result.cacheControl.directives.join(", ");
|
|
439
|
+
const maxAge = result.cacheControl.maxAge ? `, max-age=${result.cacheControl.maxAge}` : "";
|
|
440
|
+
res.header("Cache-Control", directives + maxAge);
|
|
441
|
+
}
|
|
442
|
+
res.json(result.data);
|
|
443
|
+
} else {
|
|
444
|
+
const item = await this.protocol.getMetaItem({ type: req.params.type, name: req.params.name });
|
|
445
|
+
res.json(item);
|
|
446
|
+
}
|
|
447
|
+
} catch (error) {
|
|
448
|
+
res.status(404).json({ error: error.message });
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
metadata: {
|
|
452
|
+
summary: "Get specific metadata item",
|
|
453
|
+
tags: ["metadata"]
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
this.routeManager.register({
|
|
458
|
+
method: "PUT",
|
|
459
|
+
path: `${metaPath}/:type/:name`,
|
|
460
|
+
handler: async (req, res) => {
|
|
461
|
+
try {
|
|
462
|
+
if (!this.protocol.saveMetaItem) {
|
|
463
|
+
res.status(501).json({ error: "Save operation not supported by protocol implementation" });
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const result = await this.protocol.saveMetaItem({
|
|
467
|
+
type: req.params.type,
|
|
468
|
+
name: req.params.name,
|
|
469
|
+
item: req.body
|
|
470
|
+
});
|
|
471
|
+
res.json(result);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
res.status(400).json({ error: error.message });
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
metadata: {
|
|
477
|
+
summary: "Save specific metadata item",
|
|
478
|
+
tags: ["metadata"]
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Register CRUD endpoints for data operations
|
|
484
|
+
*/
|
|
485
|
+
registerCrudEndpoints(basePath) {
|
|
486
|
+
const { crud } = this.config;
|
|
487
|
+
const dataPath = `${basePath}${crud.dataPrefix}`;
|
|
488
|
+
const operations = crud.operations;
|
|
489
|
+
if (operations.list) {
|
|
490
|
+
this.routeManager.register({
|
|
491
|
+
method: "GET",
|
|
492
|
+
path: `${dataPath}/:object`,
|
|
493
|
+
handler: async (req, res) => {
|
|
494
|
+
try {
|
|
495
|
+
const result = await this.protocol.findData({
|
|
496
|
+
object: req.params.object,
|
|
497
|
+
query: req.query
|
|
498
|
+
});
|
|
499
|
+
res.json(result);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
res.status(400).json({ error: error.message });
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
metadata: {
|
|
505
|
+
summary: "Query records",
|
|
506
|
+
tags: ["data", "crud"]
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
if (operations.read) {
|
|
511
|
+
this.routeManager.register({
|
|
512
|
+
method: "GET",
|
|
513
|
+
path: `${dataPath}/:object/:id`,
|
|
514
|
+
handler: async (req, res) => {
|
|
515
|
+
try {
|
|
516
|
+
const result = await this.protocol.getData({
|
|
517
|
+
object: req.params.object,
|
|
518
|
+
id: req.params.id
|
|
519
|
+
});
|
|
520
|
+
res.json(result);
|
|
521
|
+
} catch (error) {
|
|
522
|
+
res.status(404).json({ error: error.message });
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
metadata: {
|
|
526
|
+
summary: "Get record by ID",
|
|
527
|
+
tags: ["data", "crud"]
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
if (operations.create) {
|
|
532
|
+
this.routeManager.register({
|
|
533
|
+
method: "POST",
|
|
534
|
+
path: `${dataPath}/:object`,
|
|
535
|
+
handler: async (req, res) => {
|
|
536
|
+
try {
|
|
537
|
+
const result = await this.protocol.createData({
|
|
538
|
+
object: req.params.object,
|
|
539
|
+
data: req.body
|
|
540
|
+
});
|
|
541
|
+
res.status(201).json(result);
|
|
542
|
+
} catch (error) {
|
|
543
|
+
res.status(400).json({ error: error.message });
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
metadata: {
|
|
547
|
+
summary: "Create record",
|
|
548
|
+
tags: ["data", "crud"]
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
if (operations.update) {
|
|
553
|
+
this.routeManager.register({
|
|
554
|
+
method: "PATCH",
|
|
555
|
+
path: `${dataPath}/:object/:id`,
|
|
556
|
+
handler: async (req, res) => {
|
|
557
|
+
try {
|
|
558
|
+
const result = await this.protocol.updateData({
|
|
559
|
+
object: req.params.object,
|
|
560
|
+
id: req.params.id,
|
|
561
|
+
data: req.body
|
|
562
|
+
});
|
|
563
|
+
res.json(result);
|
|
564
|
+
} catch (error) {
|
|
565
|
+
res.status(400).json({ error: error.message });
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
metadata: {
|
|
569
|
+
summary: "Update record",
|
|
570
|
+
tags: ["data", "crud"]
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
if (operations.delete) {
|
|
575
|
+
this.routeManager.register({
|
|
576
|
+
method: "DELETE",
|
|
577
|
+
path: `${dataPath}/:object/:id`,
|
|
578
|
+
handler: async (req, res) => {
|
|
579
|
+
try {
|
|
580
|
+
const result = await this.protocol.deleteData({
|
|
581
|
+
object: req.params.object,
|
|
582
|
+
id: req.params.id
|
|
583
|
+
});
|
|
584
|
+
res.json(result);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
res.status(400).json({ error: error.message });
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
metadata: {
|
|
590
|
+
summary: "Delete record",
|
|
591
|
+
tags: ["data", "crud"]
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Register batch operation endpoints
|
|
598
|
+
*/
|
|
599
|
+
registerBatchEndpoints(basePath) {
|
|
600
|
+
const { crud, batch } = this.config;
|
|
601
|
+
const dataPath = `${basePath}${crud.dataPrefix}`;
|
|
602
|
+
const operations = batch.operations;
|
|
603
|
+
if (batch.enableBatchEndpoint && this.protocol.batchData) {
|
|
604
|
+
this.routeManager.register({
|
|
605
|
+
method: "POST",
|
|
606
|
+
path: `${dataPath}/:object/batch`,
|
|
607
|
+
handler: async (req, res) => {
|
|
608
|
+
try {
|
|
609
|
+
const result = await this.protocol.batchData({
|
|
610
|
+
object: req.params.object,
|
|
611
|
+
request: req.body
|
|
612
|
+
});
|
|
613
|
+
res.json(result);
|
|
614
|
+
} catch (error) {
|
|
615
|
+
res.status(400).json({ error: error.message });
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
metadata: {
|
|
619
|
+
summary: "Batch operations",
|
|
620
|
+
tags: ["data", "batch"]
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
if (operations.createMany && this.protocol.createManyData) {
|
|
625
|
+
this.routeManager.register({
|
|
626
|
+
method: "POST",
|
|
627
|
+
path: `${dataPath}/:object/createMany`,
|
|
628
|
+
handler: async (req, res) => {
|
|
629
|
+
try {
|
|
630
|
+
const result = await this.protocol.createManyData({
|
|
631
|
+
object: req.params.object,
|
|
632
|
+
records: req.body || []
|
|
633
|
+
});
|
|
634
|
+
res.status(201).json(result);
|
|
635
|
+
} catch (error) {
|
|
636
|
+
res.status(400).json({ error: error.message });
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
metadata: {
|
|
640
|
+
summary: "Create multiple records",
|
|
641
|
+
tags: ["data", "batch"]
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
if (operations.updateMany && this.protocol.updateManyData) {
|
|
646
|
+
this.routeManager.register({
|
|
647
|
+
method: "POST",
|
|
648
|
+
path: `${dataPath}/:object/updateMany`,
|
|
649
|
+
handler: async (req, res) => {
|
|
650
|
+
try {
|
|
651
|
+
const result = await this.protocol.updateManyData({
|
|
652
|
+
object: req.params.object,
|
|
653
|
+
...req.body
|
|
654
|
+
});
|
|
655
|
+
res.json(result);
|
|
656
|
+
} catch (error) {
|
|
657
|
+
res.status(400).json({ error: error.message });
|
|
658
|
+
}
|
|
659
|
+
},
|
|
660
|
+
metadata: {
|
|
661
|
+
summary: "Update multiple records",
|
|
662
|
+
tags: ["data", "batch"]
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
if (operations.deleteMany && this.protocol.deleteManyData) {
|
|
667
|
+
this.routeManager.register({
|
|
668
|
+
method: "POST",
|
|
669
|
+
path: `${dataPath}/:object/deleteMany`,
|
|
670
|
+
handler: async (req, res) => {
|
|
671
|
+
try {
|
|
672
|
+
const result = await this.protocol.deleteManyData({
|
|
673
|
+
object: req.params.object,
|
|
674
|
+
...req.body
|
|
675
|
+
});
|
|
676
|
+
res.json(result);
|
|
677
|
+
} catch (error) {
|
|
678
|
+
res.status(400).json({ error: error.message });
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
metadata: {
|
|
682
|
+
summary: "Delete multiple records",
|
|
683
|
+
tags: ["data", "batch"]
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Get the route manager
|
|
690
|
+
*/
|
|
691
|
+
getRouteManager() {
|
|
692
|
+
return this.routeManager;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Get all registered routes
|
|
696
|
+
*/
|
|
697
|
+
getRoutes() {
|
|
698
|
+
return this.routeManager.getAll();
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
// src/api-registry-plugin.ts
|
|
703
|
+
function createApiRegistryPlugin(config = {}) {
|
|
704
|
+
return {
|
|
705
|
+
name: "com.objectstack.runtime.api-registry",
|
|
706
|
+
version: "1.0.0",
|
|
707
|
+
init: async (_ctx) => {
|
|
708
|
+
},
|
|
709
|
+
start: async (ctx) => {
|
|
710
|
+
const serverService = config.serverServiceName || "http.server";
|
|
711
|
+
const protocolService = config.protocolServiceName || "protocol";
|
|
712
|
+
let server;
|
|
713
|
+
let protocol;
|
|
714
|
+
try {
|
|
715
|
+
server = ctx.getService(serverService);
|
|
716
|
+
} catch (e) {
|
|
717
|
+
}
|
|
718
|
+
try {
|
|
719
|
+
protocol = ctx.getService(protocolService);
|
|
720
|
+
} catch (e) {
|
|
721
|
+
}
|
|
722
|
+
if (!server) {
|
|
723
|
+
ctx.logger.warn(`ApiRegistryPlugin: HTTP Server service '${serverService}' not found. REST routes skipped.`);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (!protocol) {
|
|
727
|
+
ctx.logger.warn(`ApiRegistryPlugin: Protocol service '${protocolService}' not found. REST routes skipped.`);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
ctx.logger.info("Hydrating REST API from Protocol...");
|
|
731
|
+
try {
|
|
732
|
+
const restServer = new RestServer(server, protocol, config.api);
|
|
733
|
+
restServer.registerRoutes();
|
|
734
|
+
ctx.logger.info("REST API successfully registered");
|
|
735
|
+
} catch (err) {
|
|
736
|
+
ctx.logger.error("Failed to register REST API routes", { error: err.message });
|
|
737
|
+
throw err;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/runtime.ts
|
|
744
|
+
var Runtime = class {
|
|
745
|
+
constructor(config = {}) {
|
|
746
|
+
this.kernel = new import_core.ObjectKernel(config.kernel);
|
|
747
|
+
if (config.server) {
|
|
748
|
+
this.kernel.registerService("http.server", config.server);
|
|
749
|
+
}
|
|
750
|
+
this.kernel.use(createApiRegistryPlugin(config.api));
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Register a plugin
|
|
754
|
+
*/
|
|
755
|
+
use(plugin) {
|
|
756
|
+
this.kernel.use(plugin);
|
|
757
|
+
return this;
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Start the runtime
|
|
761
|
+
* 1. Initializes all plugins (init phase)
|
|
762
|
+
* 2. Starts all plugins (start phase)
|
|
763
|
+
*/
|
|
764
|
+
async start() {
|
|
765
|
+
await this.kernel.bootstrap();
|
|
766
|
+
return this;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Get the kernel instance
|
|
770
|
+
*/
|
|
771
|
+
getKernel() {
|
|
772
|
+
return this.kernel;
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
// src/driver-plugin.ts
|
|
777
|
+
var DriverPlugin = class {
|
|
778
|
+
constructor(driver, driverName) {
|
|
779
|
+
this.version = "1.0.0";
|
|
780
|
+
this.init = async (ctx) => {
|
|
781
|
+
const serviceName = `driver.${this.driver.name || "unknown"}`;
|
|
782
|
+
ctx.registerService(serviceName, this.driver);
|
|
783
|
+
ctx.logger.info("Driver service registered", {
|
|
784
|
+
serviceName,
|
|
785
|
+
driverName: this.driver.name,
|
|
786
|
+
driverVersion: this.driver.version
|
|
787
|
+
});
|
|
788
|
+
};
|
|
789
|
+
this.start = async (ctx) => {
|
|
790
|
+
ctx.logger.debug("Driver plugin started", { driverName: this.driver.name || "unknown" });
|
|
791
|
+
};
|
|
792
|
+
this.driver = driver;
|
|
793
|
+
this.name = `com.objectstack.driver.${driverName || driver.name || "unknown"}`;
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
// src/app-plugin.ts
|
|
798
|
+
var AppPlugin = class {
|
|
799
|
+
constructor(bundle) {
|
|
800
|
+
this.init = async (ctx) => {
|
|
801
|
+
const sys = this.bundle.manifest || this.bundle;
|
|
802
|
+
const appId = sys.id || sys.name;
|
|
803
|
+
ctx.logger.info("Registering App Service", {
|
|
804
|
+
appId,
|
|
805
|
+
pluginName: this.name,
|
|
806
|
+
version: this.version
|
|
807
|
+
});
|
|
808
|
+
const serviceName = `app.${appId}`;
|
|
809
|
+
const servicePayload = this.bundle.manifest ? { ...this.bundle.manifest, ...this.bundle } : this.bundle;
|
|
810
|
+
ctx.registerService(serviceName, servicePayload);
|
|
811
|
+
};
|
|
812
|
+
this.start = async (ctx) => {
|
|
813
|
+
const sys = this.bundle.manifest || this.bundle;
|
|
814
|
+
const appId = sys.id || sys.name;
|
|
815
|
+
const ql = ctx.getService("objectql");
|
|
816
|
+
if (!ql) {
|
|
817
|
+
ctx.logger.warn("ObjectQL engine service not found", {
|
|
818
|
+
appName: this.name,
|
|
819
|
+
appId
|
|
820
|
+
});
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
ctx.logger.debug("Retrieved ObjectQL engine service", { appId });
|
|
824
|
+
const runtime = this.bundle.default || this.bundle;
|
|
825
|
+
if (runtime && typeof runtime.onEnable === "function") {
|
|
826
|
+
ctx.logger.info("Executing runtime.onEnable", {
|
|
827
|
+
appName: this.name,
|
|
828
|
+
appId
|
|
829
|
+
});
|
|
830
|
+
const hostContext = {
|
|
831
|
+
...ctx,
|
|
832
|
+
ql,
|
|
833
|
+
logger: ctx.logger,
|
|
834
|
+
drivers: {
|
|
835
|
+
register: (driver) => {
|
|
836
|
+
ctx.logger.debug("Registering driver via app runtime", {
|
|
837
|
+
driverName: driver.name,
|
|
838
|
+
appId
|
|
839
|
+
});
|
|
840
|
+
ql.registerDriver(driver);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
await runtime.onEnable(hostContext);
|
|
845
|
+
ctx.logger.debug("Runtime.onEnable completed", { appId });
|
|
846
|
+
} else {
|
|
847
|
+
ctx.logger.debug("No runtime.onEnable function found", { appId });
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
this.bundle = bundle;
|
|
851
|
+
const sys = bundle.manifest || bundle;
|
|
852
|
+
const appId = sys.id || sys.name || "unnamed-app";
|
|
853
|
+
this.name = `plugin.app.${appId}`;
|
|
854
|
+
this.version = sys.version;
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
// src/http-server.ts
|
|
859
|
+
var HttpServer = class {
|
|
860
|
+
/**
|
|
861
|
+
* Create an HTTP server wrapper
|
|
862
|
+
* @param server - The underlying server implementation (Hono, Express, etc.)
|
|
863
|
+
*/
|
|
864
|
+
constructor(server) {
|
|
865
|
+
this.server = server;
|
|
866
|
+
this.routes = /* @__PURE__ */ new Map();
|
|
867
|
+
this.middlewares = [];
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Register a GET route handler
|
|
871
|
+
* @param path - Route path (e.g., '/api/users/:id')
|
|
872
|
+
* @param handler - Route handler function
|
|
873
|
+
*/
|
|
874
|
+
get(path, handler) {
|
|
875
|
+
const key = `GET:${path}`;
|
|
876
|
+
this.routes.set(key, handler);
|
|
877
|
+
this.server.get(path, handler);
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Register a POST route handler
|
|
881
|
+
* @param path - Route path
|
|
882
|
+
* @param handler - Route handler function
|
|
883
|
+
*/
|
|
884
|
+
post(path, handler) {
|
|
885
|
+
const key = `POST:${path}`;
|
|
886
|
+
this.routes.set(key, handler);
|
|
887
|
+
this.server.post(path, handler);
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Register a PUT route handler
|
|
891
|
+
* @param path - Route path
|
|
892
|
+
* @param handler - Route handler function
|
|
893
|
+
*/
|
|
894
|
+
put(path, handler) {
|
|
895
|
+
const key = `PUT:${path}`;
|
|
896
|
+
this.routes.set(key, handler);
|
|
897
|
+
this.server.put(path, handler);
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Register a DELETE route handler
|
|
901
|
+
* @param path - Route path
|
|
902
|
+
* @param handler - Route handler function
|
|
903
|
+
*/
|
|
904
|
+
delete(path, handler) {
|
|
905
|
+
const key = `DELETE:${path}`;
|
|
906
|
+
this.routes.set(key, handler);
|
|
907
|
+
this.server.delete(path, handler);
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Register a PATCH route handler
|
|
911
|
+
* @param path - Route path
|
|
912
|
+
* @param handler - Route handler function
|
|
913
|
+
*/
|
|
914
|
+
patch(path, handler) {
|
|
915
|
+
const key = `PATCH:${path}`;
|
|
916
|
+
this.routes.set(key, handler);
|
|
917
|
+
this.server.patch(path, handler);
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Register middleware
|
|
921
|
+
* @param path - Optional path to apply middleware to (if omitted, applies globally)
|
|
922
|
+
* @param handler - Middleware function
|
|
923
|
+
*/
|
|
924
|
+
use(path, handler) {
|
|
925
|
+
if (typeof path === "function") {
|
|
926
|
+
this.middlewares.push(path);
|
|
927
|
+
this.server.use(path);
|
|
928
|
+
} else if (handler) {
|
|
929
|
+
this.middlewares.push(handler);
|
|
930
|
+
this.server.use(path, handler);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Start the HTTP server
|
|
935
|
+
* @param port - Port number to listen on
|
|
936
|
+
* @returns Promise that resolves when server is ready
|
|
937
|
+
*/
|
|
938
|
+
async listen(port) {
|
|
939
|
+
await this.server.listen(port);
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Stop the HTTP server
|
|
943
|
+
* @returns Promise that resolves when server is stopped
|
|
944
|
+
*/
|
|
945
|
+
async close() {
|
|
946
|
+
if (this.server.close) {
|
|
947
|
+
await this.server.close();
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Get registered routes
|
|
952
|
+
* @returns Map of route keys to handlers
|
|
953
|
+
*/
|
|
954
|
+
getRoutes() {
|
|
955
|
+
return new Map(this.routes);
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Get registered middlewares
|
|
959
|
+
* @returns Array of middleware functions
|
|
960
|
+
*/
|
|
961
|
+
getMiddlewares() {
|
|
962
|
+
return [...this.middlewares];
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
// src/http-dispatcher.ts
|
|
967
|
+
var import_core2 = require("@objectstack/core");
|
|
968
|
+
var import_system = require("@objectstack/spec/system");
|
|
969
|
+
var HttpDispatcher = class {
|
|
970
|
+
// Casting to any to access dynamic props like broker, services, graphql
|
|
971
|
+
constructor(kernel) {
|
|
972
|
+
this.kernel = kernel;
|
|
973
|
+
}
|
|
974
|
+
success(data, meta) {
|
|
975
|
+
return {
|
|
976
|
+
status: 200,
|
|
977
|
+
body: { success: true, data, meta }
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
error(message, code = 500, details) {
|
|
981
|
+
return {
|
|
982
|
+
status: code,
|
|
983
|
+
body: { success: false, error: { message, code, details } }
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
ensureBroker() {
|
|
987
|
+
if (!this.kernel.broker) {
|
|
988
|
+
throw { statusCode: 500, message: "Kernel Broker not available" };
|
|
989
|
+
}
|
|
990
|
+
return this.kernel.broker;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Generates the discovery JSON response for the API root
|
|
994
|
+
*/
|
|
995
|
+
getDiscoveryInfo(prefix) {
|
|
996
|
+
const services = this.getServicesMap();
|
|
997
|
+
const hasGraphQL = !!(services[import_system.CoreServiceName.enum.graphql] || this.kernel.graphql);
|
|
998
|
+
const hasSearch = !!services[import_system.CoreServiceName.enum.search];
|
|
999
|
+
const hasWebSockets = !!services[import_system.CoreServiceName.enum.realtime];
|
|
1000
|
+
const hasFiles = !!(services[import_system.CoreServiceName.enum["file-storage"]] || services["storage"]?.supportsFiles);
|
|
1001
|
+
const hasAnalytics = !!services[import_system.CoreServiceName.enum.analytics];
|
|
1002
|
+
const hasHub = !!services[import_system.CoreServiceName.enum.hub];
|
|
1003
|
+
return {
|
|
1004
|
+
name: "ObjectOS",
|
|
1005
|
+
version: "1.0.0",
|
|
1006
|
+
environment: (0, import_core2.getEnv)("NODE_ENV", "development"),
|
|
1007
|
+
routes: {
|
|
1008
|
+
data: `${prefix}/data`,
|
|
1009
|
+
metadata: `${prefix}/metadata`,
|
|
1010
|
+
auth: `${prefix}/auth`,
|
|
1011
|
+
graphql: hasGraphQL ? `${prefix}/graphql` : void 0,
|
|
1012
|
+
storage: hasFiles ? `${prefix}/storage` : void 0,
|
|
1013
|
+
analytics: hasAnalytics ? `${prefix}/analytics` : void 0,
|
|
1014
|
+
hub: hasHub ? `${prefix}/hub` : void 0
|
|
1015
|
+
},
|
|
1016
|
+
features: {
|
|
1017
|
+
graphql: hasGraphQL,
|
|
1018
|
+
search: hasSearch,
|
|
1019
|
+
websockets: hasWebSockets,
|
|
1020
|
+
files: hasFiles,
|
|
1021
|
+
analytics: hasAnalytics,
|
|
1022
|
+
hub: hasHub
|
|
1023
|
+
},
|
|
1024
|
+
locale: {
|
|
1025
|
+
default: "en",
|
|
1026
|
+
supported: ["en", "zh-CN"],
|
|
1027
|
+
timezone: "UTC"
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Handles GraphQL requests
|
|
1033
|
+
*/
|
|
1034
|
+
async handleGraphQL(body, context) {
|
|
1035
|
+
if (!body || !body.query) {
|
|
1036
|
+
throw { statusCode: 400, message: "Missing query in request body" };
|
|
1037
|
+
}
|
|
1038
|
+
if (typeof this.kernel.graphql !== "function") {
|
|
1039
|
+
throw { statusCode: 501, message: "GraphQL service not available" };
|
|
1040
|
+
}
|
|
1041
|
+
return this.kernel.graphql(body.query, body.variables, {
|
|
1042
|
+
request: context.request
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Handles Auth requests
|
|
1047
|
+
* path: sub-path after /auth/
|
|
1048
|
+
*/
|
|
1049
|
+
async handleAuth(path, method, body, context) {
|
|
1050
|
+
const authService = this.getService(import_system.CoreServiceName.enum.auth);
|
|
1051
|
+
if (authService && typeof authService.handler === "function") {
|
|
1052
|
+
const response = await authService.handler(context.request, context.response);
|
|
1053
|
+
return { handled: true, result: response };
|
|
1054
|
+
}
|
|
1055
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
1056
|
+
if (normalizedPath === "login" && method.toUpperCase() === "POST") {
|
|
1057
|
+
const broker = this.ensureBroker();
|
|
1058
|
+
const data = await broker.call("auth.login", body, { request: context.request });
|
|
1059
|
+
return { handled: true, response: { status: 200, body: data } };
|
|
1060
|
+
}
|
|
1061
|
+
return { handled: false };
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Handles Metadata requests
|
|
1065
|
+
* Standard: /metadata/:type/:name
|
|
1066
|
+
* Fallback for backward compat: /metadata (all objects), /metadata/:objectName (get object)
|
|
1067
|
+
*/
|
|
1068
|
+
async handleMetadata(path, context, method, body) {
|
|
1069
|
+
const broker = this.ensureBroker();
|
|
1070
|
+
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
1071
|
+
if (parts[0] === "types") {
|
|
1072
|
+
return { handled: true, response: this.success({ types: ["objects", "apps", "plugins"] }) };
|
|
1073
|
+
}
|
|
1074
|
+
if (parts.length === 2) {
|
|
1075
|
+
const [type, name] = parts;
|
|
1076
|
+
if (method === "PUT" && body) {
|
|
1077
|
+
const protocol = this.kernel?.context?.getService ? this.kernel.context.getService("protocol") : null;
|
|
1078
|
+
if (protocol && typeof protocol.saveMetaItem === "function") {
|
|
1079
|
+
try {
|
|
1080
|
+
const result = await protocol.saveMetaItem({ type, name, item: body });
|
|
1081
|
+
return { handled: true, response: this.success(result) };
|
|
1082
|
+
} catch (e) {
|
|
1083
|
+
return { handled: true, response: this.error(e.message, 400) };
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
try {
|
|
1087
|
+
const data = await broker.call("metadata.saveItem", { type, name, item: body }, { request: context.request });
|
|
1088
|
+
return { handled: true, response: this.success(data) };
|
|
1089
|
+
} catch (e) {
|
|
1090
|
+
return { handled: true, response: this.error(e.message || "Save not supported", 501) };
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
try {
|
|
1094
|
+
if (type === "objects") {
|
|
1095
|
+
const data2 = await broker.call("metadata.getObject", { objectName: name }, { request: context.request });
|
|
1096
|
+
return { handled: true, response: this.success(data2) };
|
|
1097
|
+
}
|
|
1098
|
+
const data = await broker.call(`metadata.get${this.capitalize(type.slice(0, -1))}`, { name }, { request: context.request });
|
|
1099
|
+
return { handled: true, response: this.success(data) };
|
|
1100
|
+
} catch (e) {
|
|
1101
|
+
return { handled: true, response: this.error(e.message, 404) };
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (parts.length === 1) {
|
|
1105
|
+
const typeOrName = parts[0];
|
|
1106
|
+
if (["objects", "apps", "plugins"].includes(typeOrName)) {
|
|
1107
|
+
if (typeOrName === "objects") {
|
|
1108
|
+
const data3 = await broker.call("metadata.objects", {}, { request: context.request });
|
|
1109
|
+
return { handled: true, response: this.success(data3) };
|
|
1110
|
+
}
|
|
1111
|
+
const data2 = await broker.call(`metadata.${typeOrName}`, {}, { request: context.request });
|
|
1112
|
+
return { handled: true, response: this.success(data2) };
|
|
1113
|
+
}
|
|
1114
|
+
const data = await broker.call("metadata.getObject", { objectName: typeOrName }, { request: context.request });
|
|
1115
|
+
return { handled: true, response: this.success(data) };
|
|
1116
|
+
}
|
|
1117
|
+
if (parts.length === 0) {
|
|
1118
|
+
const data = await broker.call("metadata.objects", {}, { request: context.request });
|
|
1119
|
+
return { handled: true, response: this.success(data) };
|
|
1120
|
+
}
|
|
1121
|
+
return { handled: false };
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Handles Data requests
|
|
1125
|
+
* path: sub-path after /data/ (e.g. "contacts", "contacts/123", "contacts/query")
|
|
1126
|
+
*/
|
|
1127
|
+
async handleData(path, method, body, query, context) {
|
|
1128
|
+
const broker = this.ensureBroker();
|
|
1129
|
+
const parts = path.replace(/^\/+/, "").split("/");
|
|
1130
|
+
const objectName = parts[0];
|
|
1131
|
+
if (!objectName) {
|
|
1132
|
+
return { handled: true, response: this.error("Object name required", 400) };
|
|
1133
|
+
}
|
|
1134
|
+
const m = method.toUpperCase();
|
|
1135
|
+
if (parts.length > 1) {
|
|
1136
|
+
const action = parts[1];
|
|
1137
|
+
if (action === "query" && m === "POST") {
|
|
1138
|
+
const result = await broker.call("data.query", { object: objectName, ...body }, { request: context.request });
|
|
1139
|
+
return { handled: true, response: this.success(result.data, { count: result.count, limit: body.limit, skip: body.skip }) };
|
|
1140
|
+
}
|
|
1141
|
+
if (action === "batch" && m === "POST") {
|
|
1142
|
+
const result = await broker.call("data.batch", { object: objectName, ...body }, { request: context.request });
|
|
1143
|
+
return { handled: true, response: this.success(result) };
|
|
1144
|
+
}
|
|
1145
|
+
if (parts.length === 2 && m === "GET") {
|
|
1146
|
+
const id = parts[1];
|
|
1147
|
+
const data = await broker.call("data.get", { object: objectName, id, ...query }, { request: context.request });
|
|
1148
|
+
return { handled: true, response: this.success(data) };
|
|
1149
|
+
}
|
|
1150
|
+
if (parts.length === 2 && m === "PATCH") {
|
|
1151
|
+
const id = parts[1];
|
|
1152
|
+
const data = await broker.call("data.update", { object: objectName, id, data: body }, { request: context.request });
|
|
1153
|
+
return { handled: true, response: this.success(data) };
|
|
1154
|
+
}
|
|
1155
|
+
if (parts.length === 2 && m === "DELETE") {
|
|
1156
|
+
const id = parts[1];
|
|
1157
|
+
await broker.call("data.delete", { object: objectName, id }, { request: context.request });
|
|
1158
|
+
return { handled: true, response: this.success({ id, deleted: true }) };
|
|
1159
|
+
}
|
|
1160
|
+
} else {
|
|
1161
|
+
if (m === "GET") {
|
|
1162
|
+
const result = await broker.call("data.query", { object: objectName, filters: query }, { request: context.request });
|
|
1163
|
+
return { handled: true, response: this.success(result.data, { count: result.count }) };
|
|
1164
|
+
}
|
|
1165
|
+
if (m === "POST") {
|
|
1166
|
+
const data = await broker.call("data.create", { object: objectName, data: body }, { request: context.request });
|
|
1167
|
+
const res = this.success(data);
|
|
1168
|
+
res.status = 201;
|
|
1169
|
+
return { handled: true, response: res };
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return { handled: false };
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Handles Analytics requests
|
|
1176
|
+
* path: sub-path after /analytics/
|
|
1177
|
+
*/
|
|
1178
|
+
async handleAnalytics(path, method, body, context) {
|
|
1179
|
+
const analyticsService = this.getService(import_system.CoreServiceName.enum.analytics);
|
|
1180
|
+
if (!analyticsService) return { handled: false };
|
|
1181
|
+
const m = method.toUpperCase();
|
|
1182
|
+
const subPath = path.replace(/^\/+/, "");
|
|
1183
|
+
if (subPath === "query" && m === "POST") {
|
|
1184
|
+
const result = await analyticsService.query(body, { request: context.request });
|
|
1185
|
+
return { handled: true, response: this.success(result) };
|
|
1186
|
+
}
|
|
1187
|
+
if (subPath === "meta" && m === "GET") {
|
|
1188
|
+
const result = await analyticsService.getMetadata({ request: context.request });
|
|
1189
|
+
return { handled: true, response: this.success(result) };
|
|
1190
|
+
}
|
|
1191
|
+
if (subPath === "sql" && m === "POST") {
|
|
1192
|
+
const result = await analyticsService.generateSql(body, { request: context.request });
|
|
1193
|
+
return { handled: true, response: this.success(result) };
|
|
1194
|
+
}
|
|
1195
|
+
return { handled: false };
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Handles Hub requests
|
|
1199
|
+
* path: sub-path after /hub/
|
|
1200
|
+
*/
|
|
1201
|
+
async handleHub(path, method, body, query, context) {
|
|
1202
|
+
const hubService = this.getService(import_system.CoreServiceName.enum.hub);
|
|
1203
|
+
if (!hubService) return { handled: false };
|
|
1204
|
+
const m = method.toUpperCase();
|
|
1205
|
+
const parts = path.replace(/^\/+/, "").split("/");
|
|
1206
|
+
if (parts.length > 0) {
|
|
1207
|
+
const resource = parts[0];
|
|
1208
|
+
const actionBase = resource.endsWith("s") ? resource.slice(0, -1) : resource;
|
|
1209
|
+
const id = parts[1];
|
|
1210
|
+
try {
|
|
1211
|
+
if (parts.length === 1) {
|
|
1212
|
+
if (m === "GET") {
|
|
1213
|
+
const capitalizedAction = "list" + this.capitalize(resource);
|
|
1214
|
+
if (typeof hubService[capitalizedAction] === "function") {
|
|
1215
|
+
const result = await hubService[capitalizedAction](query, { request: context.request });
|
|
1216
|
+
return { handled: true, response: this.success(result) };
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
if (m === "POST") {
|
|
1220
|
+
const capitalizedAction = "create" + this.capitalize(actionBase);
|
|
1221
|
+
if (typeof hubService[capitalizedAction] === "function") {
|
|
1222
|
+
const result = await hubService[capitalizedAction](body, { request: context.request });
|
|
1223
|
+
return { handled: true, response: this.success(result) };
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
} else if (parts.length === 2) {
|
|
1227
|
+
if (m === "GET") {
|
|
1228
|
+
const capitalizedAction = "get" + this.capitalize(actionBase);
|
|
1229
|
+
if (typeof hubService[capitalizedAction] === "function") {
|
|
1230
|
+
const result = await hubService[capitalizedAction](id, { request: context.request });
|
|
1231
|
+
return { handled: true, response: this.success(result) };
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if (m === "PATCH" || m === "PUT") {
|
|
1235
|
+
const capitalizedAction = "update" + this.capitalize(actionBase);
|
|
1236
|
+
if (typeof hubService[capitalizedAction] === "function") {
|
|
1237
|
+
const result = await hubService[capitalizedAction](id, body, { request: context.request });
|
|
1238
|
+
return { handled: true, response: this.success(result) };
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
if (m === "DELETE") {
|
|
1242
|
+
const capitalizedAction = "delete" + this.capitalize(actionBase);
|
|
1243
|
+
if (typeof hubService[capitalizedAction] === "function") {
|
|
1244
|
+
const result = await hubService[capitalizedAction](id, { request: context.request });
|
|
1245
|
+
return { handled: true, response: this.success(result) };
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
} catch (e) {
|
|
1250
|
+
return { handled: true, response: this.error(e.message, 500) };
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return { handled: false };
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Handles Storage requests
|
|
1257
|
+
* path: sub-path after /storage/
|
|
1258
|
+
*/
|
|
1259
|
+
async handleStorage(path, method, file, context) {
|
|
1260
|
+
const storageService = this.getService(import_system.CoreServiceName.enum["file-storage"]) || this.kernel.services?.["file-storage"];
|
|
1261
|
+
if (!storageService) {
|
|
1262
|
+
return { handled: true, response: this.error("File storage not configured", 501) };
|
|
1263
|
+
}
|
|
1264
|
+
const m = method.toUpperCase();
|
|
1265
|
+
const parts = path.replace(/^\/+/, "").split("/");
|
|
1266
|
+
if (parts[0] === "upload" && m === "POST") {
|
|
1267
|
+
if (!file) {
|
|
1268
|
+
return { handled: true, response: this.error("No file provided", 400) };
|
|
1269
|
+
}
|
|
1270
|
+
const result = await storageService.upload(file, { request: context.request });
|
|
1271
|
+
return { handled: true, response: this.success(result) };
|
|
1272
|
+
}
|
|
1273
|
+
if (parts[0] === "file" && parts[1] && m === "GET") {
|
|
1274
|
+
const id = parts[1];
|
|
1275
|
+
const result = await storageService.download(id, { request: context.request });
|
|
1276
|
+
if (result.url && result.redirect) {
|
|
1277
|
+
return { handled: true, result: { type: "redirect", url: result.url } };
|
|
1278
|
+
}
|
|
1279
|
+
if (result.stream) {
|
|
1280
|
+
return {
|
|
1281
|
+
handled: true,
|
|
1282
|
+
result: {
|
|
1283
|
+
type: "stream",
|
|
1284
|
+
stream: result.stream,
|
|
1285
|
+
headers: {
|
|
1286
|
+
"Content-Type": result.mimeType || "application/octet-stream",
|
|
1287
|
+
"Content-Length": result.size
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
return { handled: true, response: this.success(result) };
|
|
1293
|
+
}
|
|
1294
|
+
return { handled: false };
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Handles Automation requests
|
|
1298
|
+
* path: sub-path after /automation/
|
|
1299
|
+
*/
|
|
1300
|
+
async handleAutomation(path, method, body, context) {
|
|
1301
|
+
const automationService = this.getService(import_system.CoreServiceName.enum.automation);
|
|
1302
|
+
if (!automationService) return { handled: false };
|
|
1303
|
+
const m = method.toUpperCase();
|
|
1304
|
+
const parts = path.replace(/^\/+/, "").split("/");
|
|
1305
|
+
if (parts[0] === "trigger" && parts[1] && m === "POST") {
|
|
1306
|
+
const triggerName = parts[1];
|
|
1307
|
+
if (typeof automationService.trigger === "function") {
|
|
1308
|
+
const result = await automationService.trigger(triggerName, body, { request: context.request });
|
|
1309
|
+
return { handled: true, response: this.success(result) };
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
return { handled: false };
|
|
1313
|
+
}
|
|
1314
|
+
getServicesMap() {
|
|
1315
|
+
if (this.kernel.services instanceof Map) {
|
|
1316
|
+
return Object.fromEntries(this.kernel.services);
|
|
1317
|
+
}
|
|
1318
|
+
return this.kernel.services || {};
|
|
1319
|
+
}
|
|
1320
|
+
getService(name) {
|
|
1321
|
+
if (typeof this.kernel.getService === "function") {
|
|
1322
|
+
return this.kernel.getService(name);
|
|
1323
|
+
}
|
|
1324
|
+
const services = this.getServicesMap();
|
|
1325
|
+
return services[name];
|
|
1326
|
+
}
|
|
1327
|
+
capitalize(s) {
|
|
1328
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Main Dispatcher Entry Point
|
|
1332
|
+
* Routes the request to the appropriate handler based on path and precedence
|
|
1333
|
+
*/
|
|
1334
|
+
async dispatch(method, path, body, query, context) {
|
|
1335
|
+
const cleanPath = path.replace(/\/$/, "");
|
|
1336
|
+
if (cleanPath.startsWith("/auth")) {
|
|
1337
|
+
return this.handleAuth(cleanPath.substring(5), method, body, context);
|
|
1338
|
+
}
|
|
1339
|
+
if (cleanPath.startsWith("/metadata")) {
|
|
1340
|
+
return this.handleMetadata(cleanPath.substring(9), context);
|
|
1341
|
+
}
|
|
1342
|
+
if (cleanPath.startsWith("/data")) {
|
|
1343
|
+
return this.handleData(cleanPath.substring(5), method, body, query, context);
|
|
1344
|
+
}
|
|
1345
|
+
if (cleanPath.startsWith("/graphql")) {
|
|
1346
|
+
if (method === "POST") return this.handleGraphQL(body, context);
|
|
1347
|
+
}
|
|
1348
|
+
if (cleanPath.startsWith("/storage")) {
|
|
1349
|
+
return this.handleStorage(cleanPath.substring(8), method, body, context);
|
|
1350
|
+
}
|
|
1351
|
+
if (cleanPath.startsWith("/automation")) {
|
|
1352
|
+
return this.handleAutomation(cleanPath.substring(11), method, body, context);
|
|
1353
|
+
}
|
|
1354
|
+
if (cleanPath.startsWith("/analytics")) {
|
|
1355
|
+
return this.handleAnalytics(cleanPath.substring(10), method, body, context);
|
|
1356
|
+
}
|
|
1357
|
+
if (cleanPath.startsWith("/hub")) {
|
|
1358
|
+
return this.handleHub(cleanPath.substring(4), method, body, query, context);
|
|
1359
|
+
}
|
|
1360
|
+
if (cleanPath === "/openapi.json" && method === "GET") {
|
|
1361
|
+
const broker = this.ensureBroker();
|
|
1362
|
+
try {
|
|
1363
|
+
const result2 = await broker.call("metadata.generateOpenApi", {}, { request: context.request });
|
|
1364
|
+
return { handled: true, response: this.success(result2) };
|
|
1365
|
+
} catch (e) {
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
const result = await this.handleApiEndpoint(cleanPath, method, body, query, context);
|
|
1369
|
+
if (result.handled) return result;
|
|
1370
|
+
return { handled: false };
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Handles Custom API Endpoints defined in metadata
|
|
1374
|
+
*/
|
|
1375
|
+
async handleApiEndpoint(path, method, body, query, context) {
|
|
1376
|
+
const broker = this.ensureBroker();
|
|
1377
|
+
try {
|
|
1378
|
+
const endpoint = await broker.call("metadata.matchEndpoint", { path, method });
|
|
1379
|
+
if (endpoint) {
|
|
1380
|
+
if (endpoint.type === "flow") {
|
|
1381
|
+
const result = await broker.call("automation.runFlow", {
|
|
1382
|
+
flowId: endpoint.target,
|
|
1383
|
+
inputs: { ...query, ...body, _request: context.request }
|
|
1384
|
+
});
|
|
1385
|
+
return { handled: true, response: this.success(result) };
|
|
1386
|
+
}
|
|
1387
|
+
if (endpoint.type === "script") {
|
|
1388
|
+
const result = await broker.call("automation.runScript", {
|
|
1389
|
+
scriptName: endpoint.target,
|
|
1390
|
+
context: { ...query, ...body, request: context.request }
|
|
1391
|
+
}, { request: context.request });
|
|
1392
|
+
return { handled: true, response: this.success(result) };
|
|
1393
|
+
}
|
|
1394
|
+
if (endpoint.type === "object_operation") {
|
|
1395
|
+
if (endpoint.objectParams) {
|
|
1396
|
+
const { object, operation } = endpoint.objectParams;
|
|
1397
|
+
if (operation === "find") {
|
|
1398
|
+
const result = await broker.call("data.query", { object, filters: query }, { request: context.request });
|
|
1399
|
+
return { handled: true, response: this.success(result.data, { count: result.count }) };
|
|
1400
|
+
}
|
|
1401
|
+
if (operation === "get" && query.id) {
|
|
1402
|
+
const result = await broker.call("data.get", { object, id: query.id }, { request: context.request });
|
|
1403
|
+
return { handled: true, response: this.success(result) };
|
|
1404
|
+
}
|
|
1405
|
+
if (operation === "create") {
|
|
1406
|
+
const result = await broker.call("data.create", { object, data: body }, { request: context.request });
|
|
1407
|
+
return { handled: true, response: this.success(result) };
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
if (endpoint.type === "proxy") {
|
|
1412
|
+
return {
|
|
1413
|
+
handled: true,
|
|
1414
|
+
response: {
|
|
1415
|
+
status: 200,
|
|
1416
|
+
body: { proxy: true, target: endpoint.target, note: "Proxy execution requires http-client service" }
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
} catch (e) {
|
|
1422
|
+
}
|
|
1423
|
+
return { handled: false };
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
// src/middleware.ts
|
|
1428
|
+
var MiddlewareManager = class {
|
|
1429
|
+
constructor() {
|
|
1430
|
+
this.middlewares = /* @__PURE__ */ new Map();
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Register middleware with configuration
|
|
1434
|
+
* @param config - Middleware configuration
|
|
1435
|
+
* @param middleware - Middleware function
|
|
1436
|
+
*/
|
|
1437
|
+
register(config, middleware) {
|
|
1438
|
+
const entry = {
|
|
1439
|
+
name: config.name,
|
|
1440
|
+
type: config.type,
|
|
1441
|
+
middleware,
|
|
1442
|
+
order: config.order ?? 100,
|
|
1443
|
+
enabled: config.enabled ?? true,
|
|
1444
|
+
paths: config.paths
|
|
1445
|
+
};
|
|
1446
|
+
this.middlewares.set(config.name, entry);
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Unregister middleware by name
|
|
1450
|
+
* @param name - Middleware name
|
|
1451
|
+
*/
|
|
1452
|
+
unregister(name) {
|
|
1453
|
+
this.middlewares.delete(name);
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Enable middleware by name
|
|
1457
|
+
* @param name - Middleware name
|
|
1458
|
+
*/
|
|
1459
|
+
enable(name) {
|
|
1460
|
+
const entry = this.middlewares.get(name);
|
|
1461
|
+
if (entry) {
|
|
1462
|
+
entry.enabled = true;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Disable middleware by name
|
|
1467
|
+
* @param name - Middleware name
|
|
1468
|
+
*/
|
|
1469
|
+
disable(name) {
|
|
1470
|
+
const entry = this.middlewares.get(name);
|
|
1471
|
+
if (entry) {
|
|
1472
|
+
entry.enabled = false;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Get middleware entry by name
|
|
1477
|
+
* @param name - Middleware name
|
|
1478
|
+
*/
|
|
1479
|
+
get(name) {
|
|
1480
|
+
return this.middlewares.get(name);
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Get all middleware entries
|
|
1484
|
+
*/
|
|
1485
|
+
getAll() {
|
|
1486
|
+
return Array.from(this.middlewares.values());
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Get middleware by type
|
|
1490
|
+
* @param type - Middleware type
|
|
1491
|
+
*/
|
|
1492
|
+
getByType(type) {
|
|
1493
|
+
return this.getAll().filter((entry) => entry.type === type);
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Get middleware chain sorted by order
|
|
1497
|
+
* Returns only enabled middleware
|
|
1498
|
+
*/
|
|
1499
|
+
getMiddlewareChain() {
|
|
1500
|
+
return this.getAll().filter((entry) => entry.enabled).sort((a, b) => a.order - b.order).map((entry) => entry.middleware);
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Get middleware chain with path filtering
|
|
1504
|
+
* @param path - Request path to match against
|
|
1505
|
+
*/
|
|
1506
|
+
getMiddlewareChainForPath(path) {
|
|
1507
|
+
return this.getAll().filter((entry) => {
|
|
1508
|
+
if (!entry.enabled) return false;
|
|
1509
|
+
if (entry.paths) {
|
|
1510
|
+
if (entry.paths.exclude) {
|
|
1511
|
+
const excluded = entry.paths.exclude.some(
|
|
1512
|
+
(pattern) => this.matchPath(path, pattern)
|
|
1513
|
+
);
|
|
1514
|
+
if (excluded) return false;
|
|
1515
|
+
}
|
|
1516
|
+
if (entry.paths.include) {
|
|
1517
|
+
const included = entry.paths.include.some(
|
|
1518
|
+
(pattern) => this.matchPath(path, pattern)
|
|
1519
|
+
);
|
|
1520
|
+
if (!included) return false;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return true;
|
|
1524
|
+
}).sort((a, b) => a.order - b.order).map((entry) => entry.middleware);
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Match path against pattern (simple glob matching)
|
|
1528
|
+
* @param path - Request path
|
|
1529
|
+
* @param pattern - Pattern to match (supports * wildcard)
|
|
1530
|
+
*/
|
|
1531
|
+
matchPath(path, pattern) {
|
|
1532
|
+
const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1533
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
1534
|
+
return regex.test(path);
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Clear all middleware
|
|
1538
|
+
*/
|
|
1539
|
+
clear() {
|
|
1540
|
+
this.middlewares.clear();
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Get middleware count
|
|
1544
|
+
*/
|
|
1545
|
+
count() {
|
|
1546
|
+
return this.middlewares.size;
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Create a composite middleware from the chain
|
|
1550
|
+
* This can be used to apply all middleware at once
|
|
1551
|
+
*/
|
|
1552
|
+
createCompositeMiddleware() {
|
|
1553
|
+
const chain = this.getMiddlewareChain();
|
|
1554
|
+
return async (req, res, next) => {
|
|
1555
|
+
let index = 0;
|
|
1556
|
+
const executeNext = async () => {
|
|
1557
|
+
if (index >= chain.length) {
|
|
1558
|
+
await next();
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
const middleware = chain[index++];
|
|
1562
|
+
await middleware(req, res, executeNext);
|
|
1563
|
+
};
|
|
1564
|
+
await executeNext();
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
|
|
1569
|
+
// src/index.ts
|
|
1570
|
+
__reExport(index_exports, require("@objectstack/core"), module.exports);
|
|
1571
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1572
|
+
0 && (module.exports = {
|
|
1573
|
+
AppPlugin,
|
|
1574
|
+
DriverPlugin,
|
|
1575
|
+
HttpDispatcher,
|
|
1576
|
+
HttpServer,
|
|
1577
|
+
MiddlewareManager,
|
|
1578
|
+
ObjectKernel,
|
|
1579
|
+
RestServer,
|
|
1580
|
+
RouteGroupBuilder,
|
|
1581
|
+
RouteManager,
|
|
1582
|
+
Runtime,
|
|
1583
|
+
createApiRegistryPlugin,
|
|
1584
|
+
...require("@objectstack/core")
|
|
1585
|
+
});
|
|
1586
|
+
//# sourceMappingURL=index.cjs.map
|