@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,332 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { ObjectKernel } from './kernel';
|
|
3
|
+
import { createApiRegistryPlugin } from './api-registry-plugin';
|
|
4
|
+
import { ApiRegistry } from './api-registry';
|
|
5
|
+
describe('API Registry Plugin', () => {
|
|
6
|
+
let kernel;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
kernel = new ObjectKernel();
|
|
9
|
+
});
|
|
10
|
+
describe('Plugin Registration', () => {
|
|
11
|
+
it('should register API Registry as a service', async () => {
|
|
12
|
+
kernel.use(createApiRegistryPlugin());
|
|
13
|
+
await kernel.bootstrap();
|
|
14
|
+
const registry = kernel.getService('api-registry');
|
|
15
|
+
expect(registry).toBeDefined();
|
|
16
|
+
expect(registry).toBeInstanceOf(ApiRegistry);
|
|
17
|
+
await kernel.shutdown();
|
|
18
|
+
});
|
|
19
|
+
it('should register with custom conflict resolution', async () => {
|
|
20
|
+
kernel.use(createApiRegistryPlugin({
|
|
21
|
+
conflictResolution: 'priority',
|
|
22
|
+
version: '2.0.0',
|
|
23
|
+
}));
|
|
24
|
+
await kernel.bootstrap();
|
|
25
|
+
const registry = kernel.getService('api-registry');
|
|
26
|
+
const snapshot = registry.getRegistry();
|
|
27
|
+
expect(snapshot.conflictResolution).toBe('priority');
|
|
28
|
+
expect(snapshot.version).toBe('2.0.0');
|
|
29
|
+
await kernel.shutdown();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('Integration with Plugins', () => {
|
|
33
|
+
it('should allow plugins to register APIs', async () => {
|
|
34
|
+
kernel.use(createApiRegistryPlugin());
|
|
35
|
+
const testPlugin = {
|
|
36
|
+
name: 'test-plugin',
|
|
37
|
+
init: async (ctx) => {
|
|
38
|
+
const registry = ctx.getService('api-registry');
|
|
39
|
+
const api = {
|
|
40
|
+
id: 'test_api',
|
|
41
|
+
name: 'Test API',
|
|
42
|
+
type: 'rest',
|
|
43
|
+
version: 'v1',
|
|
44
|
+
basePath: '/api/test',
|
|
45
|
+
endpoints: [
|
|
46
|
+
{
|
|
47
|
+
id: 'get_test',
|
|
48
|
+
method: 'GET',
|
|
49
|
+
path: '/api/test/hello',
|
|
50
|
+
summary: 'Test endpoint',
|
|
51
|
+
responses: [
|
|
52
|
+
{
|
|
53
|
+
statusCode: 200,
|
|
54
|
+
description: 'Success',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
registry.registerApi(api);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
kernel.use(testPlugin);
|
|
64
|
+
await kernel.bootstrap();
|
|
65
|
+
const registry = kernel.getService('api-registry');
|
|
66
|
+
const api = registry.getApi('test_api');
|
|
67
|
+
expect(api).toBeDefined();
|
|
68
|
+
expect(api?.name).toBe('Test API');
|
|
69
|
+
expect(api?.endpoints.length).toBe(1);
|
|
70
|
+
await kernel.shutdown();
|
|
71
|
+
});
|
|
72
|
+
it('should allow multiple plugins to register APIs', async () => {
|
|
73
|
+
kernel.use(createApiRegistryPlugin());
|
|
74
|
+
const plugin1 = {
|
|
75
|
+
name: 'plugin-1',
|
|
76
|
+
init: async (ctx) => {
|
|
77
|
+
const registry = ctx.getService('api-registry');
|
|
78
|
+
registry.registerApi({
|
|
79
|
+
id: 'api1',
|
|
80
|
+
name: 'API 1',
|
|
81
|
+
type: 'rest',
|
|
82
|
+
version: 'v1',
|
|
83
|
+
basePath: '/api/plugin1',
|
|
84
|
+
endpoints: [
|
|
85
|
+
{
|
|
86
|
+
id: 'endpoint1',
|
|
87
|
+
method: 'GET',
|
|
88
|
+
path: '/api/plugin1/data',
|
|
89
|
+
responses: [],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
const plugin2 = {
|
|
96
|
+
name: 'plugin-2',
|
|
97
|
+
init: async (ctx) => {
|
|
98
|
+
const registry = ctx.getService('api-registry');
|
|
99
|
+
registry.registerApi({
|
|
100
|
+
id: 'api2',
|
|
101
|
+
name: 'API 2',
|
|
102
|
+
type: 'graphql',
|
|
103
|
+
version: 'v1',
|
|
104
|
+
basePath: '/graphql',
|
|
105
|
+
endpoints: [
|
|
106
|
+
{
|
|
107
|
+
id: 'query',
|
|
108
|
+
path: '/graphql',
|
|
109
|
+
responses: [],
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
kernel.use(plugin1);
|
|
116
|
+
kernel.use(plugin2);
|
|
117
|
+
await kernel.bootstrap();
|
|
118
|
+
const registry = kernel.getService('api-registry');
|
|
119
|
+
const stats = registry.getStats();
|
|
120
|
+
expect(stats.totalApis).toBe(2);
|
|
121
|
+
expect(stats.apisByType.rest).toBe(1);
|
|
122
|
+
expect(stats.apisByType.graphql).toBe(1);
|
|
123
|
+
await kernel.shutdown();
|
|
124
|
+
});
|
|
125
|
+
it('should support API discovery across plugins', async () => {
|
|
126
|
+
kernel.use(createApiRegistryPlugin());
|
|
127
|
+
const dataPlugin = {
|
|
128
|
+
name: 'data-plugin',
|
|
129
|
+
init: async (ctx) => {
|
|
130
|
+
const registry = ctx.getService('api-registry');
|
|
131
|
+
registry.registerApi({
|
|
132
|
+
id: 'customer_api',
|
|
133
|
+
name: 'Customer API',
|
|
134
|
+
type: 'rest',
|
|
135
|
+
version: 'v1',
|
|
136
|
+
basePath: '/api/v1/customers',
|
|
137
|
+
endpoints: [],
|
|
138
|
+
metadata: {
|
|
139
|
+
status: 'active',
|
|
140
|
+
tags: ['crm', 'data'],
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
registry.registerApi({
|
|
144
|
+
id: 'product_api',
|
|
145
|
+
name: 'Product API',
|
|
146
|
+
type: 'rest',
|
|
147
|
+
version: 'v1',
|
|
148
|
+
basePath: '/api/v1/products',
|
|
149
|
+
endpoints: [],
|
|
150
|
+
metadata: {
|
|
151
|
+
status: 'active',
|
|
152
|
+
tags: ['inventory', 'data'],
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
const analyticsPlugin = {
|
|
158
|
+
name: 'analytics-plugin',
|
|
159
|
+
init: async (ctx) => {
|
|
160
|
+
const registry = ctx.getService('api-registry');
|
|
161
|
+
registry.registerApi({
|
|
162
|
+
id: 'analytics_api',
|
|
163
|
+
name: 'Analytics API',
|
|
164
|
+
type: 'rest',
|
|
165
|
+
version: 'v1',
|
|
166
|
+
basePath: '/api/v1/analytics',
|
|
167
|
+
endpoints: [],
|
|
168
|
+
metadata: {
|
|
169
|
+
status: 'beta',
|
|
170
|
+
tags: ['analytics', 'reporting'],
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
kernel.use(dataPlugin);
|
|
176
|
+
kernel.use(analyticsPlugin);
|
|
177
|
+
await kernel.bootstrap();
|
|
178
|
+
const registry = kernel.getService('api-registry');
|
|
179
|
+
// Find all data APIs
|
|
180
|
+
const dataApis = registry.findApis({ tags: ['data'] });
|
|
181
|
+
expect(dataApis.total).toBe(2);
|
|
182
|
+
// Find active APIs
|
|
183
|
+
const activeApis = registry.findApis({ status: 'active' });
|
|
184
|
+
expect(activeApis.total).toBe(2);
|
|
185
|
+
// Find CRM APIs
|
|
186
|
+
const crmApis = registry.findApis({ tags: ['crm'] });
|
|
187
|
+
expect(crmApis.total).toBe(1);
|
|
188
|
+
expect(crmApis.apis[0].id).toBe('customer_api');
|
|
189
|
+
await kernel.shutdown();
|
|
190
|
+
});
|
|
191
|
+
it('should handle route conflicts based on strategy', async () => {
|
|
192
|
+
kernel.use(createApiRegistryPlugin({
|
|
193
|
+
conflictResolution: 'priority',
|
|
194
|
+
}));
|
|
195
|
+
const corePlugin = {
|
|
196
|
+
name: 'core-plugin',
|
|
197
|
+
init: async (ctx) => {
|
|
198
|
+
const registry = ctx.getService('api-registry');
|
|
199
|
+
registry.registerApi({
|
|
200
|
+
id: 'core_api',
|
|
201
|
+
name: 'Core API',
|
|
202
|
+
type: 'rest',
|
|
203
|
+
version: 'v1',
|
|
204
|
+
basePath: '/api',
|
|
205
|
+
endpoints: [
|
|
206
|
+
{
|
|
207
|
+
id: 'core_endpoint',
|
|
208
|
+
method: 'GET',
|
|
209
|
+
path: '/api/data/:object',
|
|
210
|
+
priority: 900, // High priority
|
|
211
|
+
summary: 'Core data endpoint',
|
|
212
|
+
responses: [],
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
const pluginOverride = {
|
|
219
|
+
name: 'plugin-override',
|
|
220
|
+
init: async (ctx) => {
|
|
221
|
+
const registry = ctx.getService('api-registry');
|
|
222
|
+
registry.registerApi({
|
|
223
|
+
id: 'plugin_api',
|
|
224
|
+
name: 'Plugin API',
|
|
225
|
+
type: 'rest',
|
|
226
|
+
version: 'v1',
|
|
227
|
+
basePath: '/api',
|
|
228
|
+
endpoints: [
|
|
229
|
+
{
|
|
230
|
+
id: 'plugin_endpoint',
|
|
231
|
+
method: 'GET',
|
|
232
|
+
path: '/api/data/:object',
|
|
233
|
+
priority: 300, // Lower priority
|
|
234
|
+
summary: 'Plugin data endpoint',
|
|
235
|
+
responses: [],
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
kernel.use(corePlugin);
|
|
242
|
+
kernel.use(pluginOverride);
|
|
243
|
+
await kernel.bootstrap();
|
|
244
|
+
const registry = kernel.getService('api-registry');
|
|
245
|
+
const result = registry.findEndpointByRoute('GET', '/api/data/:object');
|
|
246
|
+
// Core API should win due to higher priority
|
|
247
|
+
expect(result?.api.id).toBe('core_api');
|
|
248
|
+
expect(result?.endpoint.id).toBe('core_endpoint');
|
|
249
|
+
await kernel.shutdown();
|
|
250
|
+
});
|
|
251
|
+
it('should support cleanup on plugin unload', async () => {
|
|
252
|
+
kernel.use(createApiRegistryPlugin());
|
|
253
|
+
const dynamicPlugin = {
|
|
254
|
+
name: 'dynamic-plugin',
|
|
255
|
+
init: async (ctx) => {
|
|
256
|
+
const registry = ctx.getService('api-registry');
|
|
257
|
+
registry.registerApi({
|
|
258
|
+
id: 'dynamic_api',
|
|
259
|
+
name: 'Dynamic API',
|
|
260
|
+
type: 'rest',
|
|
261
|
+
version: 'v1',
|
|
262
|
+
basePath: '/api/dynamic',
|
|
263
|
+
endpoints: [
|
|
264
|
+
{
|
|
265
|
+
id: 'test',
|
|
266
|
+
method: 'GET',
|
|
267
|
+
path: '/api/dynamic/test',
|
|
268
|
+
responses: [],
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
destroy: async () => {
|
|
274
|
+
// In a real scenario, this would use ctx to access registry
|
|
275
|
+
// For now, we'll test the registry's unregister capability
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
kernel.use(dynamicPlugin);
|
|
279
|
+
await kernel.bootstrap();
|
|
280
|
+
const registry = kernel.getService('api-registry');
|
|
281
|
+
expect(registry.getApi('dynamic_api')).toBeDefined();
|
|
282
|
+
// Unregister the API
|
|
283
|
+
registry.unregisterApi('dynamic_api');
|
|
284
|
+
expect(registry.getApi('dynamic_api')).toBeUndefined();
|
|
285
|
+
await kernel.shutdown();
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
describe('API Registry Lifecycle', () => {
|
|
289
|
+
it('should be available during plugin start phase', async () => {
|
|
290
|
+
kernel.use(createApiRegistryPlugin());
|
|
291
|
+
let registryAvailable = false;
|
|
292
|
+
const testPlugin = {
|
|
293
|
+
name: 'test-plugin',
|
|
294
|
+
init: async () => {
|
|
295
|
+
// Init phase
|
|
296
|
+
},
|
|
297
|
+
start: async (ctx) => {
|
|
298
|
+
// Start phase - registry should be available
|
|
299
|
+
const registry = ctx.getService('api-registry');
|
|
300
|
+
registryAvailable = registry !== undefined;
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
kernel.use(testPlugin);
|
|
304
|
+
await kernel.bootstrap();
|
|
305
|
+
expect(registryAvailable).toBe(true);
|
|
306
|
+
await kernel.shutdown();
|
|
307
|
+
});
|
|
308
|
+
it('should provide consistent registry across all plugins', async () => {
|
|
309
|
+
kernel.use(createApiRegistryPlugin());
|
|
310
|
+
let registry1;
|
|
311
|
+
let registry2;
|
|
312
|
+
const plugin1 = {
|
|
313
|
+
name: 'plugin-1',
|
|
314
|
+
init: async (ctx) => {
|
|
315
|
+
registry1 = ctx.getService('api-registry');
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
const plugin2 = {
|
|
319
|
+
name: 'plugin-2',
|
|
320
|
+
init: async (ctx) => {
|
|
321
|
+
registry2 = ctx.getService('api-registry');
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
kernel.use(plugin1);
|
|
325
|
+
kernel.use(plugin2);
|
|
326
|
+
await kernel.bootstrap();
|
|
327
|
+
// Same registry instance should be shared
|
|
328
|
+
expect(registry1).toBe(registry2);
|
|
329
|
+
await kernel.shutdown();
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
});
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import type { ApiRegistry as ApiRegistryType, ApiRegistryEntry, ApiRegistryEntryInput, ApiEndpointRegistration, ConflictResolutionStrategy, ApiDiscoveryQuery, ApiDiscoveryResponse } from '@objectstack/spec/api';
|
|
2
|
+
import type { Logger } from '@objectstack/spec/contracts';
|
|
3
|
+
/**
|
|
4
|
+
* API Registry Service
|
|
5
|
+
*
|
|
6
|
+
* Central registry for managing API endpoints across different protocols.
|
|
7
|
+
* Provides endpoint registration, discovery, and conflict resolution.
|
|
8
|
+
*
|
|
9
|
+
* **Features:**
|
|
10
|
+
* - Multi-protocol support (REST, GraphQL, OData, WebSocket, etc.)
|
|
11
|
+
* - Route conflict detection with configurable resolution strategies
|
|
12
|
+
* - RBAC permission integration
|
|
13
|
+
* - Dynamic schema linking with ObjectQL references
|
|
14
|
+
* - Plugin API registration
|
|
15
|
+
*
|
|
16
|
+
* **Architecture Alignment:**
|
|
17
|
+
* - Kubernetes: Service Discovery & API Server
|
|
18
|
+
* - AWS API Gateway: Unified API Management
|
|
19
|
+
* - Kong Gateway: Plugin-based API Management
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const registry = new ApiRegistry(logger, 'priority');
|
|
24
|
+
*
|
|
25
|
+
* // Register an API
|
|
26
|
+
* registry.registerApi({
|
|
27
|
+
* id: 'customer_api',
|
|
28
|
+
* name: 'Customer API',
|
|
29
|
+
* type: 'rest',
|
|
30
|
+
* version: 'v1',
|
|
31
|
+
* basePath: '/api/v1/customers',
|
|
32
|
+
* endpoints: [...]
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* // Discover APIs
|
|
36
|
+
* const apis = registry.findApis({ type: 'rest', status: 'active' });
|
|
37
|
+
*
|
|
38
|
+
* // Get registry snapshot
|
|
39
|
+
* const snapshot = registry.getRegistry();
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare class ApiRegistry {
|
|
43
|
+
private apis;
|
|
44
|
+
private endpoints;
|
|
45
|
+
private routes;
|
|
46
|
+
private apisByType;
|
|
47
|
+
private apisByTag;
|
|
48
|
+
private apisByStatus;
|
|
49
|
+
private conflictResolution;
|
|
50
|
+
private logger;
|
|
51
|
+
private version;
|
|
52
|
+
private updatedAt;
|
|
53
|
+
constructor(logger: Logger, conflictResolution?: ConflictResolutionStrategy, version?: string);
|
|
54
|
+
/**
|
|
55
|
+
* Register an API with its endpoints
|
|
56
|
+
*
|
|
57
|
+
* @param api - API registry entry
|
|
58
|
+
* @throws Error if API already registered or route conflicts detected
|
|
59
|
+
*/
|
|
60
|
+
registerApi(api: ApiRegistryEntryInput): void;
|
|
61
|
+
/**
|
|
62
|
+
* Unregister an API and all its endpoints
|
|
63
|
+
*
|
|
64
|
+
* @param apiId - API identifier
|
|
65
|
+
*/
|
|
66
|
+
unregisterApi(apiId: string): void;
|
|
67
|
+
/**
|
|
68
|
+
* Register a single endpoint
|
|
69
|
+
*
|
|
70
|
+
* @param apiId - API identifier
|
|
71
|
+
* @param endpoint - Endpoint registration
|
|
72
|
+
* @throws Error if route conflict detected
|
|
73
|
+
*/
|
|
74
|
+
private registerEndpoint;
|
|
75
|
+
/**
|
|
76
|
+
* Unregister a single endpoint
|
|
77
|
+
*
|
|
78
|
+
* @param apiId - API identifier
|
|
79
|
+
* @param endpointId - Endpoint identifier
|
|
80
|
+
*/
|
|
81
|
+
private unregisterEndpoint;
|
|
82
|
+
/**
|
|
83
|
+
* Register a route with conflict detection
|
|
84
|
+
*
|
|
85
|
+
* @param apiId - API identifier
|
|
86
|
+
* @param endpoint - Endpoint registration
|
|
87
|
+
* @throws Error if route conflict detected (based on strategy)
|
|
88
|
+
*/
|
|
89
|
+
private registerRoute;
|
|
90
|
+
/**
|
|
91
|
+
* Handle route conflict based on resolution strategy
|
|
92
|
+
*
|
|
93
|
+
* @param routeKey - Route key
|
|
94
|
+
* @param apiId - New API identifier
|
|
95
|
+
* @param endpoint - New endpoint
|
|
96
|
+
* @param existingRoute - Existing route registration
|
|
97
|
+
* @param newPriority - New endpoint priority
|
|
98
|
+
* @throws Error if strategy is 'error'
|
|
99
|
+
*/
|
|
100
|
+
private handleRouteConflict;
|
|
101
|
+
/**
|
|
102
|
+
* Generate a unique route key for conflict detection
|
|
103
|
+
*
|
|
104
|
+
* NOTE: This implementation uses exact string matching for route conflict detection.
|
|
105
|
+
* It works well for static paths but has limitations with parameterized routes.
|
|
106
|
+
* For example, `/api/users/:id` and `/api/users/:userId` will NOT be detected as conflicts
|
|
107
|
+
* even though they are semantically identical parameterized patterns. Similarly,
|
|
108
|
+
* `/api/:resource/list` and `/api/:entity/list` would also not be detected as conflicting.
|
|
109
|
+
*
|
|
110
|
+
* For more advanced conflict detection (e.g., path-to-regexp pattern matching),
|
|
111
|
+
* consider integrating with your routing library's conflict detection mechanism.
|
|
112
|
+
*
|
|
113
|
+
* @param endpoint - Endpoint registration
|
|
114
|
+
* @returns Route key (e.g., "GET:/api/v1/customers/:id")
|
|
115
|
+
*/
|
|
116
|
+
private getRouteKey;
|
|
117
|
+
/**
|
|
118
|
+
* Validate endpoint registration
|
|
119
|
+
*
|
|
120
|
+
* @param endpoint - Endpoint to validate
|
|
121
|
+
* @param apiId - API identifier (for error messages)
|
|
122
|
+
* @throws Error if endpoint is invalid
|
|
123
|
+
*/
|
|
124
|
+
private validateEndpoint;
|
|
125
|
+
/**
|
|
126
|
+
* Get an API by ID
|
|
127
|
+
*
|
|
128
|
+
* @param apiId - API identifier
|
|
129
|
+
* @returns API registry entry or undefined
|
|
130
|
+
*/
|
|
131
|
+
getApi(apiId: string): ApiRegistryEntry | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* Get all registered APIs
|
|
134
|
+
*
|
|
135
|
+
* @returns Array of all APIs
|
|
136
|
+
*/
|
|
137
|
+
getAllApis(): ApiRegistryEntry[];
|
|
138
|
+
/**
|
|
139
|
+
* Find APIs matching query criteria
|
|
140
|
+
*
|
|
141
|
+
* Performance optimized with auxiliary indices for O(1) lookups on type, tags, and status.
|
|
142
|
+
*
|
|
143
|
+
* @param query - Discovery query parameters
|
|
144
|
+
* @returns Matching APIs
|
|
145
|
+
*/
|
|
146
|
+
findApis(query: ApiDiscoveryQuery): ApiDiscoveryResponse;
|
|
147
|
+
/**
|
|
148
|
+
* Get endpoint by API ID and endpoint ID
|
|
149
|
+
*
|
|
150
|
+
* @param apiId - API identifier
|
|
151
|
+
* @param endpointId - Endpoint identifier
|
|
152
|
+
* @returns Endpoint registration or undefined
|
|
153
|
+
*/
|
|
154
|
+
getEndpoint(apiId: string, endpointId: string): ApiEndpointRegistration | undefined;
|
|
155
|
+
/**
|
|
156
|
+
* Find endpoint by route (method + path)
|
|
157
|
+
*
|
|
158
|
+
* @param method - HTTP method
|
|
159
|
+
* @param path - URL path
|
|
160
|
+
* @returns Endpoint registration or undefined
|
|
161
|
+
*/
|
|
162
|
+
findEndpointByRoute(method: string, path: string): {
|
|
163
|
+
api: ApiRegistryEntry;
|
|
164
|
+
endpoint: ApiEndpointRegistration;
|
|
165
|
+
} | undefined;
|
|
166
|
+
/**
|
|
167
|
+
* Get complete registry snapshot
|
|
168
|
+
*
|
|
169
|
+
* @returns Current registry state
|
|
170
|
+
*/
|
|
171
|
+
getRegistry(): ApiRegistryType;
|
|
172
|
+
/**
|
|
173
|
+
* Clear all registered APIs
|
|
174
|
+
*
|
|
175
|
+
* **⚠️ SAFETY WARNING:**
|
|
176
|
+
* This method clears all registered APIs and should be used with caution.
|
|
177
|
+
*
|
|
178
|
+
* **Usage Restrictions:**
|
|
179
|
+
* - In production environments (NODE_ENV=production), a `force: true` parameter is required
|
|
180
|
+
* - Primarily intended for testing and development hot-reload scenarios
|
|
181
|
+
*
|
|
182
|
+
* @param options - Clear options
|
|
183
|
+
* @param options.force - Force clear in production environment (default: false)
|
|
184
|
+
* @throws Error if called in production without force flag
|
|
185
|
+
*
|
|
186
|
+
* @example Safe usage in tests
|
|
187
|
+
* ```typescript
|
|
188
|
+
* beforeEach(() => {
|
|
189
|
+
* registry.clear(); // OK in test environment
|
|
190
|
+
* });
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @example Usage in production (requires explicit force)
|
|
194
|
+
* ```typescript
|
|
195
|
+
* // In production, explicit force is required
|
|
196
|
+
* registry.clear({ force: true });
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
clear(options?: {
|
|
200
|
+
force?: boolean;
|
|
201
|
+
}): void;
|
|
202
|
+
/**
|
|
203
|
+
* Get registry statistics
|
|
204
|
+
*
|
|
205
|
+
* @returns Registry statistics
|
|
206
|
+
*/
|
|
207
|
+
getStats(): {
|
|
208
|
+
totalApis: number;
|
|
209
|
+
totalEndpoints: number;
|
|
210
|
+
totalRoutes: number;
|
|
211
|
+
apisByType: Record<string, number>;
|
|
212
|
+
endpointsByApi: Record<string, number>;
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Update auxiliary indices when an API is registered
|
|
216
|
+
*
|
|
217
|
+
* @param api - API entry to index
|
|
218
|
+
* @private
|
|
219
|
+
* @internal
|
|
220
|
+
*/
|
|
221
|
+
private updateIndices;
|
|
222
|
+
/**
|
|
223
|
+
* Remove API from auxiliary indices when unregistered
|
|
224
|
+
*
|
|
225
|
+
* @param api - API entry to remove from indices
|
|
226
|
+
* @private
|
|
227
|
+
* @internal
|
|
228
|
+
*/
|
|
229
|
+
private removeFromIndices;
|
|
230
|
+
/**
|
|
231
|
+
* Helper to ensure an index set exists and return it
|
|
232
|
+
*
|
|
233
|
+
* @param map - Index map
|
|
234
|
+
* @param key - Index key
|
|
235
|
+
* @returns The Set for this key (created if needed)
|
|
236
|
+
* @private
|
|
237
|
+
* @internal
|
|
238
|
+
*/
|
|
239
|
+
private ensureIndexSet;
|
|
240
|
+
/**
|
|
241
|
+
* Helper to remove an ID from an index set and clean up empty sets
|
|
242
|
+
*
|
|
243
|
+
* @param map - Index map
|
|
244
|
+
* @param key - Index key
|
|
245
|
+
* @param id - API ID to remove
|
|
246
|
+
* @private
|
|
247
|
+
* @internal
|
|
248
|
+
*/
|
|
249
|
+
private removeFromIndexSet;
|
|
250
|
+
/**
|
|
251
|
+
* Check if running in production environment
|
|
252
|
+
*
|
|
253
|
+
* @returns true if NODE_ENV is 'production'
|
|
254
|
+
* @private
|
|
255
|
+
* @internal
|
|
256
|
+
*/
|
|
257
|
+
private isProductionEnvironment;
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=api-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-registry.d.ts","sourceRoot":"","sources":["../src/api-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,IAAI,eAAe,EAC9B,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAA4C;IACxD,OAAO,CAAC,SAAS,CAA8E;IAC/F,OAAO,CAAC,MAAM,CAAiF;IAG/F,OAAO,CAAC,UAAU,CAAuC;IACzD,OAAO,CAAC,SAAS,CAAuC;IACxD,OAAO,CAAC,YAAY,CAAuC;IAE3D,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;gBAGxB,MAAM,EAAE,MAAM,EACd,kBAAkB,GAAE,0BAAoC,EACxD,OAAO,GAAE,MAAgB;IAQ3B;;;;;OAKG;IACH,WAAW,CAAC,GAAG,EAAE,qBAAqB,GAAG,IAAI;IAiC7C;;;;OAIG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAqBlC;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAiBxB;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAkB1B;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAmBrB;;;;;;;;;OASG;IACH,OAAO,CAAC,mBAAmB;IA8E3B;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,WAAW;IAKnB;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAUxB;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAInD;;;;OAIG;IACH,UAAU,IAAI,gBAAgB,EAAE;IAIhC;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,oBAAoB;IAsGxD;;;;;;OAMG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS;IAKnF;;;;;;OAMG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;QACjD,GAAG,EAAE,gBAAgB,CAAC;QACtB,QAAQ,EAAE,uBAAuB,CAAC;KACnC,GAAG,SAAS;IAkBb;;;;OAIG;IACH,WAAW,IAAI,eAAe;IAwC9B;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,KAAK,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,IAAI;IA4B9C;;;;OAIG;IACH,QAAQ,IAAI;QACV,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACxC;IAsBD;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAerB;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAezB;;;;;;;;OAQG;IACH,OAAO,CAAC,cAAc;IAStB;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAW1B;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;CAGhC"}
|