@objectstack/runtime 1.0.4 → 1.0.6

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/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