@objectstack/plugin-hono-server 1.0.1 → 1.0.4

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.
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HonoServerPlugin = void 0;
4
4
  const adapter_1 = require("./adapter");
5
+ const hono_1 = require("@objectstack/hono");
5
6
  /**
6
7
  * Hono Server Plugin
7
8
  *
@@ -35,1039 +36,48 @@ class HonoServerPlugin {
35
36
  staticRoot: this.options.staticRoot
36
37
  });
37
38
  // Register HTTP server service as IHttpServer
39
+ // Register as 'http.server' to match core requirements
40
+ ctx.registerService('http.server', this.server);
41
+ // Alias 'http-server' for backward compatibility
38
42
  ctx.registerService('http-server', this.server);
39
- ctx.logger.info('HTTP server service registered', { serviceName: 'http-server' });
43
+ ctx.logger.debug('HTTP server service registered', { serviceName: 'http.server' });
40
44
  };
41
- /**
42
- * Helper to create cache request object from HTTP headers
43
- */
44
- createCacheRequest(headers) {
45
- return {
46
- ifNoneMatch: headers['if-none-match'],
47
- ifModifiedSince: headers['if-modified-since'],
48
- };
49
- }
50
45
  /**
51
46
  * Start phase - Bind routes and start listening
52
47
  */
53
48
  start = async (ctx) => {
54
49
  ctx.logger.debug('Starting Hono server plugin');
55
- // Get protocol implementation instance
56
- let protocol = null;
50
+ // Use Standard ObjectStack Runtime Hono App
57
51
  try {
58
- protocol = ctx.getService('protocol');
59
- ctx.logger.debug('Protocol service found');
60
- }
61
- catch (e) {
62
- ctx.logger.warn('Protocol service not found, skipping protocol routes');
63
- }
64
- // Try to get API Registry
65
- let apiRegistry = null;
66
- try {
67
- apiRegistry = ctx.getService('api-registry');
68
- ctx.logger.debug('API Registry found, will use for endpoint registration');
52
+ const kernel = ctx.getKernel();
53
+ const config = this.options.restConfig || {};
54
+ // Calculate prefix similar to before
55
+ const apiVersion = config.api?.version || 'v1';
56
+ const basePath = config.api?.basePath || '/api';
57
+ const apiPath = config.api?.apiPath || `${basePath}/${apiVersion}`;
58
+ const app = (0, hono_1.createHonoApp)({
59
+ kernel,
60
+ prefix: apiPath // Use the calculated path
61
+ });
62
+ ctx.logger.debug('Mounting ObjectStack Runtime App', { prefix: apiPath });
63
+ // Use the mount method we added to HonoHttpServer
64
+ this.server.mount('/', app);
69
65
  }
70
66
  catch (e) {
71
- ctx.logger.debug('API Registry not found, using legacy route registration');
72
- }
73
- // Register standard ObjectStack endpoints
74
- if (protocol) {
75
- if (apiRegistry && this.options.registerStandardEndpoints) {
76
- this.registerStandardEndpointsToRegistry(apiRegistry, ctx);
77
- }
78
- // Bind routes from registry or fallback to legacy
79
- if (apiRegistry && this.options.useApiRegistry) {
80
- this.bindRoutesFromRegistry(apiRegistry, protocol, ctx);
81
- }
82
- else {
83
- this.bindLegacyRoutes(protocol, ctx);
84
- }
67
+ ctx.logger.error('Failed to create standard Hono app', e);
85
68
  }
86
69
  // Start server on kernel:ready hook
87
70
  ctx.hook('kernel:ready', async () => {
88
71
  const port = this.options.port || 3000;
89
- ctx.logger.info('Starting HTTP server', { port });
72
+ ctx.logger.debug('Starting HTTP server', { port });
90
73
  await this.server.listen(port);
74
+ const actualPort = this.server.getPort();
91
75
  ctx.logger.info('HTTP server started successfully', {
92
- port,
93
- url: `http://localhost:${port}`
76
+ port: actualPort,
77
+ url: `http://localhost:${actualPort}`
94
78
  });
95
79
  });
96
80
  };
97
- /**
98
- * Register standard ObjectStack API endpoints to the API Registry
99
- */
100
- registerStandardEndpointsToRegistry(registry, ctx) {
101
- const config = this.options.restConfig || {};
102
- const apiVersion = config.api?.version || 'v1';
103
- const basePath = config.api?.basePath || '/api';
104
- const apiPath = config.api?.apiPath || `${basePath}/${apiVersion}`;
105
- const endpoints = [];
106
- // Discovery endpoint
107
- if (config.api?.enableDiscovery !== false) {
108
- endpoints.push({
109
- id: 'get_discovery',
110
- method: 'GET',
111
- path: apiPath,
112
- summary: 'API Discovery',
113
- description: 'Get API version and capabilities',
114
- responses: [{
115
- statusCode: 200,
116
- description: 'API discovery information'
117
- }],
118
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
119
- });
120
- }
121
- // Metadata endpoints
122
- if (config.api?.enableMetadata !== false) {
123
- const metaPrefix = config.metadata?.prefix || '/meta';
124
- endpoints.push({
125
- id: 'get_meta_types',
126
- method: 'GET',
127
- path: `${apiPath}${metaPrefix}`,
128
- summary: 'Get Metadata Types',
129
- description: 'List all available metadata types',
130
- responses: [{
131
- statusCode: 200,
132
- description: 'List of metadata types'
133
- }],
134
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
135
- }, {
136
- id: 'get_meta_items',
137
- method: 'GET',
138
- path: `${apiPath}${metaPrefix}/:type`,
139
- summary: 'Get Metadata Items',
140
- description: 'Get all items of a metadata type',
141
- parameters: [{
142
- name: 'type',
143
- in: 'path',
144
- required: true,
145
- schema: { type: 'string' }
146
- }],
147
- responses: [{
148
- statusCode: 200,
149
- description: 'List of metadata items'
150
- }],
151
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
152
- }, {
153
- id: 'get_meta_item_cached',
154
- method: 'GET',
155
- path: `${apiPath}${metaPrefix}/:type/:name`,
156
- summary: 'Get Metadata Item with Cache',
157
- description: 'Get a specific metadata item with ETag support',
158
- parameters: [
159
- {
160
- name: 'type',
161
- in: 'path',
162
- required: true,
163
- schema: { type: 'string' }
164
- },
165
- {
166
- name: 'name',
167
- in: 'path',
168
- required: true,
169
- schema: { type: 'string' }
170
- }
171
- ],
172
- responses: [
173
- {
174
- statusCode: 200,
175
- description: 'Metadata item',
176
- headers: {
177
- 'ETag': { description: 'Entity tag for caching', schema: { type: 'string' } },
178
- 'Last-Modified': { description: 'Last modification time', schema: { type: 'string' } },
179
- 'Cache-Control': { description: 'Cache directives', schema: { type: 'string' } }
180
- }
181
- },
182
- {
183
- statusCode: 304,
184
- description: 'Not Modified'
185
- }
186
- ],
187
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
188
- });
189
- }
190
- // CRUD endpoints
191
- if (config.api?.enableCrud !== false) {
192
- const dataPrefix = config.crud?.dataPrefix || '/data';
193
- endpoints.push(
194
- // List/Query
195
- {
196
- id: 'find_data',
197
- method: 'GET',
198
- path: `${apiPath}${dataPrefix}/:object`,
199
- summary: 'Find Records',
200
- description: 'Query records from an object',
201
- parameters: [{
202
- name: 'object',
203
- in: 'path',
204
- required: true,
205
- schema: { type: 'string' }
206
- }],
207
- responses: [{
208
- statusCode: 200,
209
- description: 'List of records'
210
- }],
211
- priority: HonoServerPlugin.CORE_ENDPOINT_PRIORITY
212
- },
213
- // Get by ID
214
- {
215
- id: 'get_data',
216
- method: 'GET',
217
- path: `${apiPath}${dataPrefix}/:object/:id`,
218
- summary: 'Get Record by ID',
219
- description: 'Retrieve a single record by its ID',
220
- parameters: [
221
- {
222
- name: 'object',
223
- in: 'path',
224
- required: true,
225
- schema: { type: 'string' }
226
- },
227
- {
228
- name: 'id',
229
- in: 'path',
230
- required: true,
231
- schema: { type: 'string' }
232
- }
233
- ],
234
- responses: [
235
- {
236
- statusCode: 200,
237
- description: 'Record found'
238
- },
239
- {
240
- statusCode: 404,
241
- description: 'Record not found'
242
- }
243
- ],
244
- priority: HonoServerPlugin.CORE_ENDPOINT_PRIORITY
245
- },
246
- // Create
247
- {
248
- id: 'create_data',
249
- method: 'POST',
250
- path: `${apiPath}${dataPrefix}/:object`,
251
- summary: 'Create Record',
252
- description: 'Create a new record',
253
- parameters: [{
254
- name: 'object',
255
- in: 'path',
256
- required: true,
257
- schema: { type: 'string' }
258
- }],
259
- requestBody: {
260
- required: true,
261
- description: 'Record data'
262
- },
263
- responses: [{
264
- statusCode: 201,
265
- description: 'Record created'
266
- }],
267
- priority: HonoServerPlugin.CORE_ENDPOINT_PRIORITY
268
- },
269
- // Update
270
- {
271
- id: 'update_data',
272
- method: 'PATCH',
273
- path: `${apiPath}${dataPrefix}/:object/:id`,
274
- summary: 'Update Record',
275
- description: 'Update an existing record',
276
- parameters: [
277
- {
278
- name: 'object',
279
- in: 'path',
280
- required: true,
281
- schema: { type: 'string' }
282
- },
283
- {
284
- name: 'id',
285
- in: 'path',
286
- required: true,
287
- schema: { type: 'string' }
288
- }
289
- ],
290
- requestBody: {
291
- required: true,
292
- description: 'Fields to update'
293
- },
294
- responses: [{
295
- statusCode: 200,
296
- description: 'Record updated'
297
- }],
298
- priority: HonoServerPlugin.CORE_ENDPOINT_PRIORITY
299
- },
300
- // Delete
301
- {
302
- id: 'delete_data',
303
- method: 'DELETE',
304
- path: `${apiPath}${dataPrefix}/:object/:id`,
305
- summary: 'Delete Record',
306
- description: 'Delete a record by ID',
307
- parameters: [
308
- {
309
- name: 'object',
310
- in: 'path',
311
- required: true,
312
- schema: { type: 'string' }
313
- },
314
- {
315
- name: 'id',
316
- in: 'path',
317
- required: true,
318
- schema: { type: 'string' }
319
- }
320
- ],
321
- responses: [{
322
- statusCode: 200,
323
- description: 'Record deleted'
324
- }],
325
- priority: HonoServerPlugin.CORE_ENDPOINT_PRIORITY
326
- });
327
- }
328
- // Batch endpoints
329
- if (config.api?.enableBatch !== false) {
330
- const dataPrefix = config.crud?.dataPrefix || '/data';
331
- endpoints.push({
332
- id: 'batch_data',
333
- method: 'POST',
334
- path: `${apiPath}${dataPrefix}/:object/batch`,
335
- summary: 'Batch Operations',
336
- description: 'Perform batch create/update/delete operations',
337
- parameters: [{
338
- name: 'object',
339
- in: 'path',
340
- required: true,
341
- schema: { type: 'string' }
342
- }],
343
- requestBody: {
344
- required: true,
345
- description: 'Batch operation request'
346
- },
347
- responses: [{
348
- statusCode: 200,
349
- description: 'Batch operation completed'
350
- }],
351
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
352
- }, {
353
- id: 'create_many_data',
354
- method: 'POST',
355
- path: `${apiPath}${dataPrefix}/:object/createMany`,
356
- summary: 'Create Multiple Records',
357
- description: 'Create multiple records in one request',
358
- parameters: [{
359
- name: 'object',
360
- in: 'path',
361
- required: true,
362
- schema: { type: 'string' }
363
- }],
364
- requestBody: {
365
- required: true,
366
- description: 'Array of records to create'
367
- },
368
- responses: [{
369
- statusCode: 201,
370
- description: 'Records created'
371
- }],
372
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
373
- }, {
374
- id: 'update_many_data',
375
- method: 'POST',
376
- path: `${apiPath}${dataPrefix}/:object/updateMany`,
377
- summary: 'Update Multiple Records',
378
- description: 'Update multiple records in one request',
379
- parameters: [{
380
- name: 'object',
381
- in: 'path',
382
- required: true,
383
- schema: { type: 'string' }
384
- }],
385
- requestBody: {
386
- required: true,
387
- description: 'Array of records to update'
388
- },
389
- responses: [{
390
- statusCode: 200,
391
- description: 'Records updated'
392
- }],
393
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
394
- }, {
395
- id: 'delete_many_data',
396
- method: 'POST',
397
- path: `${apiPath}${dataPrefix}/:object/deleteMany`,
398
- summary: 'Delete Multiple Records',
399
- description: 'Delete multiple records in one request',
400
- parameters: [{
401
- name: 'object',
402
- in: 'path',
403
- required: true,
404
- schema: { type: 'string' }
405
- }],
406
- requestBody: {
407
- required: true,
408
- description: 'Array of record IDs to delete'
409
- },
410
- responses: [{
411
- statusCode: 200,
412
- description: 'Records deleted'
413
- }],
414
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
415
- });
416
- }
417
- // UI endpoints
418
- endpoints.push({
419
- id: 'get_ui_view',
420
- method: 'GET',
421
- path: `${apiPath}/ui/view/:object`,
422
- summary: 'Get UI View',
423
- description: 'Get UI view definition for an object',
424
- parameters: [
425
- {
426
- name: 'object',
427
- in: 'path',
428
- required: true,
429
- schema: { type: 'string' }
430
- },
431
- {
432
- name: 'type',
433
- in: 'query',
434
- schema: {
435
- type: 'string',
436
- enum: ['list', 'form'],
437
- default: 'list'
438
- }
439
- }
440
- ],
441
- responses: [
442
- {
443
- statusCode: 200,
444
- description: 'UI view definition'
445
- },
446
- {
447
- statusCode: 404,
448
- description: 'View not found'
449
- }
450
- ],
451
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
452
- });
453
- // Analytics Endpoints
454
- endpoints.push({
455
- id: 'analytics_query',
456
- method: 'POST',
457
- path: `${apiPath}/analytics/query`,
458
- summary: 'Analytics Query',
459
- description: 'Execute analytics query',
460
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
461
- }, {
462
- id: 'get_analytics_meta',
463
- method: 'GET',
464
- path: `${apiPath}/analytics/meta`,
465
- summary: 'Analytics Metadata',
466
- description: 'Get analytics cubes definitions',
467
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
468
- });
469
- // Automation Endpoints
470
- endpoints.push({
471
- id: 'automation_trigger',
472
- method: 'POST',
473
- path: `${apiPath}/automation/trigger/:trigger`,
474
- summary: 'Trigger Automation',
475
- description: 'Trigger a named automation',
476
- parameters: [{
477
- name: 'trigger',
478
- in: 'path',
479
- required: true,
480
- schema: { type: 'string' }
481
- }],
482
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
483
- });
484
- // Hub Endpoints
485
- endpoints.push({
486
- id: 'hub_list_spaces',
487
- method: 'GET',
488
- path: `${apiPath}/hub/spaces`,
489
- summary: 'List Spaces',
490
- description: 'List all Hub spaces',
491
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
492
- }, {
493
- id: 'hub_create_space',
494
- method: 'POST',
495
- path: `${apiPath}/hub/spaces`,
496
- summary: 'Create Space',
497
- description: 'Create a new Hub space',
498
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
499
- }, {
500
- id: 'hub_install_plugin',
501
- method: 'POST',
502
- path: `${apiPath}/hub/plugins/install`,
503
- summary: 'Install Plugin',
504
- description: 'Install a plugin from marketplace',
505
- priority: HonoServerPlugin.DISCOVERY_ENDPOINT_PRIORITY
506
- });
507
- // Register the API in the registry
508
- const apiEntry = {
509
- id: 'objectstack_core_api',
510
- name: 'ObjectStack Core API',
511
- type: 'rest',
512
- version: apiVersion,
513
- basePath: apiPath,
514
- description: 'Standard ObjectStack CRUD and metadata API',
515
- endpoints,
516
- metadata: {
517
- owner: 'objectstack',
518
- status: 'active',
519
- tags: ['core', 'crud', 'metadata']
520
- }
521
- };
522
- try {
523
- registry.registerApi(apiEntry);
524
- ctx.logger.info('Standard ObjectStack endpoints registered to API Registry', {
525
- endpointCount: endpoints.length
526
- });
527
- }
528
- catch (error) {
529
- ctx.logger.error('Failed to register standard endpoints', error);
530
- }
531
- }
532
- /**
533
- * Bind HTTP routes from API Registry
534
- */
535
- bindRoutesFromRegistry(registry, protocol, ctx) {
536
- const apiRegistry = registry.getRegistry();
537
- ctx.logger.debug('Binding routes from API Registry', {
538
- totalApis: apiRegistry.totalApis,
539
- totalEndpoints: apiRegistry.totalEndpoints
540
- });
541
- // Get all endpoints sorted by priority (highest first)
542
- const allEndpoints = [];
543
- for (const api of apiRegistry.apis) {
544
- for (const endpoint of api.endpoints) {
545
- allEndpoints.push({ api: api.id, endpoint });
546
- }
547
- }
548
- // Sort by priority (highest first)
549
- allEndpoints.sort((a, b) => (b.endpoint.priority || HonoServerPlugin.DEFAULT_ENDPOINT_PRIORITY) -
550
- (a.endpoint.priority || HonoServerPlugin.DEFAULT_ENDPOINT_PRIORITY));
551
- // Bind routes
552
- for (const { endpoint } of allEndpoints) {
553
- this.bindEndpoint(endpoint, protocol, ctx);
554
- }
555
- ctx.logger.info('Routes bound from API Registry', {
556
- totalRoutes: allEndpoints.length
557
- });
558
- }
559
- /**
560
- * Bind a single endpoint to the HTTP server
561
- */
562
- bindEndpoint(endpoint, protocol, ctx) {
563
- const method = endpoint.method || 'GET';
564
- const path = endpoint.path;
565
- const id = endpoint.id;
566
- // Map endpoint ID to protocol method
567
- const handler = this.createHandlerForEndpoint(id, protocol, ctx);
568
- if (!handler) {
569
- ctx.logger.warn('No handler found for endpoint', { id, method, path });
570
- return;
571
- }
572
- // Register route based on method
573
- switch (method.toUpperCase()) {
574
- case 'GET':
575
- this.server.get(path, handler);
576
- break;
577
- case 'POST':
578
- this.server.post(path, handler);
579
- break;
580
- case 'PATCH':
581
- this.server.patch(path, handler);
582
- break;
583
- case 'PUT':
584
- this.server.put(path, handler);
585
- break;
586
- case 'DELETE':
587
- this.server.delete(path, handler);
588
- break;
589
- default:
590
- ctx.logger.warn('Unsupported HTTP method', { method, path });
591
- }
592
- ctx.logger.debug('Route bound', { method, path, endpoint: id });
593
- }
594
- /**
595
- * Create a route handler for an endpoint
596
- */
597
- createHandlerForEndpoint(endpointId, protocol, ctx) {
598
- const p = protocol;
599
- // Map endpoint IDs to protocol methods
600
- const handlerMap = {
601
- 'get_discovery': async (req, res) => {
602
- ctx.logger.debug('API discovery request');
603
- res.json(await p.getDiscovery({}));
604
- },
605
- 'get_meta_types': async (req, res) => {
606
- ctx.logger.debug('Meta types request');
607
- res.json(await p.getMetaTypes({}));
608
- },
609
- 'get_meta_items': async (req, res) => {
610
- ctx.logger.debug('Meta items request', { type: req.params.type });
611
- res.json(await p.getMetaItems({ type: req.params.type }));
612
- },
613
- 'get_meta_item_cached': async (req, res) => {
614
- ctx.logger.debug('Meta item cached request', {
615
- type: req.params.type,
616
- name: req.params.name
617
- });
618
- try {
619
- const result = await p.getMetaItemCached({
620
- type: req.params.type,
621
- name: req.params.name,
622
- cacheRequest: this.createCacheRequest(req.headers)
623
- });
624
- if (result.notModified) {
625
- res.status(304).send('');
626
- }
627
- else {
628
- // Set cache headers
629
- if (result.etag) {
630
- const etagValue = result.etag.weak ? `W/"${result.etag.value}"` : `"${result.etag.value}"`;
631
- res.header('ETag', etagValue);
632
- }
633
- if (result.lastModified) {
634
- res.header('Last-Modified', new Date(result.lastModified).toUTCString());
635
- }
636
- if (result.cacheControl) {
637
- const directives = result.cacheControl.directives.join(', ');
638
- const maxAge = result.cacheControl.maxAge ? `, max-age=${result.cacheControl.maxAge}` : '';
639
- res.header('Cache-Control', directives + maxAge);
640
- }
641
- res.json(result.data);
642
- }
643
- }
644
- catch (e) {
645
- ctx.logger.warn('Meta item not found', { type: req.params.type, name: req.params.name });
646
- res.status(404).json({ error: e.message });
647
- }
648
- },
649
- 'find_data': async (req, res) => {
650
- ctx.logger.debug('Data find request', { object: req.params.object });
651
- try {
652
- const result = await p.findData({ object: req.params.object, query: req.query });
653
- ctx.logger.debug('Data find completed', { object: req.params.object, count: result?.records?.length ?? 0 });
654
- res.json(result);
655
- }
656
- catch (e) {
657
- ctx.logger.error('Data find failed', e, { object: req.params.object });
658
- res.status(400).json({ error: e.message });
659
- }
660
- },
661
- 'get_data': async (req, res) => {
662
- ctx.logger.debug('Data get request', { object: req.params.object, id: req.params.id });
663
- try {
664
- const result = await p.getData({ object: req.params.object, id: req.params.id });
665
- res.json(result);
666
- }
667
- catch (e) {
668
- ctx.logger.warn('Data get failed', { object: req.params.object, id: req.params.id });
669
- res.status(404).json({ error: e.message });
670
- }
671
- },
672
- 'create_data': async (req, res) => {
673
- ctx.logger.debug('Data create request', { object: req.params.object });
674
- try {
675
- const result = await p.createData({ object: req.params.object, data: req.body });
676
- ctx.logger.info('Data created', { object: req.params.object, id: result?.id });
677
- res.status(201).json(result);
678
- }
679
- catch (e) {
680
- ctx.logger.error('Data create failed', e, { object: req.params.object });
681
- res.status(400).json({ error: e.message });
682
- }
683
- },
684
- 'update_data': async (req, res) => {
685
- ctx.logger.debug('Data update request', { object: req.params.object, id: req.params.id });
686
- try {
687
- const result = await p.updateData({ object: req.params.object, id: req.params.id, data: req.body });
688
- ctx.logger.info('Data updated', { object: req.params.object, id: req.params.id });
689
- res.json(result);
690
- }
691
- catch (e) {
692
- ctx.logger.error('Data update failed', e, { object: req.params.object, id: req.params.id });
693
- res.status(400).json({ error: e.message });
694
- }
695
- },
696
- 'delete_data': async (req, res) => {
697
- ctx.logger.debug('Data delete request', { object: req.params.object, id: req.params.id });
698
- try {
699
- const result = await p.deleteData({ object: req.params.object, id: req.params.id });
700
- ctx.logger.info('Data deleted', { object: req.params.object, id: req.params.id });
701
- res.json(result);
702
- }
703
- catch (e) {
704
- ctx.logger.error('Data delete failed', e, { object: req.params.object, id: req.params.id });
705
- res.status(400).json({ error: e.message });
706
- }
707
- },
708
- 'batch_data': async (req, res) => {
709
- ctx.logger.info('Batch operation request', { object: req.params.object });
710
- try {
711
- const result = await p.batchData({ object: req.params.object, request: req.body });
712
- ctx.logger.info('Batch operation completed', {
713
- object: req.params.object,
714
- total: result.total,
715
- succeeded: result.succeeded,
716
- failed: result.failed
717
- });
718
- res.json(result);
719
- }
720
- catch (e) {
721
- ctx.logger.error('Batch operation failed', e, { object: req.params.object });
722
- res.status(400).json({ error: e.message });
723
- }
724
- },
725
- 'create_many_data': async (req, res) => {
726
- ctx.logger.debug('Create many request', { object: req.params.object });
727
- try {
728
- const result = await p.createManyData({ object: req.params.object, records: req.body || [] });
729
- ctx.logger.info('Create many completed', { object: req.params.object, count: result.records?.length ?? 0 });
730
- res.status(201).json(result);
731
- }
732
- catch (e) {
733
- ctx.logger.error('Create many failed', e, { object: req.params.object });
734
- res.status(400).json({ error: e.message });
735
- }
736
- },
737
- 'update_many_data': async (req, res) => {
738
- ctx.logger.debug('Update many request', { object: req.params.object });
739
- try {
740
- const result = await p.updateManyData({
741
- object: req.params.object,
742
- records: req.body?.records,
743
- options: req.body?.options
744
- });
745
- ctx.logger.info('Update many completed', {
746
- object: req.params.object,
747
- total: result.total,
748
- succeeded: result.succeeded,
749
- failed: result.failed
750
- });
751
- res.json(result);
752
- }
753
- catch (e) {
754
- ctx.logger.error('Update many failed', e, { object: req.params.object });
755
- res.status(400).json({ error: e.message });
756
- }
757
- },
758
- 'delete_many_data': async (req, res) => {
759
- ctx.logger.debug('Delete many request', { object: req.params.object });
760
- try {
761
- const result = await p.deleteManyData({
762
- object: req.params.object,
763
- ids: req.body?.ids,
764
- options: req.body?.options
765
- });
766
- ctx.logger.info('Delete many completed', {
767
- object: req.params.object,
768
- total: result.total,
769
- succeeded: result.succeeded,
770
- failed: result.failed
771
- });
772
- res.json(result);
773
- }
774
- catch (e) {
775
- ctx.logger.error('Delete many failed', e, { object: req.params.object });
776
- res.status(400).json({ error: e.message });
777
- }
778
- },
779
- 'get_ui_view': async (req, res) => {
780
- const viewType = req.query.type || 'list';
781
- ctx.logger.debug('UI view request', { object: req.params.object, viewType });
782
- try {
783
- const view = await p.getUiView({ object: req.params.object, type: viewType });
784
- res.json(view);
785
- }
786
- catch (e) {
787
- ctx.logger.warn('UI view not found', { object: req.params.object });
788
- res.status(404).json({ error: e.message });
789
- }
790
- },
791
- // Analytics
792
- 'analytics_query': async (req, res) => {
793
- ctx.logger.info('Analytics query request');
794
- try {
795
- const result = await p.analyticsQuery(req.body);
796
- res.json(result);
797
- }
798
- catch (e) {
799
- ctx.logger.error('Analytics query failed', e);
800
- res.status(400).json({ error: e.message });
801
- }
802
- },
803
- 'get_analytics_meta': async (req, res) => {
804
- ctx.logger.info('Analytics meta request');
805
- try {
806
- const result = await p.getAnalyticsMeta({});
807
- res.json(result);
808
- }
809
- catch (e) {
810
- ctx.logger.error('Analytics meta failed', e);
811
- res.status(500).json({ error: e.message });
812
- }
813
- },
814
- // Automation
815
- 'automation_trigger': async (req, res) => {
816
- const trigger = req.params.trigger;
817
- ctx.logger.info('Automation trigger request', { trigger });
818
- try {
819
- const result = await p.triggerAutomation({ trigger, payload: req.body });
820
- res.json(result);
821
- }
822
- catch (e) {
823
- ctx.logger.error('Automation trigger failed', e, { trigger });
824
- res.status(500).json({ error: e.message });
825
- }
826
- },
827
- // Hub
828
- 'hub_list_spaces': async (req, res) => {
829
- try {
830
- const result = await p.listSpaces({ ...req.query });
831
- res.json(result);
832
- }
833
- catch (e) {
834
- res.status(500).json({ error: e.message });
835
- }
836
- },
837
- 'hub_create_space': async (req, res) => {
838
- try {
839
- const result = await p.createSpace(req.body);
840
- res.status(201).json(result);
841
- }
842
- catch (e) {
843
- res.status(500).json({ error: e.message });
844
- }
845
- },
846
- 'hub_install_plugin': async (req, res) => {
847
- const spaceId = req.params.space_id;
848
- try {
849
- const result = await p.installPlugin({ spaceId, ...req.body });
850
- res.json(result);
851
- }
852
- catch (e) {
853
- res.status(500).json({ error: e.message });
854
- }
855
- }
856
- };
857
- return handlerMap[endpointId];
858
- }
859
- /**
860
- * Legacy route registration (fallback when API Registry is not available)
861
- */
862
- bindLegacyRoutes(protocol, ctx) {
863
- const p = protocol;
864
- ctx.logger.debug('Using legacy route registration');
865
- ctx.logger.debug('Registering API routes');
866
- this.server.get('/api/v1', async (req, res) => {
867
- ctx.logger.debug('API discovery request');
868
- res.json(await p.getDiscovery({}));
869
- });
870
- // Meta Protocol
871
- this.server.get('/api/v1/meta', async (req, res) => {
872
- ctx.logger.debug('Meta types request');
873
- res.json(await p.getMetaTypes({}));
874
- });
875
- this.server.get('/api/v1/meta/:type', async (req, res) => {
876
- ctx.logger.debug('Meta items request', { type: req.params.type });
877
- res.json(await p.getMetaItems({ type: req.params.type }));
878
- });
879
- // Data Protocol
880
- this.server.get('/api/v1/data/:object', async (req, res) => {
881
- ctx.logger.debug('Data find request', { object: req.params.object, query: req.query });
882
- try {
883
- const result = await p.findData({ object: req.params.object, query: req.query });
884
- ctx.logger.debug('Data find completed', { object: req.params.object, count: result?.records?.length ?? 0 });
885
- res.json(result);
886
- }
887
- catch (e) {
888
- ctx.logger.error('Data find failed', e, { object: req.params.object });
889
- res.status(400).json({ error: e.message });
890
- }
891
- });
892
- this.server.get('/api/v1/data/:object/:id', async (req, res) => {
893
- ctx.logger.debug('Data get request', { object: req.params.object, id: req.params.id });
894
- try {
895
- const result = await p.getData({ object: req.params.object, id: req.params.id });
896
- ctx.logger.debug('Data get completed', { object: req.params.object, id: req.params.id });
897
- res.json(result);
898
- }
899
- catch (e) {
900
- ctx.logger.warn('Data get failed - not found', { object: req.params.object, id: req.params.id });
901
- res.status(404).json({ error: e.message });
902
- }
903
- });
904
- this.server.post('/api/v1/data/:object', async (req, res) => {
905
- ctx.logger.debug('Data create request', { object: req.params.object });
906
- try {
907
- const result = await p.createData({ object: req.params.object, data: req.body });
908
- ctx.logger.info('Data created', { object: req.params.object, id: result?.id });
909
- res.status(201).json(result);
910
- }
911
- catch (e) {
912
- ctx.logger.error('Data create failed', e, { object: req.params.object });
913
- res.status(400).json({ error: e.message });
914
- }
915
- });
916
- this.server.patch('/api/v1/data/:object/:id', async (req, res) => {
917
- ctx.logger.debug('Data update request', { object: req.params.object, id: req.params.id });
918
- try {
919
- const result = await p.updateData({ object: req.params.object, id: req.params.id, data: req.body });
920
- ctx.logger.info('Data updated', { object: req.params.object, id: req.params.id });
921
- res.json(result);
922
- }
923
- catch (e) {
924
- ctx.logger.error('Data update failed', e, { object: req.params.object, id: req.params.id });
925
- res.status(400).json({ error: e.message });
926
- }
927
- });
928
- this.server.delete('/api/v1/data/:object/:id', async (req, res) => {
929
- ctx.logger.debug('Data delete request', { object: req.params.object, id: req.params.id });
930
- try {
931
- const result = await p.deleteData({ object: req.params.object, id: req.params.id });
932
- ctx.logger.info('Data deleted', { object: req.params.object, id: req.params.id, success: result?.success });
933
- res.json(result);
934
- }
935
- catch (e) {
936
- ctx.logger.error('Data delete failed', e, { object: req.params.object, id: req.params.id });
937
- res.status(400).json({ error: e.message });
938
- }
939
- });
940
- // UI Protocol
941
- this.server.get('/api/v1/ui/view/:object', async (req, res) => {
942
- const viewType = (req.query.type) || 'list';
943
- const qt = Array.isArray(viewType) ? viewType[0] : viewType;
944
- ctx.logger.debug('UI view request', { object: req.params.object, viewType: qt });
945
- try {
946
- res.json(await p.getUiView({ object: req.params.object, type: qt }));
947
- }
948
- catch (e) {
949
- ctx.logger.warn('UI view not found', { object: req.params.object, viewType: qt });
950
- res.status(404).json({ error: e.message });
951
- }
952
- });
953
- // Batch Operations
954
- this.server.post('/api/v1/data/:object/batch', async (req, res) => {
955
- ctx.logger.info('Batch operation request', {
956
- object: req.params.object,
957
- operation: req.body?.operation,
958
- hasBody: !!req.body,
959
- bodyType: typeof req.body,
960
- bodyKeys: req.body ? Object.keys(req.body) : []
961
- });
962
- try {
963
- const result = await p.batchData({ object: req.params.object, request: req.body });
964
- ctx.logger.info('Batch operation completed', {
965
- object: req.params.object,
966
- operation: req.body?.operation,
967
- total: result.total,
968
- succeeded: result.succeeded,
969
- failed: result.failed
970
- });
971
- res.json(result);
972
- }
973
- catch (e) {
974
- ctx.logger.error('Batch operation failed', e, { object: req.params.object });
975
- res.status(400).json({ error: e.message });
976
- }
977
- });
978
- this.server.post('/api/v1/data/:object/createMany', async (req, res) => {
979
- ctx.logger.debug('Create many request', { object: req.params.object, count: req.body?.length });
980
- try {
981
- const result = await p.createManyData({ object: req.params.object, records: req.body || [] });
982
- ctx.logger.info('Create many completed', { object: req.params.object, count: result.records?.length ?? 0 });
983
- res.status(201).json(result);
984
- }
985
- catch (e) {
986
- ctx.logger.error('Create many failed', e, { object: req.params.object });
987
- res.status(400).json({ error: e.message });
988
- }
989
- });
990
- this.server.post('/api/v1/data/:object/updateMany', async (req, res) => {
991
- ctx.logger.debug('Update many request', { object: req.params.object, count: req.body?.records?.length });
992
- try {
993
- const result = await p.updateManyData({ object: req.params.object, records: req.body?.records, options: req.body?.options });
994
- ctx.logger.info('Update many completed', {
995
- object: req.params.object,
996
- total: result.total,
997
- succeeded: result.succeeded,
998
- failed: result.failed
999
- });
1000
- res.json(result);
1001
- }
1002
- catch (e) {
1003
- ctx.logger.error('Update many failed', e, { object: req.params.object });
1004
- res.status(400).json({ error: e.message });
1005
- }
1006
- });
1007
- this.server.post('/api/v1/data/:object/deleteMany', async (req, res) => {
1008
- ctx.logger.debug('Delete many request', { object: req.params.object, count: req.body?.ids?.length });
1009
- try {
1010
- const result = await p.deleteManyData({ object: req.params.object, ids: req.body?.ids, options: req.body?.options });
1011
- ctx.logger.info('Delete many completed', {
1012
- object: req.params.object,
1013
- total: result.total,
1014
- succeeded: result.succeeded,
1015
- failed: result.failed
1016
- });
1017
- res.json(result);
1018
- }
1019
- catch (e) {
1020
- ctx.logger.error('Delete many failed', e, { object: req.params.object });
1021
- res.status(400).json({ error: e.message });
1022
- }
1023
- });
1024
- // Enhanced Metadata Route with ETag Support
1025
- this.server.get('/api/v1/meta/:type/:name', async (req, res) => {
1026
- ctx.logger.debug('Meta item request with cache support', {
1027
- type: req.params.type,
1028
- name: req.params.name,
1029
- ifNoneMatch: req.headers['if-none-match']
1030
- });
1031
- try {
1032
- const cacheRequest = this.createCacheRequest(req.headers);
1033
- const result = await p.getMetaItemCached({
1034
- type: req.params.type,
1035
- name: req.params.name,
1036
- cacheRequest
1037
- });
1038
- if (result.notModified) {
1039
- ctx.logger.debug('Meta item not modified (304)', { type: req.params.type, name: req.params.name });
1040
- res.status(304).send('');
1041
- }
1042
- else {
1043
- // Set cache headers
1044
- if (result.etag) {
1045
- const etagValue = result.etag.weak ? `W/"${result.etag.value}"` : `"${result.etag.value}"`;
1046
- res.header('ETag', etagValue);
1047
- }
1048
- if (result.lastModified) {
1049
- res.header('Last-Modified', new Date(result.lastModified).toUTCString());
1050
- }
1051
- if (result.cacheControl) {
1052
- const directives = result.cacheControl.directives.join(', ');
1053
- const maxAge = result.cacheControl.maxAge ? `, max-age=${result.cacheControl.maxAge}` : '';
1054
- res.header('Cache-Control', directives + maxAge);
1055
- }
1056
- ctx.logger.debug('Meta item returned with cache headers', {
1057
- type: req.params.type,
1058
- name: req.params.name,
1059
- etag: result.etag?.value
1060
- });
1061
- res.json(result.data);
1062
- }
1063
- }
1064
- catch (e) {
1065
- ctx.logger.warn('Meta item not found', { type: req.params.type, name: req.params.name });
1066
- res.status(404).json({ error: e.message });
1067
- }
1068
- });
1069
- ctx.logger.info('All legacy API routes registered');
1070
- }
1071
81
  /**
1072
82
  * Destroy phase - Stop server
1073
83
  */