@objectstack/core 1.0.4 → 1.0.5
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/.turbo/turbo-build.log +58 -0
- package/CHANGELOG.md +12 -0
- package/dist/index.cjs +4294 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1777 -0
- package/dist/index.d.ts +1776 -21
- package/dist/index.js +4246 -23
- package/dist/index.js.map +1 -0
- package/package.json +4 -4
- package/tsconfig.json +1 -3
- package/dist/api-registry-plugin.d.ts +0 -54
- package/dist/api-registry-plugin.d.ts.map +0 -1
- package/dist/api-registry-plugin.js +0 -53
- package/dist/api-registry-plugin.test.d.ts +0 -2
- package/dist/api-registry-plugin.test.d.ts.map +0 -1
- package/dist/api-registry-plugin.test.js +0 -334
- package/dist/api-registry.d.ts +0 -259
- package/dist/api-registry.d.ts.map +0 -1
- package/dist/api-registry.js +0 -600
- package/dist/api-registry.test.d.ts +0 -2
- package/dist/api-registry.test.d.ts.map +0 -1
- package/dist/api-registry.test.js +0 -957
- package/dist/contracts/data-engine.d.ts +0 -62
- package/dist/contracts/data-engine.d.ts.map +0 -1
- package/dist/contracts/data-engine.js +0 -1
- package/dist/contracts/http-server.d.ts +0 -119
- package/dist/contracts/http-server.d.ts.map +0 -1
- package/dist/contracts/http-server.js +0 -11
- package/dist/contracts/logger.d.ts +0 -63
- package/dist/contracts/logger.d.ts.map +0 -1
- package/dist/contracts/logger.js +0 -1
- package/dist/dependency-resolver.d.ts +0 -62
- package/dist/dependency-resolver.d.ts.map +0 -1
- package/dist/dependency-resolver.js +0 -317
- package/dist/dependency-resolver.test.d.ts +0 -2
- package/dist/dependency-resolver.test.d.ts.map +0 -1
- package/dist/dependency-resolver.test.js +0 -241
- package/dist/health-monitor.d.ts +0 -65
- package/dist/health-monitor.d.ts.map +0 -1
- package/dist/health-monitor.js +0 -269
- package/dist/health-monitor.test.d.ts +0 -2
- package/dist/health-monitor.test.d.ts.map +0 -1
- package/dist/health-monitor.test.js +0 -68
- package/dist/hot-reload.d.ts +0 -79
- package/dist/hot-reload.d.ts.map +0 -1
- package/dist/hot-reload.js +0 -313
- package/dist/index.d.ts.map +0 -1
- package/dist/kernel-base.d.ts +0 -84
- package/dist/kernel-base.d.ts.map +0 -1
- package/dist/kernel-base.js +0 -219
- package/dist/kernel.d.ts +0 -113
- package/dist/kernel.d.ts.map +0 -1
- package/dist/kernel.js +0 -472
- package/dist/kernel.test.d.ts +0 -2
- package/dist/kernel.test.d.ts.map +0 -1
- package/dist/kernel.test.js +0 -414
- package/dist/lite-kernel.d.ts +0 -55
- package/dist/lite-kernel.d.ts.map +0 -1
- package/dist/lite-kernel.js +0 -112
- package/dist/lite-kernel.test.d.ts +0 -2
- package/dist/lite-kernel.test.d.ts.map +0 -1
- package/dist/lite-kernel.test.js +0 -161
- package/dist/logger.d.ts +0 -71
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -312
- package/dist/logger.test.d.ts +0 -2
- package/dist/logger.test.d.ts.map +0 -1
- package/dist/logger.test.js +0 -92
- package/dist/plugin-loader.d.ts +0 -164
- package/dist/plugin-loader.d.ts.map +0 -1
- package/dist/plugin-loader.js +0 -319
- package/dist/plugin-loader.test.d.ts +0 -2
- package/dist/plugin-loader.test.d.ts.map +0 -1
- package/dist/plugin-loader.test.js +0 -348
- package/dist/qa/adapter.d.ts +0 -14
- package/dist/qa/adapter.d.ts.map +0 -1
- package/dist/qa/adapter.js +0 -1
- package/dist/qa/http-adapter.d.ts +0 -16
- package/dist/qa/http-adapter.d.ts.map +0 -1
- package/dist/qa/http-adapter.js +0 -107
- package/dist/qa/index.d.ts +0 -4
- package/dist/qa/index.d.ts.map +0 -1
- package/dist/qa/index.js +0 -3
- package/dist/qa/runner.d.ts +0 -27
- package/dist/qa/runner.d.ts.map +0 -1
- package/dist/qa/runner.js +0 -157
- package/dist/security/index.d.ts +0 -17
- package/dist/security/index.d.ts.map +0 -1
- package/dist/security/index.js +0 -17
- package/dist/security/permission-manager.d.ts +0 -96
- package/dist/security/permission-manager.d.ts.map +0 -1
- package/dist/security/permission-manager.js +0 -235
- package/dist/security/permission-manager.test.d.ts +0 -2
- package/dist/security/permission-manager.test.d.ts.map +0 -1
- package/dist/security/permission-manager.test.js +0 -220
- package/dist/security/plugin-config-validator.d.ts +0 -79
- package/dist/security/plugin-config-validator.d.ts.map +0 -1
- package/dist/security/plugin-config-validator.js +0 -166
- package/dist/security/plugin-config-validator.test.d.ts +0 -2
- package/dist/security/plugin-config-validator.test.d.ts.map +0 -1
- package/dist/security/plugin-config-validator.test.js +0 -223
- package/dist/security/plugin-permission-enforcer.d.ts +0 -154
- package/dist/security/plugin-permission-enforcer.d.ts.map +0 -1
- package/dist/security/plugin-permission-enforcer.js +0 -323
- package/dist/security/plugin-permission-enforcer.test.d.ts +0 -2
- package/dist/security/plugin-permission-enforcer.test.d.ts.map +0 -1
- package/dist/security/plugin-permission-enforcer.test.js +0 -205
- package/dist/security/plugin-signature-verifier.d.ts +0 -96
- package/dist/security/plugin-signature-verifier.d.ts.map +0 -1
- package/dist/security/plugin-signature-verifier.js +0 -250
- package/dist/security/sandbox-runtime.d.ts +0 -115
- package/dist/security/sandbox-runtime.d.ts.map +0 -1
- package/dist/security/sandbox-runtime.js +0 -311
- package/dist/security/security-scanner.d.ts +0 -92
- package/dist/security/security-scanner.d.ts.map +0 -1
- package/dist/security/security-scanner.js +0 -273
- package/dist/types.d.ts +0 -89
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
- package/dist/utils/env.d.ts +0 -20
- package/dist/utils/env.d.ts.map +0 -1
- package/dist/utils/env.js +0 -46
- package/dist/utils/env.test.d.ts +0 -2
- package/dist/utils/env.test.d.ts.map +0 -1
- package/dist/utils/env.test.js +0 -52
package/dist/api-registry.js
DELETED
|
@@ -1,600 +0,0 @@
|
|
|
1
|
-
import { ApiRegistryEntrySchema } from '@objectstack/spec/api';
|
|
2
|
-
import { getEnv } from './utils/env.js';
|
|
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 class ApiRegistry {
|
|
43
|
-
constructor(logger, conflictResolution = 'error', version = '1.0.0') {
|
|
44
|
-
this.apis = new Map();
|
|
45
|
-
this.endpoints = new Map();
|
|
46
|
-
this.routes = new Map();
|
|
47
|
-
// Performance optimization: Auxiliary indices for O(1) lookups
|
|
48
|
-
this.apisByType = new Map();
|
|
49
|
-
this.apisByTag = new Map();
|
|
50
|
-
this.apisByStatus = new Map();
|
|
51
|
-
this.logger = logger;
|
|
52
|
-
this.conflictResolution = conflictResolution;
|
|
53
|
-
this.version = version;
|
|
54
|
-
this.updatedAt = new Date().toISOString();
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Register an API with its endpoints
|
|
58
|
-
*
|
|
59
|
-
* @param api - API registry entry
|
|
60
|
-
* @throws Error if API already registered or route conflicts detected
|
|
61
|
-
*/
|
|
62
|
-
registerApi(api) {
|
|
63
|
-
// Check if API already exists
|
|
64
|
-
if (this.apis.has(api.id)) {
|
|
65
|
-
throw new Error(`[ApiRegistry] API '${api.id}' already registered`);
|
|
66
|
-
}
|
|
67
|
-
// Parse and validate the input using Zod schema
|
|
68
|
-
const fullApi = ApiRegistryEntrySchema.parse(api);
|
|
69
|
-
// Validate and register endpoints
|
|
70
|
-
for (const endpoint of fullApi.endpoints) {
|
|
71
|
-
this.validateEndpoint(endpoint, fullApi.id);
|
|
72
|
-
}
|
|
73
|
-
// Register the API
|
|
74
|
-
this.apis.set(fullApi.id, fullApi);
|
|
75
|
-
// Register endpoints
|
|
76
|
-
for (const endpoint of fullApi.endpoints) {
|
|
77
|
-
this.registerEndpoint(fullApi.id, endpoint);
|
|
78
|
-
}
|
|
79
|
-
// Update auxiliary indices for performance optimization
|
|
80
|
-
this.updateIndices(fullApi);
|
|
81
|
-
this.updatedAt = new Date().toISOString();
|
|
82
|
-
this.logger.info(`API registered: ${fullApi.id}`, {
|
|
83
|
-
api: fullApi.id,
|
|
84
|
-
type: fullApi.type,
|
|
85
|
-
endpointCount: fullApi.endpoints.length,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Unregister an API and all its endpoints
|
|
90
|
-
*
|
|
91
|
-
* @param apiId - API identifier
|
|
92
|
-
*/
|
|
93
|
-
unregisterApi(apiId) {
|
|
94
|
-
const api = this.apis.get(apiId);
|
|
95
|
-
if (!api) {
|
|
96
|
-
throw new Error(`[ApiRegistry] API '${apiId}' not found`);
|
|
97
|
-
}
|
|
98
|
-
// Remove all endpoints
|
|
99
|
-
for (const endpoint of api.endpoints) {
|
|
100
|
-
this.unregisterEndpoint(apiId, endpoint.id);
|
|
101
|
-
}
|
|
102
|
-
// Remove from auxiliary indices
|
|
103
|
-
this.removeFromIndices(api);
|
|
104
|
-
// Remove the API
|
|
105
|
-
this.apis.delete(apiId);
|
|
106
|
-
this.updatedAt = new Date().toISOString();
|
|
107
|
-
this.logger.info(`API unregistered: ${apiId}`);
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Register a single endpoint
|
|
111
|
-
*
|
|
112
|
-
* @param apiId - API identifier
|
|
113
|
-
* @param endpoint - Endpoint registration
|
|
114
|
-
* @throws Error if route conflict detected
|
|
115
|
-
*/
|
|
116
|
-
registerEndpoint(apiId, endpoint) {
|
|
117
|
-
const endpointKey = `${apiId}:${endpoint.id}`;
|
|
118
|
-
// Check if endpoint already registered
|
|
119
|
-
if (this.endpoints.has(endpointKey)) {
|
|
120
|
-
throw new Error(`[ApiRegistry] Endpoint '${endpoint.id}' already registered for API '${apiId}'`);
|
|
121
|
-
}
|
|
122
|
-
// Register endpoint
|
|
123
|
-
this.endpoints.set(endpointKey, { api: apiId, endpoint });
|
|
124
|
-
// Register route if path is defined
|
|
125
|
-
if (endpoint.path) {
|
|
126
|
-
this.registerRoute(apiId, endpoint);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Unregister a single endpoint
|
|
131
|
-
*
|
|
132
|
-
* @param apiId - API identifier
|
|
133
|
-
* @param endpointId - Endpoint identifier
|
|
134
|
-
*/
|
|
135
|
-
unregisterEndpoint(apiId, endpointId) {
|
|
136
|
-
const endpointKey = `${apiId}:${endpointId}`;
|
|
137
|
-
const entry = this.endpoints.get(endpointKey);
|
|
138
|
-
if (!entry) {
|
|
139
|
-
return; // Already unregistered
|
|
140
|
-
}
|
|
141
|
-
// Unregister route
|
|
142
|
-
if (entry.endpoint.path) {
|
|
143
|
-
const routeKey = this.getRouteKey(entry.endpoint);
|
|
144
|
-
this.routes.delete(routeKey);
|
|
145
|
-
}
|
|
146
|
-
// Unregister endpoint
|
|
147
|
-
this.endpoints.delete(endpointKey);
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Register a route with conflict detection
|
|
151
|
-
*
|
|
152
|
-
* @param apiId - API identifier
|
|
153
|
-
* @param endpoint - Endpoint registration
|
|
154
|
-
* @throws Error if route conflict detected (based on strategy)
|
|
155
|
-
*/
|
|
156
|
-
registerRoute(apiId, endpoint) {
|
|
157
|
-
const routeKey = this.getRouteKey(endpoint);
|
|
158
|
-
const priority = endpoint.priority ?? 100;
|
|
159
|
-
const existingRoute = this.routes.get(routeKey);
|
|
160
|
-
if (existingRoute) {
|
|
161
|
-
// Route conflict detected
|
|
162
|
-
this.handleRouteConflict(routeKey, apiId, endpoint, existingRoute, priority);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
// Register route
|
|
166
|
-
this.routes.set(routeKey, {
|
|
167
|
-
api: apiId,
|
|
168
|
-
endpointId: endpoint.id,
|
|
169
|
-
priority,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Handle route conflict based on resolution strategy
|
|
174
|
-
*
|
|
175
|
-
* @param routeKey - Route key
|
|
176
|
-
* @param apiId - New API identifier
|
|
177
|
-
* @param endpoint - New endpoint
|
|
178
|
-
* @param existingRoute - Existing route registration
|
|
179
|
-
* @param newPriority - New endpoint priority
|
|
180
|
-
* @throws Error if strategy is 'error'
|
|
181
|
-
*/
|
|
182
|
-
handleRouteConflict(routeKey, apiId, endpoint, existingRoute, newPriority) {
|
|
183
|
-
const strategy = this.conflictResolution;
|
|
184
|
-
switch (strategy) {
|
|
185
|
-
case 'error':
|
|
186
|
-
throw new Error(`[ApiRegistry] Route conflict detected: '${routeKey}' is already registered by API '${existingRoute.api}' endpoint '${existingRoute.endpointId}'`);
|
|
187
|
-
case 'priority':
|
|
188
|
-
if (newPriority > existingRoute.priority) {
|
|
189
|
-
// New endpoint has higher priority, replace
|
|
190
|
-
this.logger.warn(`Route conflict: replacing '${routeKey}' (priority ${existingRoute.priority} -> ${newPriority})`, {
|
|
191
|
-
oldApi: existingRoute.api,
|
|
192
|
-
oldEndpoint: existingRoute.endpointId,
|
|
193
|
-
newApi: apiId,
|
|
194
|
-
newEndpoint: endpoint.id,
|
|
195
|
-
});
|
|
196
|
-
this.routes.set(routeKey, {
|
|
197
|
-
api: apiId,
|
|
198
|
-
endpointId: endpoint.id,
|
|
199
|
-
priority: newPriority,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
// Existing endpoint has higher priority, keep it
|
|
204
|
-
this.logger.warn(`Route conflict: keeping existing '${routeKey}' (priority ${existingRoute.priority} >= ${newPriority})`, {
|
|
205
|
-
existingApi: existingRoute.api,
|
|
206
|
-
existingEndpoint: existingRoute.endpointId,
|
|
207
|
-
newApi: apiId,
|
|
208
|
-
newEndpoint: endpoint.id,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
break;
|
|
212
|
-
case 'first-wins':
|
|
213
|
-
// Keep existing route
|
|
214
|
-
this.logger.warn(`Route conflict: keeping first registered '${routeKey}'`, {
|
|
215
|
-
existingApi: existingRoute.api,
|
|
216
|
-
newApi: apiId,
|
|
217
|
-
});
|
|
218
|
-
break;
|
|
219
|
-
case 'last-wins':
|
|
220
|
-
// Replace with new route
|
|
221
|
-
this.logger.warn(`Route conflict: replacing with last registered '${routeKey}'`, {
|
|
222
|
-
oldApi: existingRoute.api,
|
|
223
|
-
newApi: apiId,
|
|
224
|
-
});
|
|
225
|
-
this.routes.set(routeKey, {
|
|
226
|
-
api: apiId,
|
|
227
|
-
endpointId: endpoint.id,
|
|
228
|
-
priority: newPriority,
|
|
229
|
-
});
|
|
230
|
-
break;
|
|
231
|
-
default:
|
|
232
|
-
throw new Error(`[ApiRegistry] Unknown conflict resolution strategy: ${strategy}`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Generate a unique route key for conflict detection
|
|
237
|
-
*
|
|
238
|
-
* NOTE: This implementation uses exact string matching for route conflict detection.
|
|
239
|
-
* It works well for static paths but has limitations with parameterized routes.
|
|
240
|
-
* For example, `/api/users/:id` and `/api/users/:userId` will NOT be detected as conflicts
|
|
241
|
-
* even though they are semantically identical parameterized patterns. Similarly,
|
|
242
|
-
* `/api/:resource/list` and `/api/:entity/list` would also not be detected as conflicting.
|
|
243
|
-
*
|
|
244
|
-
* For more advanced conflict detection (e.g., path-to-regexp pattern matching),
|
|
245
|
-
* consider integrating with your routing library's conflict detection mechanism.
|
|
246
|
-
*
|
|
247
|
-
* @param endpoint - Endpoint registration
|
|
248
|
-
* @returns Route key (e.g., "GET:/api/v1/customers/:id")
|
|
249
|
-
*/
|
|
250
|
-
getRouteKey(endpoint) {
|
|
251
|
-
const method = endpoint.method || 'ANY';
|
|
252
|
-
return `${method}:${endpoint.path}`;
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Validate endpoint registration
|
|
256
|
-
*
|
|
257
|
-
* @param endpoint - Endpoint to validate
|
|
258
|
-
* @param apiId - API identifier (for error messages)
|
|
259
|
-
* @throws Error if endpoint is invalid
|
|
260
|
-
*/
|
|
261
|
-
validateEndpoint(endpoint, apiId) {
|
|
262
|
-
if (!endpoint.id) {
|
|
263
|
-
throw new Error(`[ApiRegistry] Endpoint in API '${apiId}' missing 'id' field`);
|
|
264
|
-
}
|
|
265
|
-
if (!endpoint.path) {
|
|
266
|
-
throw new Error(`[ApiRegistry] Endpoint '${endpoint.id}' in API '${apiId}' missing 'path' field`);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Get an API by ID
|
|
271
|
-
*
|
|
272
|
-
* @param apiId - API identifier
|
|
273
|
-
* @returns API registry entry or undefined
|
|
274
|
-
*/
|
|
275
|
-
getApi(apiId) {
|
|
276
|
-
return this.apis.get(apiId);
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Get all registered APIs
|
|
280
|
-
*
|
|
281
|
-
* @returns Array of all APIs
|
|
282
|
-
*/
|
|
283
|
-
getAllApis() {
|
|
284
|
-
return Array.from(this.apis.values());
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Find APIs matching query criteria
|
|
288
|
-
*
|
|
289
|
-
* Performance optimized with auxiliary indices for O(1) lookups on type, tags, and status.
|
|
290
|
-
*
|
|
291
|
-
* @param query - Discovery query parameters
|
|
292
|
-
* @returns Matching APIs
|
|
293
|
-
*/
|
|
294
|
-
findApis(query) {
|
|
295
|
-
let resultIds;
|
|
296
|
-
// Use indices for performance-optimized filtering
|
|
297
|
-
// Start with the most restrictive filter to minimize subsequent filtering
|
|
298
|
-
// Filter by type (using index for O(1) lookup)
|
|
299
|
-
if (query.type) {
|
|
300
|
-
const typeIds = this.apisByType.get(query.type);
|
|
301
|
-
if (!typeIds || typeIds.size === 0) {
|
|
302
|
-
return { apis: [], total: 0, filters: query };
|
|
303
|
-
}
|
|
304
|
-
resultIds = new Set(typeIds);
|
|
305
|
-
}
|
|
306
|
-
// Filter by status (using index for O(1) lookup)
|
|
307
|
-
if (query.status) {
|
|
308
|
-
const statusIds = this.apisByStatus.get(query.status);
|
|
309
|
-
if (!statusIds || statusIds.size === 0) {
|
|
310
|
-
return { apis: [], total: 0, filters: query };
|
|
311
|
-
}
|
|
312
|
-
if (resultIds) {
|
|
313
|
-
// Intersect with previous results
|
|
314
|
-
resultIds = new Set([...resultIds].filter(id => statusIds.has(id)));
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
resultIds = new Set(statusIds);
|
|
318
|
-
}
|
|
319
|
-
if (resultIds.size === 0) {
|
|
320
|
-
return { apis: [], total: 0, filters: query };
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
// Filter by tags (using index for O(M) lookup where M is number of tags)
|
|
324
|
-
if (query.tags && query.tags.length > 0) {
|
|
325
|
-
const tagMatches = new Set();
|
|
326
|
-
for (const tag of query.tags) {
|
|
327
|
-
const tagIds = this.apisByTag.get(tag);
|
|
328
|
-
if (tagIds) {
|
|
329
|
-
tagIds.forEach(id => tagMatches.add(id));
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
if (tagMatches.size === 0) {
|
|
333
|
-
return { apis: [], total: 0, filters: query };
|
|
334
|
-
}
|
|
335
|
-
if (resultIds) {
|
|
336
|
-
// Intersect with previous results
|
|
337
|
-
resultIds = new Set([...resultIds].filter(id => tagMatches.has(id)));
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
resultIds = tagMatches;
|
|
341
|
-
}
|
|
342
|
-
if (resultIds.size === 0) {
|
|
343
|
-
return { apis: [], total: 0, filters: query };
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
// Get the actual API objects
|
|
347
|
-
let results;
|
|
348
|
-
if (resultIds) {
|
|
349
|
-
results = Array.from(resultIds)
|
|
350
|
-
.map(id => this.apis.get(id))
|
|
351
|
-
.filter((api) => api !== undefined);
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
354
|
-
results = Array.from(this.apis.values());
|
|
355
|
-
}
|
|
356
|
-
// Apply remaining filters that don't have indices (less common filters)
|
|
357
|
-
// Filter by plugin source
|
|
358
|
-
if (query.pluginSource) {
|
|
359
|
-
results = results.filter((api) => api.metadata?.pluginSource === query.pluginSource);
|
|
360
|
-
}
|
|
361
|
-
// Filter by version
|
|
362
|
-
if (query.version) {
|
|
363
|
-
results = results.filter((api) => api.version === query.version);
|
|
364
|
-
}
|
|
365
|
-
// Search in name/description
|
|
366
|
-
if (query.search) {
|
|
367
|
-
const searchLower = query.search.toLowerCase();
|
|
368
|
-
results = results.filter((api) => api.name.toLowerCase().includes(searchLower) ||
|
|
369
|
-
(api.description && api.description.toLowerCase().includes(searchLower)));
|
|
370
|
-
}
|
|
371
|
-
return {
|
|
372
|
-
apis: results,
|
|
373
|
-
total: results.length,
|
|
374
|
-
filters: query,
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Get endpoint by API ID and endpoint ID
|
|
379
|
-
*
|
|
380
|
-
* @param apiId - API identifier
|
|
381
|
-
* @param endpointId - Endpoint identifier
|
|
382
|
-
* @returns Endpoint registration or undefined
|
|
383
|
-
*/
|
|
384
|
-
getEndpoint(apiId, endpointId) {
|
|
385
|
-
const key = `${apiId}:${endpointId}`;
|
|
386
|
-
return this.endpoints.get(key)?.endpoint;
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* Find endpoint by route (method + path)
|
|
390
|
-
*
|
|
391
|
-
* @param method - HTTP method
|
|
392
|
-
* @param path - URL path
|
|
393
|
-
* @returns Endpoint registration or undefined
|
|
394
|
-
*/
|
|
395
|
-
findEndpointByRoute(method, path) {
|
|
396
|
-
const routeKey = `${method}:${path}`;
|
|
397
|
-
const route = this.routes.get(routeKey);
|
|
398
|
-
if (!route) {
|
|
399
|
-
return undefined;
|
|
400
|
-
}
|
|
401
|
-
const api = this.apis.get(route.api);
|
|
402
|
-
const endpoint = this.getEndpoint(route.api, route.endpointId);
|
|
403
|
-
if (!api || !endpoint) {
|
|
404
|
-
return undefined;
|
|
405
|
-
}
|
|
406
|
-
return { api, endpoint };
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Get complete registry snapshot
|
|
410
|
-
*
|
|
411
|
-
* @returns Current registry state
|
|
412
|
-
*/
|
|
413
|
-
getRegistry() {
|
|
414
|
-
const apis = Array.from(this.apis.values());
|
|
415
|
-
// Group by type
|
|
416
|
-
const byType = {};
|
|
417
|
-
for (const api of apis) {
|
|
418
|
-
if (!byType[api.type]) {
|
|
419
|
-
byType[api.type] = [];
|
|
420
|
-
}
|
|
421
|
-
byType[api.type].push(api);
|
|
422
|
-
}
|
|
423
|
-
// Group by status
|
|
424
|
-
const byStatus = {};
|
|
425
|
-
for (const api of apis) {
|
|
426
|
-
const status = api.metadata?.status || 'active';
|
|
427
|
-
if (!byStatus[status]) {
|
|
428
|
-
byStatus[status] = [];
|
|
429
|
-
}
|
|
430
|
-
byStatus[status].push(api);
|
|
431
|
-
}
|
|
432
|
-
// Count total endpoints
|
|
433
|
-
const totalEndpoints = apis.reduce((sum, api) => sum + api.endpoints.length, 0);
|
|
434
|
-
return {
|
|
435
|
-
version: this.version,
|
|
436
|
-
conflictResolution: this.conflictResolution,
|
|
437
|
-
apis,
|
|
438
|
-
totalApis: apis.length,
|
|
439
|
-
totalEndpoints,
|
|
440
|
-
byType,
|
|
441
|
-
byStatus,
|
|
442
|
-
updatedAt: this.updatedAt,
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Clear all registered APIs
|
|
447
|
-
*
|
|
448
|
-
* **⚠️ SAFETY WARNING:**
|
|
449
|
-
* This method clears all registered APIs and should be used with caution.
|
|
450
|
-
*
|
|
451
|
-
* **Usage Restrictions:**
|
|
452
|
-
* - In production environments (NODE_ENV=production), a `force: true` parameter is required
|
|
453
|
-
* - Primarily intended for testing and development hot-reload scenarios
|
|
454
|
-
*
|
|
455
|
-
* @param options - Clear options
|
|
456
|
-
* @param options.force - Force clear in production environment (default: false)
|
|
457
|
-
* @throws Error if called in production without force flag
|
|
458
|
-
*
|
|
459
|
-
* @example Safe usage in tests
|
|
460
|
-
* ```typescript
|
|
461
|
-
* beforeEach(() => {
|
|
462
|
-
* registry.clear(); // OK in test environment
|
|
463
|
-
* });
|
|
464
|
-
* ```
|
|
465
|
-
*
|
|
466
|
-
* @example Usage in production (requires explicit force)
|
|
467
|
-
* ```typescript
|
|
468
|
-
* // In production, explicit force is required
|
|
469
|
-
* registry.clear({ force: true });
|
|
470
|
-
* ```
|
|
471
|
-
*/
|
|
472
|
-
clear(options = {}) {
|
|
473
|
-
const isProduction = this.isProductionEnvironment();
|
|
474
|
-
if (isProduction && !options.force) {
|
|
475
|
-
throw new Error('[ApiRegistry] Cannot clear registry in production environment without force flag. ' +
|
|
476
|
-
'Use clear({ force: true }) if you really want to clear the registry.');
|
|
477
|
-
}
|
|
478
|
-
this.apis.clear();
|
|
479
|
-
this.endpoints.clear();
|
|
480
|
-
this.routes.clear();
|
|
481
|
-
// Clear auxiliary indices
|
|
482
|
-
this.apisByType.clear();
|
|
483
|
-
this.apisByTag.clear();
|
|
484
|
-
this.apisByStatus.clear();
|
|
485
|
-
this.updatedAt = new Date().toISOString();
|
|
486
|
-
if (isProduction) {
|
|
487
|
-
this.logger.warn('API registry forcefully cleared in production', { force: options.force });
|
|
488
|
-
}
|
|
489
|
-
else {
|
|
490
|
-
this.logger.info('API registry cleared');
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* Get registry statistics
|
|
495
|
-
*
|
|
496
|
-
* @returns Registry statistics
|
|
497
|
-
*/
|
|
498
|
-
getStats() {
|
|
499
|
-
const apis = Array.from(this.apis.values());
|
|
500
|
-
const apisByType = {};
|
|
501
|
-
for (const api of apis) {
|
|
502
|
-
apisByType[api.type] = (apisByType[api.type] || 0) + 1;
|
|
503
|
-
}
|
|
504
|
-
const endpointsByApi = {};
|
|
505
|
-
for (const api of apis) {
|
|
506
|
-
endpointsByApi[api.id] = api.endpoints.length;
|
|
507
|
-
}
|
|
508
|
-
return {
|
|
509
|
-
totalApis: this.apis.size,
|
|
510
|
-
totalEndpoints: this.endpoints.size,
|
|
511
|
-
totalRoutes: this.routes.size,
|
|
512
|
-
apisByType,
|
|
513
|
-
endpointsByApi,
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Update auxiliary indices when an API is registered
|
|
518
|
-
*
|
|
519
|
-
* @param api - API entry to index
|
|
520
|
-
* @private
|
|
521
|
-
* @internal
|
|
522
|
-
*/
|
|
523
|
-
updateIndices(api) {
|
|
524
|
-
// Index by type
|
|
525
|
-
this.ensureIndexSet(this.apisByType, api.type).add(api.id);
|
|
526
|
-
// Index by status
|
|
527
|
-
const status = api.metadata?.status || 'active';
|
|
528
|
-
this.ensureIndexSet(this.apisByStatus, status).add(api.id);
|
|
529
|
-
// Index by tags
|
|
530
|
-
const tags = api.metadata?.tags || [];
|
|
531
|
-
for (const tag of tags) {
|
|
532
|
-
this.ensureIndexSet(this.apisByTag, tag).add(api.id);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
/**
|
|
536
|
-
* Remove API from auxiliary indices when unregistered
|
|
537
|
-
*
|
|
538
|
-
* @param api - API entry to remove from indices
|
|
539
|
-
* @private
|
|
540
|
-
* @internal
|
|
541
|
-
*/
|
|
542
|
-
removeFromIndices(api) {
|
|
543
|
-
// Remove from type index
|
|
544
|
-
this.removeFromIndexSet(this.apisByType, api.type, api.id);
|
|
545
|
-
// Remove from status index
|
|
546
|
-
const status = api.metadata?.status || 'active';
|
|
547
|
-
this.removeFromIndexSet(this.apisByStatus, status, api.id);
|
|
548
|
-
// Remove from tag indices
|
|
549
|
-
const tags = api.metadata?.tags || [];
|
|
550
|
-
for (const tag of tags) {
|
|
551
|
-
this.removeFromIndexSet(this.apisByTag, tag, api.id);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* Helper to ensure an index set exists and return it
|
|
556
|
-
*
|
|
557
|
-
* @param map - Index map
|
|
558
|
-
* @param key - Index key
|
|
559
|
-
* @returns The Set for this key (created if needed)
|
|
560
|
-
* @private
|
|
561
|
-
* @internal
|
|
562
|
-
*/
|
|
563
|
-
ensureIndexSet(map, key) {
|
|
564
|
-
let set = map.get(key);
|
|
565
|
-
if (!set) {
|
|
566
|
-
set = new Set();
|
|
567
|
-
map.set(key, set);
|
|
568
|
-
}
|
|
569
|
-
return set;
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Helper to remove an ID from an index set and clean up empty sets
|
|
573
|
-
*
|
|
574
|
-
* @param map - Index map
|
|
575
|
-
* @param key - Index key
|
|
576
|
-
* @param id - API ID to remove
|
|
577
|
-
* @private
|
|
578
|
-
* @internal
|
|
579
|
-
*/
|
|
580
|
-
removeFromIndexSet(map, key, id) {
|
|
581
|
-
const set = map.get(key);
|
|
582
|
-
if (set) {
|
|
583
|
-
set.delete(id);
|
|
584
|
-
// Clean up empty sets to avoid memory leaks
|
|
585
|
-
if (set.size === 0) {
|
|
586
|
-
map.delete(key);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
/**
|
|
591
|
-
* Check if running in production environment
|
|
592
|
-
*
|
|
593
|
-
* @returns true if NODE_ENV is 'production'
|
|
594
|
-
* @private
|
|
595
|
-
* @internal
|
|
596
|
-
*/
|
|
597
|
-
isProductionEnvironment() {
|
|
598
|
-
return getEnv('NODE_ENV') === 'production';
|
|
599
|
-
}
|
|
600
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api-registry.test.d.ts","sourceRoot":"","sources":["../src/api-registry.test.ts"],"names":[],"mappings":""}
|