@objectstack/core 0.8.2 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API_REGISTRY.md +392 -0
- package/CHANGELOG.md +8 -0
- package/README.md +36 -0
- package/dist/api-registry-plugin.d.ts +54 -0
- package/dist/api-registry-plugin.d.ts.map +1 -0
- package/dist/api-registry-plugin.js +53 -0
- package/dist/api-registry-plugin.test.d.ts +2 -0
- package/dist/api-registry-plugin.test.d.ts.map +1 -0
- package/dist/api-registry-plugin.test.js +332 -0
- package/dist/api-registry.d.ts +259 -0
- package/dist/api-registry.d.ts.map +1 -0
- package/dist/api-registry.js +599 -0
- package/dist/api-registry.test.d.ts +2 -0
- package/dist/api-registry.test.d.ts.map +1 -0
- package/dist/api-registry.test.js +957 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +35 -11
- package/dist/plugin-loader.d.ts +3 -2
- package/dist/plugin-loader.d.ts.map +1 -1
- package/dist/plugin-loader.js +13 -11
- package/dist/qa/adapter.d.ts +14 -0
- package/dist/qa/adapter.d.ts.map +1 -0
- package/dist/qa/adapter.js +1 -0
- package/dist/qa/http-adapter.d.ts +16 -0
- package/dist/qa/http-adapter.d.ts.map +1 -0
- package/dist/qa/http-adapter.js +107 -0
- package/dist/qa/index.d.ts +4 -0
- package/dist/qa/index.d.ts.map +1 -0
- package/dist/qa/index.js +3 -0
- package/dist/qa/runner.d.ts +27 -0
- package/dist/qa/runner.d.ts.map +1 -0
- package/dist/qa/runner.js +157 -0
- package/dist/security/index.d.ts +14 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +13 -0
- package/dist/security/plugin-config-validator.d.ts +79 -0
- package/dist/security/plugin-config-validator.d.ts.map +1 -0
- package/dist/security/plugin-config-validator.js +166 -0
- package/dist/security/plugin-config-validator.test.d.ts +2 -0
- package/dist/security/plugin-config-validator.test.d.ts.map +1 -0
- package/dist/security/plugin-config-validator.test.js +223 -0
- package/dist/security/plugin-permission-enforcer.d.ts +154 -0
- package/dist/security/plugin-permission-enforcer.d.ts.map +1 -0
- package/dist/security/plugin-permission-enforcer.js +323 -0
- package/dist/security/plugin-permission-enforcer.test.d.ts +2 -0
- package/dist/security/plugin-permission-enforcer.test.d.ts.map +1 -0
- package/dist/security/plugin-permission-enforcer.test.js +205 -0
- package/dist/security/plugin-signature-verifier.d.ts +96 -0
- package/dist/security/plugin-signature-verifier.d.ts.map +1 -0
- package/dist/security/plugin-signature-verifier.js +250 -0
- package/examples/api-registry-example.ts +557 -0
- package/package.json +2 -2
- package/src/api-registry-plugin.test.ts +391 -0
- package/src/api-registry-plugin.ts +86 -0
- package/src/api-registry.test.ts +1089 -0
- package/src/api-registry.ts +736 -0
- package/src/index.ts +6 -0
- package/src/logger.ts +36 -11
- package/src/plugin-loader.ts +17 -13
- package/src/qa/adapter.ts +14 -0
- package/src/qa/http-adapter.ts +114 -0
- package/src/qa/index.ts +3 -0
- package/src/qa/runner.ts +179 -0
- package/src/security/index.ts +29 -0
- package/src/security/plugin-config-validator.test.ts +276 -0
- package/src/security/plugin-config-validator.ts +191 -0
- package/src/security/plugin-permission-enforcer.test.ts +251 -0
- package/src/security/plugin-permission-enforcer.ts +408 -0
- package/src/security/plugin-signature-verifier.ts +359 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Registry Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates how to use the API Registry in the ObjectStack kernel
|
|
5
|
+
* to register and discover API endpoints across plugins.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ObjectKernel, createApiRegistryPlugin, ApiRegistry } from '@objectstack/core';
|
|
9
|
+
import type { Plugin } from '@objectstack/core';
|
|
10
|
+
import type { ApiRegistryEntry } from '@objectstack/spec/api';
|
|
11
|
+
|
|
12
|
+
// Example 1: Basic API Registration
|
|
13
|
+
async function example1_BasicApiRegistration() {
|
|
14
|
+
console.log('\n=== Example 1: Basic API Registration ===\n');
|
|
15
|
+
|
|
16
|
+
const kernel = new ObjectKernel();
|
|
17
|
+
|
|
18
|
+
// Register API Registry plugin with default settings
|
|
19
|
+
kernel.use(createApiRegistryPlugin());
|
|
20
|
+
|
|
21
|
+
// Create a plugin that registers a simple REST API
|
|
22
|
+
const customerPlugin: Plugin = {
|
|
23
|
+
name: 'customer-plugin',
|
|
24
|
+
version: '1.0.0',
|
|
25
|
+
init: async (ctx) => {
|
|
26
|
+
const registry = ctx.getService<ApiRegistry>('api-registry');
|
|
27
|
+
|
|
28
|
+
const customerApi: ApiRegistryEntry = {
|
|
29
|
+
id: 'customer_api',
|
|
30
|
+
name: 'Customer Management API',
|
|
31
|
+
type: 'rest',
|
|
32
|
+
version: 'v1',
|
|
33
|
+
basePath: '/api/v1/customers',
|
|
34
|
+
description: 'CRUD operations for customer records',
|
|
35
|
+
endpoints: [
|
|
36
|
+
{
|
|
37
|
+
id: 'list_customers',
|
|
38
|
+
method: 'GET',
|
|
39
|
+
path: '/api/v1/customers',
|
|
40
|
+
summary: 'List all customers',
|
|
41
|
+
parameters: [
|
|
42
|
+
{
|
|
43
|
+
name: 'limit',
|
|
44
|
+
in: 'query',
|
|
45
|
+
schema: { type: 'number' },
|
|
46
|
+
description: 'Maximum number of results',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'offset',
|
|
50
|
+
in: 'query',
|
|
51
|
+
schema: { type: 'number' },
|
|
52
|
+
description: 'Offset for pagination',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
responses: [
|
|
56
|
+
{
|
|
57
|
+
statusCode: 200,
|
|
58
|
+
description: 'Customers retrieved successfully',
|
|
59
|
+
schema: {
|
|
60
|
+
type: 'array',
|
|
61
|
+
items: {
|
|
62
|
+
type: 'object',
|
|
63
|
+
properties: {
|
|
64
|
+
id: { type: 'string' },
|
|
65
|
+
name: { type: 'string' },
|
|
66
|
+
email: { type: 'string' },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'get_customer',
|
|
75
|
+
method: 'GET',
|
|
76
|
+
path: '/api/v1/customers/:id',
|
|
77
|
+
summary: 'Get customer by ID',
|
|
78
|
+
requiredPermissions: ['customer.read'], // RBAC integration
|
|
79
|
+
parameters: [
|
|
80
|
+
{
|
|
81
|
+
name: 'id',
|
|
82
|
+
in: 'path',
|
|
83
|
+
required: true,
|
|
84
|
+
schema: { type: 'string', format: 'uuid' },
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
responses: [
|
|
88
|
+
{
|
|
89
|
+
statusCode: 200,
|
|
90
|
+
description: 'Customer found',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
statusCode: 404,
|
|
94
|
+
description: 'Customer not found',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'create_customer',
|
|
100
|
+
method: 'POST',
|
|
101
|
+
path: '/api/v1/customers',
|
|
102
|
+
summary: 'Create new customer',
|
|
103
|
+
requiredPermissions: ['customer.create'],
|
|
104
|
+
requestBody: {
|
|
105
|
+
required: true,
|
|
106
|
+
contentType: 'application/json',
|
|
107
|
+
schema: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
name: { type: 'string' },
|
|
111
|
+
email: { type: 'string', format: 'email' },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
responses: [
|
|
116
|
+
{
|
|
117
|
+
statusCode: 201,
|
|
118
|
+
description: 'Customer created',
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
metadata: {
|
|
124
|
+
status: 'active',
|
|
125
|
+
tags: ['customer', 'crm', 'data'],
|
|
126
|
+
owner: 'sales_team',
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
registry.registerApi(customerApi);
|
|
131
|
+
ctx.logger.info('Customer API registered', {
|
|
132
|
+
endpointCount: customerApi.endpoints.length,
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
kernel.use(customerPlugin);
|
|
138
|
+
await kernel.bootstrap();
|
|
139
|
+
|
|
140
|
+
// Access the registry
|
|
141
|
+
const registry = kernel.getService<ApiRegistry>('api-registry');
|
|
142
|
+
const snapshot = registry.getRegistry();
|
|
143
|
+
|
|
144
|
+
console.log(`Total APIs: ${snapshot.totalApis}`);
|
|
145
|
+
console.log(`Total Endpoints: ${snapshot.totalEndpoints}`);
|
|
146
|
+
console.log('\nRegistered APIs:');
|
|
147
|
+
snapshot.apis.forEach((api) => {
|
|
148
|
+
console.log(` - ${api.name} (${api.type}) - ${api.endpoints.length} endpoints`);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await kernel.shutdown();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Example 2: Multi-Plugin API Discovery
|
|
155
|
+
async function example2_MultiPluginDiscovery() {
|
|
156
|
+
console.log('\n=== Example 2: Multi-Plugin API Discovery ===\n');
|
|
157
|
+
|
|
158
|
+
const kernel = new ObjectKernel();
|
|
159
|
+
kernel.use(createApiRegistryPlugin());
|
|
160
|
+
|
|
161
|
+
// Data Plugin - REST APIs
|
|
162
|
+
const dataPlugin: Plugin = {
|
|
163
|
+
name: 'data-plugin',
|
|
164
|
+
init: async (ctx) => {
|
|
165
|
+
const registry = ctx.getService<ApiRegistry>('api-registry');
|
|
166
|
+
|
|
167
|
+
registry.registerApi({
|
|
168
|
+
id: 'customer_api',
|
|
169
|
+
name: 'Customer API',
|
|
170
|
+
type: 'rest',
|
|
171
|
+
version: 'v1',
|
|
172
|
+
basePath: '/api/v1/customers',
|
|
173
|
+
endpoints: [
|
|
174
|
+
{
|
|
175
|
+
id: 'get_customers',
|
|
176
|
+
method: 'GET',
|
|
177
|
+
path: '/api/v1/customers',
|
|
178
|
+
responses: [],
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
metadata: {
|
|
182
|
+
status: 'active',
|
|
183
|
+
tags: ['data', 'crm'],
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
registry.registerApi({
|
|
188
|
+
id: 'product_api',
|
|
189
|
+
name: 'Product API',
|
|
190
|
+
type: 'rest',
|
|
191
|
+
version: 'v1',
|
|
192
|
+
basePath: '/api/v1/products',
|
|
193
|
+
endpoints: [
|
|
194
|
+
{
|
|
195
|
+
id: 'get_products',
|
|
196
|
+
method: 'GET',
|
|
197
|
+
path: '/api/v1/products',
|
|
198
|
+
responses: [],
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
metadata: {
|
|
202
|
+
status: 'active',
|
|
203
|
+
tags: ['data', 'inventory'],
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// GraphQL Plugin
|
|
210
|
+
const graphqlPlugin: Plugin = {
|
|
211
|
+
name: 'graphql-plugin',
|
|
212
|
+
init: async (ctx) => {
|
|
213
|
+
const registry = ctx.getService<ApiRegistry>('api-registry');
|
|
214
|
+
|
|
215
|
+
registry.registerApi({
|
|
216
|
+
id: 'graphql_api',
|
|
217
|
+
name: 'GraphQL API',
|
|
218
|
+
type: 'graphql',
|
|
219
|
+
version: 'v1',
|
|
220
|
+
basePath: '/graphql',
|
|
221
|
+
endpoints: [
|
|
222
|
+
{
|
|
223
|
+
id: 'query',
|
|
224
|
+
path: '/graphql',
|
|
225
|
+
summary: 'GraphQL Query Endpoint',
|
|
226
|
+
responses: [],
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
metadata: {
|
|
230
|
+
status: 'active',
|
|
231
|
+
tags: ['query', 'flexible'],
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Analytics Plugin - Beta API
|
|
238
|
+
const analyticsPlugin: Plugin = {
|
|
239
|
+
name: 'analytics-plugin',
|
|
240
|
+
init: async (ctx) => {
|
|
241
|
+
const registry = ctx.getService<ApiRegistry>('api-registry');
|
|
242
|
+
|
|
243
|
+
registry.registerApi({
|
|
244
|
+
id: 'analytics_api',
|
|
245
|
+
name: 'Analytics API',
|
|
246
|
+
type: 'rest',
|
|
247
|
+
version: 'v1',
|
|
248
|
+
basePath: '/api/v1/analytics',
|
|
249
|
+
endpoints: [
|
|
250
|
+
{
|
|
251
|
+
id: 'get_reports',
|
|
252
|
+
method: 'GET',
|
|
253
|
+
path: '/api/v1/analytics/reports',
|
|
254
|
+
responses: [],
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
metadata: {
|
|
258
|
+
status: 'beta',
|
|
259
|
+
tags: ['analytics', 'reporting'],
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
kernel.use(dataPlugin);
|
|
266
|
+
kernel.use(graphqlPlugin);
|
|
267
|
+
kernel.use(analyticsPlugin);
|
|
268
|
+
await kernel.bootstrap();
|
|
269
|
+
|
|
270
|
+
const registry = kernel.getService<ApiRegistry>('api-registry');
|
|
271
|
+
|
|
272
|
+
// Discovery 1: Find all REST APIs
|
|
273
|
+
console.log('All REST APIs:');
|
|
274
|
+
const restApis = registry.findApis({ type: 'rest' });
|
|
275
|
+
restApis.apis.forEach((api) => console.log(` - ${api.name}`));
|
|
276
|
+
|
|
277
|
+
// Discovery 2: Find active APIs
|
|
278
|
+
console.log('\nActive APIs:');
|
|
279
|
+
const activeApis = registry.findApis({ status: 'active' });
|
|
280
|
+
console.log(` Total: ${activeApis.total}`);
|
|
281
|
+
|
|
282
|
+
// Discovery 3: Find data-related APIs
|
|
283
|
+
console.log('\nData-related APIs:');
|
|
284
|
+
const dataApis = registry.findApis({ tags: ['data'] });
|
|
285
|
+
dataApis.apis.forEach((api) => console.log(` - ${api.name}`));
|
|
286
|
+
|
|
287
|
+
// Discovery 4: Search by name
|
|
288
|
+
console.log('\nSearch for "analytics":');
|
|
289
|
+
const analyticsApis = registry.findApis({ search: 'analytics' });
|
|
290
|
+
analyticsApis.apis.forEach((api) => console.log(` - ${api.name} (${api.metadata?.status})`));
|
|
291
|
+
|
|
292
|
+
await kernel.shutdown();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Example 3: Route Conflict Resolution
|
|
296
|
+
async function example3_ConflictResolution() {
|
|
297
|
+
console.log('\n=== Example 3: Route Conflict Resolution ===\n');
|
|
298
|
+
|
|
299
|
+
const kernel = new ObjectKernel();
|
|
300
|
+
|
|
301
|
+
// Use priority-based conflict resolution
|
|
302
|
+
kernel.use(
|
|
303
|
+
createApiRegistryPlugin({
|
|
304
|
+
conflictResolution: 'priority',
|
|
305
|
+
})
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
// Core Plugin - High priority
|
|
309
|
+
const corePlugin: Plugin = {
|
|
310
|
+
name: 'core-plugin',
|
|
311
|
+
init: async (ctx) => {
|
|
312
|
+
const registry = ctx.getService<ApiRegistry>('api-registry');
|
|
313
|
+
|
|
314
|
+
registry.registerApi({
|
|
315
|
+
id: 'core_data_api',
|
|
316
|
+
name: 'Core Data API',
|
|
317
|
+
type: 'rest',
|
|
318
|
+
version: 'v1',
|
|
319
|
+
basePath: '/api',
|
|
320
|
+
endpoints: [
|
|
321
|
+
{
|
|
322
|
+
id: 'core_data',
|
|
323
|
+
method: 'GET',
|
|
324
|
+
path: '/api/data/:object',
|
|
325
|
+
priority: 900, // High priority
|
|
326
|
+
summary: 'Core data endpoint (generic)',
|
|
327
|
+
responses: [],
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
ctx.logger.info('Core API registered with priority 900');
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Custom Plugin - Medium priority
|
|
337
|
+
const customPlugin: Plugin = {
|
|
338
|
+
name: 'custom-plugin',
|
|
339
|
+
init: async (ctx) => {
|
|
340
|
+
const registry = ctx.getService<ApiRegistry>('api-registry');
|
|
341
|
+
|
|
342
|
+
registry.registerApi({
|
|
343
|
+
id: 'custom_data_api',
|
|
344
|
+
name: 'Custom Data API',
|
|
345
|
+
type: 'rest',
|
|
346
|
+
version: 'v1',
|
|
347
|
+
basePath: '/api',
|
|
348
|
+
endpoints: [
|
|
349
|
+
{
|
|
350
|
+
id: 'custom_data',
|
|
351
|
+
method: 'GET',
|
|
352
|
+
path: '/api/data/:object',
|
|
353
|
+
priority: 300, // Lower priority
|
|
354
|
+
summary: 'Custom data endpoint (specialized)',
|
|
355
|
+
responses: [],
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
ctx.logger.info('Custom API registered with priority 300');
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
kernel.use(corePlugin);
|
|
365
|
+
kernel.use(customPlugin);
|
|
366
|
+
await kernel.bootstrap();
|
|
367
|
+
|
|
368
|
+
const registry = kernel.getService<ApiRegistry>('api-registry');
|
|
369
|
+
|
|
370
|
+
// Check which endpoint won
|
|
371
|
+
const winner = registry.findEndpointByRoute('GET', '/api/data/:object');
|
|
372
|
+
console.log('\nConflict Resolution Result:');
|
|
373
|
+
console.log(` Route: GET /api/data/:object`);
|
|
374
|
+
console.log(` Winner: ${winner?.api.name}`);
|
|
375
|
+
console.log(` Endpoint: ${winner?.endpoint.summary}`);
|
|
376
|
+
console.log(` Priority: ${winner?.endpoint.priority}`);
|
|
377
|
+
|
|
378
|
+
await kernel.shutdown();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Example 4: Plugin-specific APIs with Custom Protocol
|
|
382
|
+
async function example4_CustomProtocol() {
|
|
383
|
+
console.log('\n=== Example 4: Custom Protocol Support ===\n');
|
|
384
|
+
|
|
385
|
+
const kernel = new ObjectKernel();
|
|
386
|
+
kernel.use(createApiRegistryPlugin());
|
|
387
|
+
|
|
388
|
+
const websocketPlugin: Plugin = {
|
|
389
|
+
name: 'websocket-plugin',
|
|
390
|
+
init: async (ctx) => {
|
|
391
|
+
const registry = ctx.getService<ApiRegistry>('api-registry');
|
|
392
|
+
|
|
393
|
+
registry.registerApi({
|
|
394
|
+
id: 'realtime_api',
|
|
395
|
+
name: 'Real-time WebSocket API',
|
|
396
|
+
type: 'websocket',
|
|
397
|
+
version: 'v1',
|
|
398
|
+
basePath: '/ws',
|
|
399
|
+
endpoints: [
|
|
400
|
+
{
|
|
401
|
+
id: 'customer_updates',
|
|
402
|
+
path: '/ws/customers',
|
|
403
|
+
summary: 'Customer update notifications',
|
|
404
|
+
protocolConfig: {
|
|
405
|
+
subProtocol: 'websocket',
|
|
406
|
+
eventName: 'customer.updated',
|
|
407
|
+
direction: 'server-to-client',
|
|
408
|
+
},
|
|
409
|
+
responses: [],
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
id: 'order_updates',
|
|
413
|
+
path: '/ws/orders',
|
|
414
|
+
summary: 'Order update notifications',
|
|
415
|
+
protocolConfig: {
|
|
416
|
+
subProtocol: 'websocket',
|
|
417
|
+
eventName: 'order.updated',
|
|
418
|
+
direction: 'bidirectional',
|
|
419
|
+
},
|
|
420
|
+
responses: [],
|
|
421
|
+
},
|
|
422
|
+
],
|
|
423
|
+
metadata: {
|
|
424
|
+
status: 'active',
|
|
425
|
+
tags: ['realtime', 'websocket'],
|
|
426
|
+
pluginSource: 'websocket-plugin',
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
kernel.use(websocketPlugin);
|
|
433
|
+
await kernel.bootstrap();
|
|
434
|
+
|
|
435
|
+
const registry = kernel.getService<ApiRegistry>('api-registry');
|
|
436
|
+
const wsApis = registry.findApis({ type: 'websocket' });
|
|
437
|
+
|
|
438
|
+
console.log('WebSocket APIs:');
|
|
439
|
+
wsApis.apis.forEach((api) => {
|
|
440
|
+
console.log(`\n${api.name}:`);
|
|
441
|
+
api.endpoints.forEach((endpoint) => {
|
|
442
|
+
console.log(` - ${endpoint.summary}`);
|
|
443
|
+
console.log(` Event: ${endpoint.protocolConfig?.eventName}`);
|
|
444
|
+
console.log(` Direction: ${endpoint.protocolConfig?.direction}`);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
await kernel.shutdown();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Example 5: Dynamic Schema Linking with ObjectQL
|
|
452
|
+
async function example5_DynamicSchemas() {
|
|
453
|
+
console.log('\n=== Example 5: Dynamic Schema Linking ===\n');
|
|
454
|
+
|
|
455
|
+
const kernel = new ObjectKernel();
|
|
456
|
+
kernel.use(createApiRegistryPlugin());
|
|
457
|
+
|
|
458
|
+
const dynamicPlugin: Plugin = {
|
|
459
|
+
name: 'dynamic-plugin',
|
|
460
|
+
init: async (ctx) => {
|
|
461
|
+
const registry = ctx.getService<ApiRegistry>('api-registry');
|
|
462
|
+
|
|
463
|
+
registry.registerApi({
|
|
464
|
+
id: 'dynamic_customer_api',
|
|
465
|
+
name: 'Dynamic Customer API',
|
|
466
|
+
type: 'rest',
|
|
467
|
+
version: 'v1',
|
|
468
|
+
basePath: '/api/v1/customers',
|
|
469
|
+
endpoints: [
|
|
470
|
+
{
|
|
471
|
+
id: 'get_customer_dynamic',
|
|
472
|
+
method: 'GET',
|
|
473
|
+
path: '/api/v1/customers/:id',
|
|
474
|
+
summary: 'Get customer (with dynamic schema)',
|
|
475
|
+
responses: [
|
|
476
|
+
{
|
|
477
|
+
statusCode: 200,
|
|
478
|
+
description: 'Customer retrieved',
|
|
479
|
+
// Dynamic schema linked to ObjectQL
|
|
480
|
+
//
|
|
481
|
+
// IMPORTANT: The API Registry stores this ObjectQL reference as-is.
|
|
482
|
+
// The actual schema resolution (expanding the reference into a full JSON Schema)
|
|
483
|
+
// is performed by downstream tools:
|
|
484
|
+
// - API Gateway: For runtime request/response validation
|
|
485
|
+
// - OpenAPI/Swagger Generator: For API documentation generation
|
|
486
|
+
// - GraphQL Schema Builder: For GraphQL type generation
|
|
487
|
+
//
|
|
488
|
+
// The Registry's responsibility is to STORE the reference metadata,
|
|
489
|
+
// not to resolve or transform it.
|
|
490
|
+
schema: {
|
|
491
|
+
$ref: {
|
|
492
|
+
objectId: 'customer', // References ObjectQL object
|
|
493
|
+
excludeFields: ['password_hash', 'internal_notes'], // Exclude sensitive fields
|
|
494
|
+
includeRelated: ['account', 'primary_contact'], // Include related objects
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
},
|
|
500
|
+
],
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
ctx.logger.info('Dynamic Customer API registered with ObjectQL schema references');
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
kernel.use(dynamicPlugin);
|
|
508
|
+
await kernel.bootstrap();
|
|
509
|
+
|
|
510
|
+
const registry = kernel.getService<ApiRegistry>('api-registry');
|
|
511
|
+
const endpoint = registry.getEndpoint('dynamic_customer_api', 'get_customer_dynamic');
|
|
512
|
+
|
|
513
|
+
console.log('Dynamic Endpoint:');
|
|
514
|
+
console.log(` Path: ${endpoint?.path}`);
|
|
515
|
+
console.log(` Summary: ${endpoint?.summary}`);
|
|
516
|
+
|
|
517
|
+
if (endpoint?.responses?.[0]?.schema && '$ref' in endpoint.responses[0].schema) {
|
|
518
|
+
const ref = endpoint.responses[0].schema.$ref;
|
|
519
|
+
console.log('\n Schema Reference (stored as metadata):');
|
|
520
|
+
console.log(` Object: ${ref.objectId}`);
|
|
521
|
+
console.log(` Excluded Fields: ${ref.excludeFields?.join(', ')}`);
|
|
522
|
+
console.log(` Included Related: ${ref.includeRelated?.join(', ')}`);
|
|
523
|
+
console.log('\n ℹ️ Note: Schema resolution is handled by gateway/documentation tools,');
|
|
524
|
+
console.log(' not by the API Registry itself.');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
await kernel.shutdown();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Run all examples
|
|
531
|
+
async function main() {
|
|
532
|
+
try {
|
|
533
|
+
await example1_BasicApiRegistration();
|
|
534
|
+
await example2_MultiPluginDiscovery();
|
|
535
|
+
await example3_ConflictResolution();
|
|
536
|
+
await example4_CustomProtocol();
|
|
537
|
+
await example5_DynamicSchemas();
|
|
538
|
+
|
|
539
|
+
console.log('\n=== All examples completed successfully! ===\n');
|
|
540
|
+
} catch (error) {
|
|
541
|
+
console.error('Example failed:', error);
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Only run if this file is executed directly
|
|
547
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
548
|
+
main();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export {
|
|
552
|
+
example1_BasicApiRegistration,
|
|
553
|
+
example2_MultiPluginDiscovery,
|
|
554
|
+
example3_ConflictResolution,
|
|
555
|
+
example4_CustomProtocol,
|
|
556
|
+
example5_DynamicSchemas,
|
|
557
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Microkernel Core for ObjectStack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"pino": "^8.17.0",
|
|
15
15
|
"pino-pretty": "^10.3.0",
|
|
16
16
|
"zod": "^4.3.6",
|
|
17
|
-
"@objectstack/spec": "0.
|
|
17
|
+
"@objectstack/spec": "0.9.1"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"pino": "^8.0.0"
|