@objectstack/core 4.0.3 → 4.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.
Files changed (75) hide show
  1. package/README.md +95 -10
  2. package/dist/index.cjs +169 -507
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +24 -223
  5. package/dist/index.d.ts +24 -223
  6. package/dist/index.js +175 -505
  7. package/dist/index.js.map +1 -1
  8. package/dist/logger.cjs +177 -0
  9. package/dist/logger.cjs.map +1 -0
  10. package/dist/logger.d.cts +26 -0
  11. package/dist/logger.d.ts +26 -0
  12. package/dist/logger.js +158 -0
  13. package/dist/logger.js.map +1 -0
  14. package/package.json +36 -15
  15. package/.turbo/turbo-build.log +0 -22
  16. package/ADVANCED_FEATURES.md +0 -380
  17. package/API_REGISTRY.md +0 -392
  18. package/CHANGELOG.md +0 -465
  19. package/PHASE2_IMPLEMENTATION.md +0 -388
  20. package/REFACTORING_SUMMARY.md +0 -40
  21. package/examples/api-registry-example.ts +0 -559
  22. package/examples/kernel-features-example.ts +0 -311
  23. package/examples/phase2-integration.ts +0 -357
  24. package/src/api-registry-plugin.test.ts +0 -393
  25. package/src/api-registry-plugin.ts +0 -89
  26. package/src/api-registry.test.ts +0 -1089
  27. package/src/api-registry.ts +0 -739
  28. package/src/contracts/data-engine.ts +0 -57
  29. package/src/contracts/http-server.ts +0 -151
  30. package/src/contracts/logger.ts +0 -72
  31. package/src/dependency-resolver.test.ts +0 -287
  32. package/src/dependency-resolver.ts +0 -390
  33. package/src/fallbacks/fallbacks.test.ts +0 -281
  34. package/src/fallbacks/index.ts +0 -26
  35. package/src/fallbacks/memory-cache.ts +0 -34
  36. package/src/fallbacks/memory-i18n.ts +0 -112
  37. package/src/fallbacks/memory-job.ts +0 -23
  38. package/src/fallbacks/memory-metadata.ts +0 -50
  39. package/src/fallbacks/memory-queue.ts +0 -28
  40. package/src/health-monitor.test.ts +0 -81
  41. package/src/health-monitor.ts +0 -318
  42. package/src/hot-reload.ts +0 -382
  43. package/src/index.ts +0 -50
  44. package/src/kernel-base.ts +0 -273
  45. package/src/kernel.test.ts +0 -624
  46. package/src/kernel.ts +0 -631
  47. package/src/lite-kernel.test.ts +0 -248
  48. package/src/lite-kernel.ts +0 -137
  49. package/src/logger.test.ts +0 -116
  50. package/src/logger.ts +0 -355
  51. package/src/namespace-resolver.test.ts +0 -130
  52. package/src/namespace-resolver.ts +0 -188
  53. package/src/package-manager.test.ts +0 -225
  54. package/src/package-manager.ts +0 -428
  55. package/src/plugin-loader.test.ts +0 -421
  56. package/src/plugin-loader.ts +0 -484
  57. package/src/qa/adapter.ts +0 -16
  58. package/src/qa/http-adapter.ts +0 -116
  59. package/src/qa/index.ts +0 -5
  60. package/src/qa/runner.ts +0 -189
  61. package/src/security/index.ts +0 -50
  62. package/src/security/permission-manager.test.ts +0 -256
  63. package/src/security/permission-manager.ts +0 -338
  64. package/src/security/plugin-config-validator.test.ts +0 -276
  65. package/src/security/plugin-config-validator.ts +0 -193
  66. package/src/security/plugin-permission-enforcer.test.ts +0 -251
  67. package/src/security/plugin-permission-enforcer.ts +0 -436
  68. package/src/security/plugin-signature-verifier.ts +0 -403
  69. package/src/security/sandbox-runtime.ts +0 -462
  70. package/src/security/security-scanner.ts +0 -367
  71. package/src/types.ts +0 -120
  72. package/src/utils/env.test.ts +0 -62
  73. package/src/utils/env.ts +0 -53
  74. package/tsconfig.json +0 -10
  75. package/vitest.config.ts +0 -10
@@ -1,739 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import type {
4
- ApiRegistry as ApiRegistryType,
5
- ApiRegistryEntry,
6
- ApiRegistryEntryInput,
7
- ApiEndpointRegistration,
8
- ConflictResolutionStrategy,
9
- ApiDiscoveryQuery,
10
- ApiDiscoveryResponse,
11
- } from '@objectstack/spec/api';
12
- import { ApiRegistryEntrySchema } from '@objectstack/spec/api';
13
- import type { Logger } from '@objectstack/spec/contracts';
14
- import { getEnv } from './utils/env.js';
15
-
16
- /**
17
- * API Registry Service
18
- *
19
- * Central registry for managing API endpoints across different protocols.
20
- * Provides endpoint registration, discovery, and conflict resolution.
21
- *
22
- * **Features:**
23
- * - Multi-protocol support (REST, GraphQL, OData, WebSocket, etc.)
24
- * - Route conflict detection with configurable resolution strategies
25
- * - RBAC permission integration
26
- * - Dynamic schema linking with ObjectQL references
27
- * - Plugin API registration
28
- *
29
- * **Architecture Alignment:**
30
- * - Kubernetes: Service Discovery & API Server
31
- * - AWS API Gateway: Unified API Management
32
- * - Kong Gateway: Plugin-based API Management
33
- *
34
- * @example
35
- * ```typescript
36
- * const registry = new ApiRegistry(logger, 'priority');
37
- *
38
- * // Register an API
39
- * registry.registerApi({
40
- * id: 'customer_api',
41
- * name: 'Customer API',
42
- * type: 'rest',
43
- * version: 'v1',
44
- * basePath: '/api/v1/customers',
45
- * endpoints: [...]
46
- * });
47
- *
48
- * // Discover APIs
49
- * const apis = registry.findApis({ type: 'rest', status: 'active' });
50
- *
51
- * // Get registry snapshot
52
- * const snapshot = registry.getRegistry();
53
- * ```
54
- */
55
- export class ApiRegistry {
56
- private apis: Map<string, ApiRegistryEntry> = new Map();
57
- private endpoints: Map<string, { api: string; endpoint: ApiEndpointRegistration }> = new Map();
58
- private routes: Map<string, { api: string; endpointId: string; priority: number }> = new Map();
59
-
60
- // Performance optimization: Auxiliary indices for O(1) lookups
61
- private apisByType: Map<string, Set<string>> = new Map();
62
- private apisByTag: Map<string, Set<string>> = new Map();
63
- private apisByStatus: Map<string, Set<string>> = new Map();
64
-
65
- private conflictResolution: ConflictResolutionStrategy;
66
- private logger: Logger;
67
- private version: string;
68
- private updatedAt: string;
69
-
70
- constructor(
71
- logger: Logger,
72
- conflictResolution: ConflictResolutionStrategy = 'error',
73
- version: string = '1.0.0'
74
- ) {
75
- this.logger = logger;
76
- this.conflictResolution = conflictResolution;
77
- this.version = version;
78
- this.updatedAt = new Date().toISOString();
79
- }
80
-
81
- /**
82
- * Register an API with its endpoints
83
- *
84
- * @param api - API registry entry
85
- * @throws Error if API already registered or route conflicts detected
86
- */
87
- registerApi(api: ApiRegistryEntryInput): void {
88
- // Check if API already exists
89
- if (this.apis.has(api.id)) {
90
- throw new Error(`[ApiRegistry] API '${api.id}' already registered`);
91
- }
92
-
93
- // Parse and validate the input using Zod schema
94
- const fullApi = ApiRegistryEntrySchema.parse(api);
95
-
96
- // Validate and register endpoints
97
- for (const endpoint of fullApi.endpoints) {
98
- this.validateEndpoint(endpoint, fullApi.id);
99
- }
100
-
101
- // Register the API
102
- this.apis.set(fullApi.id, fullApi);
103
-
104
- // Register endpoints
105
- for (const endpoint of fullApi.endpoints) {
106
- this.registerEndpoint(fullApi.id, endpoint);
107
- }
108
-
109
- // Update auxiliary indices for performance optimization
110
- this.updateIndices(fullApi);
111
-
112
- this.updatedAt = new Date().toISOString();
113
- this.logger.info(`API registered: ${fullApi.id}`, {
114
- api: fullApi.id,
115
- type: fullApi.type,
116
- endpointCount: fullApi.endpoints.length,
117
- });
118
- }
119
-
120
- /**
121
- * Unregister an API and all its endpoints
122
- *
123
- * @param apiId - API identifier
124
- */
125
- unregisterApi(apiId: string): void {
126
- const api = this.apis.get(apiId);
127
- if (!api) {
128
- throw new Error(`[ApiRegistry] API '${apiId}' not found`);
129
- }
130
-
131
- // Remove all endpoints
132
- for (const endpoint of api.endpoints) {
133
- this.unregisterEndpoint(apiId, endpoint.id);
134
- }
135
-
136
- // Remove from auxiliary indices
137
- this.removeFromIndices(api);
138
-
139
- // Remove the API
140
- this.apis.delete(apiId);
141
- this.updatedAt = new Date().toISOString();
142
-
143
- this.logger.info(`API unregistered: ${apiId}`);
144
- }
145
-
146
- /**
147
- * Register a single endpoint
148
- *
149
- * @param apiId - API identifier
150
- * @param endpoint - Endpoint registration
151
- * @throws Error if route conflict detected
152
- */
153
- private registerEndpoint(apiId: string, endpoint: ApiEndpointRegistration): void {
154
- const endpointKey = `${apiId}:${endpoint.id}`;
155
-
156
- // Check if endpoint already registered
157
- if (this.endpoints.has(endpointKey)) {
158
- throw new Error(`[ApiRegistry] Endpoint '${endpoint.id}' already registered for API '${apiId}'`);
159
- }
160
-
161
- // Register endpoint
162
- this.endpoints.set(endpointKey, { api: apiId, endpoint });
163
-
164
- // Register route if path is defined
165
- if (endpoint.path) {
166
- this.registerRoute(apiId, endpoint);
167
- }
168
- }
169
-
170
- /**
171
- * Unregister a single endpoint
172
- *
173
- * @param apiId - API identifier
174
- * @param endpointId - Endpoint identifier
175
- */
176
- private unregisterEndpoint(apiId: string, endpointId: string): void {
177
- const endpointKey = `${apiId}:${endpointId}`;
178
- const entry = this.endpoints.get(endpointKey);
179
-
180
- if (!entry) {
181
- return; // Already unregistered
182
- }
183
-
184
- // Unregister route
185
- if (entry.endpoint.path) {
186
- const routeKey = this.getRouteKey(entry.endpoint);
187
- this.routes.delete(routeKey);
188
- }
189
-
190
- // Unregister endpoint
191
- this.endpoints.delete(endpointKey);
192
- }
193
-
194
- /**
195
- * Register a route with conflict detection
196
- *
197
- * @param apiId - API identifier
198
- * @param endpoint - Endpoint registration
199
- * @throws Error if route conflict detected (based on strategy)
200
- */
201
- private registerRoute(apiId: string, endpoint: ApiEndpointRegistration): void {
202
- const routeKey = this.getRouteKey(endpoint);
203
- const priority = endpoint.priority ?? 100;
204
- const existingRoute = this.routes.get(routeKey);
205
-
206
- if (existingRoute) {
207
- // Route conflict detected
208
- this.handleRouteConflict(routeKey, apiId, endpoint, existingRoute, priority);
209
- return;
210
- }
211
-
212
- // Register route
213
- this.routes.set(routeKey, {
214
- api: apiId,
215
- endpointId: endpoint.id,
216
- priority,
217
- });
218
- }
219
-
220
- /**
221
- * Handle route conflict based on resolution strategy
222
- *
223
- * @param routeKey - Route key
224
- * @param apiId - New API identifier
225
- * @param endpoint - New endpoint
226
- * @param existingRoute - Existing route registration
227
- * @param newPriority - New endpoint priority
228
- * @throws Error if strategy is 'error'
229
- */
230
- private handleRouteConflict(
231
- routeKey: string,
232
- apiId: string,
233
- endpoint: ApiEndpointRegistration,
234
- existingRoute: { api: string; endpointId: string; priority: number },
235
- newPriority: number
236
- ): void {
237
- const strategy = this.conflictResolution;
238
-
239
- switch (strategy) {
240
- case 'error':
241
- throw new Error(
242
- `[ApiRegistry] Route conflict detected: '${routeKey}' is already registered by API '${existingRoute.api}' endpoint '${existingRoute.endpointId}'`
243
- );
244
-
245
- case 'priority':
246
- if (newPriority > existingRoute.priority) {
247
- // New endpoint has higher priority, replace
248
- this.logger.warn(
249
- `Route conflict: replacing '${routeKey}' (priority ${existingRoute.priority} -> ${newPriority})`,
250
- {
251
- oldApi: existingRoute.api,
252
- oldEndpoint: existingRoute.endpointId,
253
- newApi: apiId,
254
- newEndpoint: endpoint.id,
255
- }
256
- );
257
- this.routes.set(routeKey, {
258
- api: apiId,
259
- endpointId: endpoint.id,
260
- priority: newPriority,
261
- });
262
- } else {
263
- // Existing endpoint has higher priority, keep it
264
- this.logger.warn(
265
- `Route conflict: keeping existing '${routeKey}' (priority ${existingRoute.priority} >= ${newPriority})`,
266
- {
267
- existingApi: existingRoute.api,
268
- existingEndpoint: existingRoute.endpointId,
269
- newApi: apiId,
270
- newEndpoint: endpoint.id,
271
- }
272
- );
273
- }
274
- break;
275
-
276
- case 'first-wins':
277
- // Keep existing route
278
- this.logger.warn(
279
- `Route conflict: keeping first registered '${routeKey}'`,
280
- {
281
- existingApi: existingRoute.api,
282
- newApi: apiId,
283
- }
284
- );
285
- break;
286
-
287
- case 'last-wins':
288
- // Replace with new route
289
- this.logger.warn(
290
- `Route conflict: replacing with last registered '${routeKey}'`,
291
- {
292
- oldApi: existingRoute.api,
293
- newApi: apiId,
294
- }
295
- );
296
- this.routes.set(routeKey, {
297
- api: apiId,
298
- endpointId: endpoint.id,
299
- priority: newPriority,
300
- });
301
- break;
302
-
303
- default:
304
- throw new Error(`[ApiRegistry] Unknown conflict resolution strategy: ${strategy}`);
305
- }
306
- }
307
-
308
- /**
309
- * Generate a unique route key for conflict detection
310
- *
311
- * NOTE: This implementation uses exact string matching for route conflict detection.
312
- * It works well for static paths but has limitations with parameterized routes.
313
- * For example, `/api/users/:id` and `/api/users/:userId` will NOT be detected as conflicts
314
- * even though they are semantically identical parameterized patterns. Similarly,
315
- * `/api/:resource/list` and `/api/:entity/list` would also not be detected as conflicting.
316
- *
317
- * For more advanced conflict detection (e.g., path-to-regexp pattern matching),
318
- * consider integrating with your routing library's conflict detection mechanism.
319
- *
320
- * @param endpoint - Endpoint registration
321
- * @returns Route key (e.g., "GET:/api/v1/customers/:id")
322
- */
323
- private getRouteKey(endpoint: ApiEndpointRegistration): string {
324
- const method = endpoint.method || 'ANY';
325
- return `${method}:${endpoint.path}`;
326
- }
327
-
328
- /**
329
- * Validate endpoint registration
330
- *
331
- * @param endpoint - Endpoint to validate
332
- * @param apiId - API identifier (for error messages)
333
- * @throws Error if endpoint is invalid
334
- */
335
- private validateEndpoint(endpoint: ApiEndpointRegistration, apiId: string): void {
336
- if (!endpoint.id) {
337
- throw new Error(`[ApiRegistry] Endpoint in API '${apiId}' missing 'id' field`);
338
- }
339
-
340
- if (!endpoint.path) {
341
- throw new Error(`[ApiRegistry] Endpoint '${endpoint.id}' in API '${apiId}' missing 'path' field`);
342
- }
343
- }
344
-
345
- /**
346
- * Get an API by ID
347
- *
348
- * @param apiId - API identifier
349
- * @returns API registry entry or undefined
350
- */
351
- getApi(apiId: string): ApiRegistryEntry | undefined {
352
- return this.apis.get(apiId);
353
- }
354
-
355
- /**
356
- * Get all registered APIs
357
- *
358
- * @returns Array of all APIs
359
- */
360
- getAllApis(): ApiRegistryEntry[] {
361
- return Array.from(this.apis.values());
362
- }
363
-
364
- /**
365
- * Find APIs matching query criteria
366
- *
367
- * Performance optimized with auxiliary indices for O(1) lookups on type, tags, and status.
368
- *
369
- * @param query - Discovery query parameters
370
- * @returns Matching APIs
371
- */
372
- findApis(query: ApiDiscoveryQuery): ApiDiscoveryResponse {
373
- let resultIds: Set<string> | undefined;
374
-
375
- // Use indices for performance-optimized filtering
376
- // Start with the most restrictive filter to minimize subsequent filtering
377
-
378
- // Filter by type (using index for O(1) lookup)
379
- if (query.type) {
380
- const typeIds = this.apisByType.get(query.type);
381
- if (!typeIds || typeIds.size === 0) {
382
- return { apis: [], total: 0, filters: query };
383
- }
384
- resultIds = new Set(typeIds);
385
- }
386
-
387
- // Filter by status (using index for O(1) lookup)
388
- if (query.status) {
389
- const statusIds = this.apisByStatus.get(query.status);
390
- if (!statusIds || statusIds.size === 0) {
391
- return { apis: [], total: 0, filters: query };
392
- }
393
-
394
- if (resultIds) {
395
- // Intersect with previous results
396
- resultIds = new Set([...resultIds].filter(id => statusIds.has(id)));
397
- } else {
398
- resultIds = new Set(statusIds);
399
- }
400
-
401
- if (resultIds.size === 0) {
402
- return { apis: [], total: 0, filters: query };
403
- }
404
- }
405
-
406
- // Filter by tags (using index for O(M) lookup where M is number of tags)
407
- if (query.tags && query.tags.length > 0) {
408
- const tagMatches = new Set<string>();
409
-
410
- for (const tag of query.tags) {
411
- const tagIds = this.apisByTag.get(tag);
412
- if (tagIds) {
413
- tagIds.forEach(id => tagMatches.add(id));
414
- }
415
- }
416
-
417
- if (tagMatches.size === 0) {
418
- return { apis: [], total: 0, filters: query };
419
- }
420
-
421
- if (resultIds) {
422
- // Intersect with previous results
423
- resultIds = new Set([...resultIds].filter(id => tagMatches.has(id)));
424
- } else {
425
- resultIds = tagMatches;
426
- }
427
-
428
- if (resultIds.size === 0) {
429
- return { apis: [], total: 0, filters: query };
430
- }
431
- }
432
-
433
- // Get the actual API objects
434
- let results: ApiRegistryEntry[];
435
- if (resultIds) {
436
- results = Array.from(resultIds)
437
- .map(id => this.apis.get(id))
438
- .filter((api): api is ApiRegistryEntry => api !== undefined);
439
- } else {
440
- results = Array.from(this.apis.values());
441
- }
442
-
443
- // Apply remaining filters that don't have indices (less common filters)
444
-
445
- // Filter by plugin source
446
- if (query.pluginSource) {
447
- results = results.filter(
448
- (api) => api.metadata?.pluginSource === query.pluginSource
449
- );
450
- }
451
-
452
- // Filter by version
453
- if (query.version) {
454
- results = results.filter((api) => api.version === query.version);
455
- }
456
-
457
- // Search in name/description
458
- if (query.search) {
459
- const searchLower = query.search.toLowerCase();
460
- results = results.filter(
461
- (api) =>
462
- api.name.toLowerCase().includes(searchLower) ||
463
- (api.description && api.description.toLowerCase().includes(searchLower))
464
- );
465
- }
466
-
467
- return {
468
- apis: results,
469
- total: results.length,
470
- filters: query,
471
- };
472
- }
473
-
474
- /**
475
- * Get endpoint by API ID and endpoint ID
476
- *
477
- * @param apiId - API identifier
478
- * @param endpointId - Endpoint identifier
479
- * @returns Endpoint registration or undefined
480
- */
481
- getEndpoint(apiId: string, endpointId: string): ApiEndpointRegistration | undefined {
482
- const key = `${apiId}:${endpointId}`;
483
- return this.endpoints.get(key)?.endpoint;
484
- }
485
-
486
- /**
487
- * Find endpoint by route (method + path)
488
- *
489
- * @param method - HTTP method
490
- * @param path - URL path
491
- * @returns Endpoint registration or undefined
492
- */
493
- findEndpointByRoute(method: string, path: string): {
494
- api: ApiRegistryEntry;
495
- endpoint: ApiEndpointRegistration;
496
- } | undefined {
497
- const routeKey = `${method}:${path}`;
498
- const route = this.routes.get(routeKey);
499
-
500
- if (!route) {
501
- return undefined;
502
- }
503
-
504
- const api = this.apis.get(route.api);
505
- const endpoint = this.getEndpoint(route.api, route.endpointId);
506
-
507
- if (!api || !endpoint) {
508
- return undefined;
509
- }
510
-
511
- return { api, endpoint };
512
- }
513
-
514
- /**
515
- * Get complete registry snapshot
516
- *
517
- * @returns Current registry state
518
- */
519
- getRegistry(): ApiRegistryType {
520
- const apis = Array.from(this.apis.values());
521
-
522
- // Group by type
523
- const byType: Record<string, ApiRegistryEntry[]> = {};
524
- for (const api of apis) {
525
- if (!byType[api.type]) {
526
- byType[api.type] = [];
527
- }
528
- byType[api.type].push(api);
529
- }
530
-
531
- // Group by status
532
- const byStatus: Record<string, ApiRegistryEntry[]> = {};
533
- for (const api of apis) {
534
- const status = api.metadata?.status || 'active';
535
- if (!byStatus[status]) {
536
- byStatus[status] = [];
537
- }
538
- byStatus[status].push(api);
539
- }
540
-
541
- // Count total endpoints
542
- const totalEndpoints = apis.reduce(
543
- (sum, api) => sum + api.endpoints.length,
544
- 0
545
- );
546
-
547
- return {
548
- version: this.version,
549
- conflictResolution: this.conflictResolution,
550
- apis,
551
- totalApis: apis.length,
552
- totalEndpoints,
553
- byType,
554
- byStatus,
555
- updatedAt: this.updatedAt,
556
- };
557
- }
558
-
559
- /**
560
- * Clear all registered APIs
561
- *
562
- * **⚠️ SAFETY WARNING:**
563
- * This method clears all registered APIs and should be used with caution.
564
- *
565
- * **Usage Restrictions:**
566
- * - In production environments (NODE_ENV=production), a `force: true` parameter is required
567
- * - Primarily intended for testing and development hot-reload scenarios
568
- *
569
- * @param options - Clear options
570
- * @param options.force - Force clear in production environment (default: false)
571
- * @throws Error if called in production without force flag
572
- *
573
- * @example Safe usage in tests
574
- * ```typescript
575
- * beforeEach(() => {
576
- * registry.clear(); // OK in test environment
577
- * });
578
- * ```
579
- *
580
- * @example Usage in production (requires explicit force)
581
- * ```typescript
582
- * // In production, explicit force is required
583
- * registry.clear({ force: true });
584
- * ```
585
- */
586
- clear(options: { force?: boolean } = {}): void {
587
- const isProduction = this.isProductionEnvironment();
588
-
589
- if (isProduction && !options.force) {
590
- throw new Error(
591
- '[ApiRegistry] Cannot clear registry in production environment without force flag. ' +
592
- 'Use clear({ force: true }) if you really want to clear the registry.'
593
- );
594
- }
595
-
596
- this.apis.clear();
597
- this.endpoints.clear();
598
- this.routes.clear();
599
-
600
- // Clear auxiliary indices
601
- this.apisByType.clear();
602
- this.apisByTag.clear();
603
- this.apisByStatus.clear();
604
-
605
- this.updatedAt = new Date().toISOString();
606
-
607
- if (isProduction) {
608
- this.logger.warn('API registry forcefully cleared in production', { force: options.force });
609
- } else {
610
- this.logger.info('API registry cleared');
611
- }
612
- }
613
-
614
- /**
615
- * Get registry statistics
616
- *
617
- * @returns Registry statistics
618
- */
619
- getStats(): {
620
- totalApis: number;
621
- totalEndpoints: number;
622
- totalRoutes: number;
623
- apisByType: Record<string, number>;
624
- endpointsByApi: Record<string, number>;
625
- } {
626
- const apis = Array.from(this.apis.values());
627
-
628
- const apisByType: Record<string, number> = {};
629
- for (const api of apis) {
630
- apisByType[api.type] = (apisByType[api.type] || 0) + 1;
631
- }
632
-
633
- const endpointsByApi: Record<string, number> = {};
634
- for (const api of apis) {
635
- endpointsByApi[api.id] = api.endpoints.length;
636
- }
637
-
638
- return {
639
- totalApis: this.apis.size,
640
- totalEndpoints: this.endpoints.size,
641
- totalRoutes: this.routes.size,
642
- apisByType,
643
- endpointsByApi,
644
- };
645
- }
646
-
647
- /**
648
- * Update auxiliary indices when an API is registered
649
- *
650
- * @param api - API entry to index
651
- * @private
652
- * @internal
653
- */
654
- private updateIndices(api: ApiRegistryEntry): void {
655
- // Index by type
656
- this.ensureIndexSet(this.apisByType, api.type).add(api.id);
657
-
658
- // Index by status
659
- const status = api.metadata?.status || 'active';
660
- this.ensureIndexSet(this.apisByStatus, status).add(api.id);
661
-
662
- // Index by tags
663
- const tags = api.metadata?.tags || [];
664
- for (const tag of tags) {
665
- this.ensureIndexSet(this.apisByTag, tag).add(api.id);
666
- }
667
- }
668
-
669
- /**
670
- * Remove API from auxiliary indices when unregistered
671
- *
672
- * @param api - API entry to remove from indices
673
- * @private
674
- * @internal
675
- */
676
- private removeFromIndices(api: ApiRegistryEntry): void {
677
- // Remove from type index
678
- this.removeFromIndexSet(this.apisByType, api.type, api.id);
679
-
680
- // Remove from status index
681
- const status = api.metadata?.status || 'active';
682
- this.removeFromIndexSet(this.apisByStatus, status, api.id);
683
-
684
- // Remove from tag indices
685
- const tags = api.metadata?.tags || [];
686
- for (const tag of tags) {
687
- this.removeFromIndexSet(this.apisByTag, tag, api.id);
688
- }
689
- }
690
-
691
- /**
692
- * Helper to ensure an index set exists and return it
693
- *
694
- * @param map - Index map
695
- * @param key - Index key
696
- * @returns The Set for this key (created if needed)
697
- * @private
698
- * @internal
699
- */
700
- private ensureIndexSet(map: Map<string, Set<string>>, key: string): Set<string> {
701
- let set = map.get(key);
702
- if (!set) {
703
- set = new Set();
704
- map.set(key, set);
705
- }
706
- return set;
707
- }
708
-
709
- /**
710
- * Helper to remove an ID from an index set and clean up empty sets
711
- *
712
- * @param map - Index map
713
- * @param key - Index key
714
- * @param id - API ID to remove
715
- * @private
716
- * @internal
717
- */
718
- private removeFromIndexSet(map: Map<string, Set<string>>, key: string, id: string): void {
719
- const set = map.get(key);
720
- if (set) {
721
- set.delete(id);
722
- // Clean up empty sets to avoid memory leaks
723
- if (set.size === 0) {
724
- map.delete(key);
725
- }
726
- }
727
- }
728
-
729
- /**
730
- * Check if running in production environment
731
- *
732
- * @returns true if NODE_ENV is 'production'
733
- * @private
734
- * @internal
735
- */
736
- private isProductionEnvironment(): boolean {
737
- return getEnv('NODE_ENV') === 'production';
738
- }
739
- }