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