@objectstack/core 1.0.2 → 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.
Files changed (125) hide show
  1. package/.turbo/turbo-build.log +58 -0
  2. package/CHANGELOG.md +25 -0
  3. package/dist/index.cjs +4294 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +1777 -0
  6. package/dist/index.d.ts +1776 -21
  7. package/dist/index.js +4246 -23
  8. package/dist/index.js.map +1 -0
  9. package/package.json +4 -4
  10. package/tsconfig.json +1 -3
  11. package/dist/api-registry-plugin.d.ts +0 -54
  12. package/dist/api-registry-plugin.d.ts.map +0 -1
  13. package/dist/api-registry-plugin.js +0 -53
  14. package/dist/api-registry-plugin.test.d.ts +0 -2
  15. package/dist/api-registry-plugin.test.d.ts.map +0 -1
  16. package/dist/api-registry-plugin.test.js +0 -334
  17. package/dist/api-registry.d.ts +0 -259
  18. package/dist/api-registry.d.ts.map +0 -1
  19. package/dist/api-registry.js +0 -600
  20. package/dist/api-registry.test.d.ts +0 -2
  21. package/dist/api-registry.test.d.ts.map +0 -1
  22. package/dist/api-registry.test.js +0 -957
  23. package/dist/contracts/data-engine.d.ts +0 -62
  24. package/dist/contracts/data-engine.d.ts.map +0 -1
  25. package/dist/contracts/data-engine.js +0 -1
  26. package/dist/contracts/http-server.d.ts +0 -119
  27. package/dist/contracts/http-server.d.ts.map +0 -1
  28. package/dist/contracts/http-server.js +0 -11
  29. package/dist/contracts/logger.d.ts +0 -63
  30. package/dist/contracts/logger.d.ts.map +0 -1
  31. package/dist/contracts/logger.js +0 -1
  32. package/dist/dependency-resolver.d.ts +0 -62
  33. package/dist/dependency-resolver.d.ts.map +0 -1
  34. package/dist/dependency-resolver.js +0 -317
  35. package/dist/dependency-resolver.test.d.ts +0 -2
  36. package/dist/dependency-resolver.test.d.ts.map +0 -1
  37. package/dist/dependency-resolver.test.js +0 -241
  38. package/dist/health-monitor.d.ts +0 -65
  39. package/dist/health-monitor.d.ts.map +0 -1
  40. package/dist/health-monitor.js +0 -269
  41. package/dist/health-monitor.test.d.ts +0 -2
  42. package/dist/health-monitor.test.d.ts.map +0 -1
  43. package/dist/health-monitor.test.js +0 -68
  44. package/dist/hot-reload.d.ts +0 -79
  45. package/dist/hot-reload.d.ts.map +0 -1
  46. package/dist/hot-reload.js +0 -313
  47. package/dist/index.d.ts.map +0 -1
  48. package/dist/kernel-base.d.ts +0 -84
  49. package/dist/kernel-base.d.ts.map +0 -1
  50. package/dist/kernel-base.js +0 -219
  51. package/dist/kernel.d.ts +0 -113
  52. package/dist/kernel.d.ts.map +0 -1
  53. package/dist/kernel.js +0 -472
  54. package/dist/kernel.test.d.ts +0 -2
  55. package/dist/kernel.test.d.ts.map +0 -1
  56. package/dist/kernel.test.js +0 -414
  57. package/dist/lite-kernel.d.ts +0 -55
  58. package/dist/lite-kernel.d.ts.map +0 -1
  59. package/dist/lite-kernel.js +0 -112
  60. package/dist/lite-kernel.test.d.ts +0 -2
  61. package/dist/lite-kernel.test.d.ts.map +0 -1
  62. package/dist/lite-kernel.test.js +0 -161
  63. package/dist/logger.d.ts +0 -71
  64. package/dist/logger.d.ts.map +0 -1
  65. package/dist/logger.js +0 -312
  66. package/dist/logger.test.d.ts +0 -2
  67. package/dist/logger.test.d.ts.map +0 -1
  68. package/dist/logger.test.js +0 -92
  69. package/dist/plugin-loader.d.ts +0 -164
  70. package/dist/plugin-loader.d.ts.map +0 -1
  71. package/dist/plugin-loader.js +0 -319
  72. package/dist/plugin-loader.test.d.ts +0 -2
  73. package/dist/plugin-loader.test.d.ts.map +0 -1
  74. package/dist/plugin-loader.test.js +0 -348
  75. package/dist/qa/adapter.d.ts +0 -14
  76. package/dist/qa/adapter.d.ts.map +0 -1
  77. package/dist/qa/adapter.js +0 -1
  78. package/dist/qa/http-adapter.d.ts +0 -16
  79. package/dist/qa/http-adapter.d.ts.map +0 -1
  80. package/dist/qa/http-adapter.js +0 -107
  81. package/dist/qa/index.d.ts +0 -4
  82. package/dist/qa/index.d.ts.map +0 -1
  83. package/dist/qa/index.js +0 -3
  84. package/dist/qa/runner.d.ts +0 -27
  85. package/dist/qa/runner.d.ts.map +0 -1
  86. package/dist/qa/runner.js +0 -157
  87. package/dist/security/index.d.ts +0 -17
  88. package/dist/security/index.d.ts.map +0 -1
  89. package/dist/security/index.js +0 -17
  90. package/dist/security/permission-manager.d.ts +0 -96
  91. package/dist/security/permission-manager.d.ts.map +0 -1
  92. package/dist/security/permission-manager.js +0 -235
  93. package/dist/security/permission-manager.test.d.ts +0 -2
  94. package/dist/security/permission-manager.test.d.ts.map +0 -1
  95. package/dist/security/permission-manager.test.js +0 -220
  96. package/dist/security/plugin-config-validator.d.ts +0 -79
  97. package/dist/security/plugin-config-validator.d.ts.map +0 -1
  98. package/dist/security/plugin-config-validator.js +0 -166
  99. package/dist/security/plugin-config-validator.test.d.ts +0 -2
  100. package/dist/security/plugin-config-validator.test.d.ts.map +0 -1
  101. package/dist/security/plugin-config-validator.test.js +0 -223
  102. package/dist/security/plugin-permission-enforcer.d.ts +0 -154
  103. package/dist/security/plugin-permission-enforcer.d.ts.map +0 -1
  104. package/dist/security/plugin-permission-enforcer.js +0 -323
  105. package/dist/security/plugin-permission-enforcer.test.d.ts +0 -2
  106. package/dist/security/plugin-permission-enforcer.test.d.ts.map +0 -1
  107. package/dist/security/plugin-permission-enforcer.test.js +0 -205
  108. package/dist/security/plugin-signature-verifier.d.ts +0 -96
  109. package/dist/security/plugin-signature-verifier.d.ts.map +0 -1
  110. package/dist/security/plugin-signature-verifier.js +0 -250
  111. package/dist/security/sandbox-runtime.d.ts +0 -115
  112. package/dist/security/sandbox-runtime.d.ts.map +0 -1
  113. package/dist/security/sandbox-runtime.js +0 -311
  114. package/dist/security/security-scanner.d.ts +0 -92
  115. package/dist/security/security-scanner.d.ts.map +0 -1
  116. package/dist/security/security-scanner.js +0 -273
  117. package/dist/types.d.ts +0 -89
  118. package/dist/types.d.ts.map +0 -1
  119. package/dist/types.js +0 -1
  120. package/dist/utils/env.d.ts +0 -20
  121. package/dist/utils/env.d.ts.map +0 -1
  122. package/dist/utils/env.js +0 -46
  123. package/dist/utils/env.test.d.ts +0 -2
  124. package/dist/utils/env.test.d.ts.map +0 -1
  125. package/dist/utils/env.test.js +0 -52
@@ -1,957 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { ApiRegistry } from './api-registry';
3
- // Mock logger
4
- const createMockLogger = () => ({
5
- debug: vi.fn(),
6
- info: vi.fn(),
7
- warn: vi.fn(),
8
- error: vi.fn(),
9
- });
10
- describe('ApiRegistry', () => {
11
- let registry;
12
- let logger;
13
- beforeEach(() => {
14
- logger = createMockLogger();
15
- registry = new ApiRegistry(logger, 'error', '1.0.0');
16
- });
17
- describe('Constructor', () => {
18
- it('should create registry with default conflict resolution', () => {
19
- const reg = new ApiRegistry(logger);
20
- const snapshot = reg.getRegistry();
21
- expect(snapshot.conflictResolution).toBe('error');
22
- expect(snapshot.version).toBe('1.0.0');
23
- });
24
- it('should create registry with custom conflict resolution', () => {
25
- const reg = new ApiRegistry(logger, 'priority', '2.0.0');
26
- const snapshot = reg.getRegistry();
27
- expect(snapshot.conflictResolution).toBe('priority');
28
- expect(snapshot.version).toBe('2.0.0');
29
- });
30
- });
31
- describe('registerApi', () => {
32
- it('should register a simple REST API', () => {
33
- const api = {
34
- id: 'customer_api',
35
- name: 'Customer API',
36
- type: 'rest',
37
- version: 'v1',
38
- basePath: '/api/v1/customers',
39
- endpoints: [
40
- {
41
- id: 'get_customer',
42
- method: 'GET',
43
- path: '/api/v1/customers/:id',
44
- summary: 'Get customer by ID',
45
- responses: [
46
- {
47
- statusCode: 200,
48
- description: 'Success',
49
- },
50
- ],
51
- },
52
- ],
53
- };
54
- registry.registerApi(api);
55
- const retrieved = registry.getApi('customer_api');
56
- expect(retrieved).toBeDefined();
57
- expect(retrieved?.name).toBe('Customer API');
58
- expect(retrieved?.endpoints.length).toBe(1);
59
- });
60
- it('should throw error when registering duplicate API', () => {
61
- const api = {
62
- id: 'test_api',
63
- name: 'Test API',
64
- type: 'rest',
65
- version: 'v1',
66
- basePath: '/api/test',
67
- endpoints: [],
68
- };
69
- registry.registerApi(api);
70
- expect(() => registry.registerApi(api)).toThrow("API 'test_api' already registered");
71
- });
72
- it('should register API with multiple endpoints', () => {
73
- const api = {
74
- id: 'crud_api',
75
- name: 'CRUD API',
76
- type: 'rest',
77
- version: 'v1',
78
- basePath: '/api/v1/data',
79
- endpoints: [
80
- {
81
- id: 'create',
82
- method: 'POST',
83
- path: '/api/v1/data',
84
- summary: 'Create record',
85
- responses: [],
86
- },
87
- {
88
- id: 'read',
89
- method: 'GET',
90
- path: '/api/v1/data/:id',
91
- summary: 'Read record',
92
- responses: [],
93
- },
94
- {
95
- id: 'update',
96
- method: 'PUT',
97
- path: '/api/v1/data/:id',
98
- summary: 'Update record',
99
- responses: [],
100
- },
101
- {
102
- id: 'delete',
103
- method: 'DELETE',
104
- path: '/api/v1/data/:id',
105
- summary: 'Delete record',
106
- responses: [],
107
- },
108
- ],
109
- };
110
- registry.registerApi(api);
111
- const stats = registry.getStats();
112
- expect(stats.totalApis).toBe(1);
113
- expect(stats.totalEndpoints).toBe(4);
114
- expect(stats.totalRoutes).toBe(4);
115
- });
116
- it('should register API with RBAC permissions', () => {
117
- const api = {
118
- id: 'protected_api',
119
- name: 'Protected API',
120
- type: 'rest',
121
- version: 'v1',
122
- basePath: '/api/protected',
123
- endpoints: [
124
- {
125
- id: 'admin_only',
126
- method: 'POST',
127
- path: '/api/protected/admin',
128
- summary: 'Admin endpoint',
129
- requiredPermissions: ['admin.access', 'api_enabled'],
130
- responses: [],
131
- },
132
- ],
133
- };
134
- registry.registerApi(api);
135
- const endpoint = registry.getEndpoint('protected_api', 'admin_only');
136
- expect(endpoint?.requiredPermissions).toEqual(['admin.access', 'api_enabled']);
137
- });
138
- });
139
- describe('unregisterApi', () => {
140
- it('should unregister an API', () => {
141
- const api = {
142
- id: 'temp_api',
143
- name: 'Temporary API',
144
- type: 'rest',
145
- version: 'v1',
146
- basePath: '/api/temp',
147
- endpoints: [
148
- {
149
- id: 'test',
150
- method: 'GET',
151
- path: '/api/temp/test',
152
- responses: [],
153
- },
154
- ],
155
- };
156
- registry.registerApi(api);
157
- expect(registry.getApi('temp_api')).toBeDefined();
158
- registry.unregisterApi('temp_api');
159
- expect(registry.getApi('temp_api')).toBeUndefined();
160
- });
161
- it('should throw error when unregistering non-existent API', () => {
162
- expect(() => registry.unregisterApi('nonexistent')).toThrow("API 'nonexistent' not found");
163
- });
164
- });
165
- describe('Route Conflict Detection', () => {
166
- describe('error strategy', () => {
167
- it('should throw error on route conflict', () => {
168
- const api1 = {
169
- id: 'api1',
170
- name: 'API 1',
171
- type: 'rest',
172
- version: 'v1',
173
- basePath: '/api/v1',
174
- endpoints: [
175
- {
176
- id: 'endpoint1',
177
- method: 'GET',
178
- path: '/api/v1/test',
179
- responses: [],
180
- },
181
- ],
182
- };
183
- const api2 = {
184
- id: 'api2',
185
- name: 'API 2',
186
- type: 'rest',
187
- version: 'v1',
188
- basePath: '/api/v1',
189
- endpoints: [
190
- {
191
- id: 'endpoint2',
192
- method: 'GET',
193
- path: '/api/v1/test', // Same route!
194
- responses: [],
195
- },
196
- ],
197
- };
198
- registry.registerApi(api1);
199
- expect(() => registry.registerApi(api2)).toThrow(/Route conflict detected/);
200
- });
201
- it('should allow same path with different methods', () => {
202
- const api = {
203
- id: 'multi_method',
204
- name: 'Multi Method API',
205
- type: 'rest',
206
- version: 'v1',
207
- basePath: '/api/v1',
208
- endpoints: [
209
- {
210
- id: 'get',
211
- method: 'GET',
212
- path: '/api/v1/resource',
213
- responses: [],
214
- },
215
- {
216
- id: 'post',
217
- method: 'POST',
218
- path: '/api/v1/resource',
219
- responses: [],
220
- },
221
- {
222
- id: 'put',
223
- method: 'PUT',
224
- path: '/api/v1/resource',
225
- responses: [],
226
- },
227
- ],
228
- };
229
- expect(() => registry.registerApi(api)).not.toThrow();
230
- expect(registry.getStats().totalRoutes).toBe(3);
231
- });
232
- });
233
- describe('priority strategy', () => {
234
- beforeEach(() => {
235
- registry = new ApiRegistry(logger, 'priority');
236
- });
237
- it('should prefer higher priority endpoint', () => {
238
- const api1 = {
239
- id: 'low_priority',
240
- name: 'Low Priority API',
241
- type: 'rest',
242
- version: 'v1',
243
- basePath: '/api',
244
- endpoints: [
245
- {
246
- id: 'low',
247
- method: 'GET',
248
- path: '/api/test',
249
- priority: 100,
250
- responses: [],
251
- },
252
- ],
253
- };
254
- const api2 = {
255
- id: 'high_priority',
256
- name: 'High Priority API',
257
- type: 'rest',
258
- version: 'v1',
259
- basePath: '/api',
260
- endpoints: [
261
- {
262
- id: 'high',
263
- method: 'GET',
264
- path: '/api/test',
265
- priority: 500,
266
- responses: [],
267
- },
268
- ],
269
- };
270
- registry.registerApi(api1);
271
- registry.registerApi(api2); // Should replace low priority
272
- const result = registry.findEndpointByRoute('GET', '/api/test');
273
- expect(result?.api.id).toBe('high_priority');
274
- expect(result?.endpoint.id).toBe('high');
275
- });
276
- it('should keep higher priority when registering lower priority', () => {
277
- const api1 = {
278
- id: 'high_priority',
279
- name: 'High Priority API',
280
- type: 'rest',
281
- version: 'v1',
282
- basePath: '/api',
283
- endpoints: [
284
- {
285
- id: 'high',
286
- method: 'GET',
287
- path: '/api/test',
288
- priority: 900,
289
- responses: [],
290
- },
291
- ],
292
- };
293
- const api2 = {
294
- id: 'low_priority',
295
- name: 'Low Priority API',
296
- type: 'rest',
297
- version: 'v1',
298
- basePath: '/api',
299
- endpoints: [
300
- {
301
- id: 'low',
302
- method: 'GET',
303
- path: '/api/test',
304
- priority: 100,
305
- responses: [],
306
- },
307
- ],
308
- };
309
- registry.registerApi(api1);
310
- registry.registerApi(api2); // Should NOT replace
311
- const result = registry.findEndpointByRoute('GET', '/api/test');
312
- expect(result?.api.id).toBe('high_priority');
313
- expect(result?.endpoint.id).toBe('high');
314
- });
315
- });
316
- describe('first-wins strategy', () => {
317
- beforeEach(() => {
318
- registry = new ApiRegistry(logger, 'first-wins');
319
- });
320
- it('should keep first registered endpoint', () => {
321
- const api1 = {
322
- id: 'first',
323
- name: 'First API',
324
- type: 'rest',
325
- version: 'v1',
326
- basePath: '/api',
327
- endpoints: [
328
- {
329
- id: 'first_endpoint',
330
- method: 'GET',
331
- path: '/api/test',
332
- responses: [],
333
- },
334
- ],
335
- };
336
- const api2 = {
337
- id: 'second',
338
- name: 'Second API',
339
- type: 'rest',
340
- version: 'v1',
341
- basePath: '/api',
342
- endpoints: [
343
- {
344
- id: 'second_endpoint',
345
- method: 'GET',
346
- path: '/api/test',
347
- responses: [],
348
- },
349
- ],
350
- };
351
- registry.registerApi(api1);
352
- registry.registerApi(api2);
353
- const result = registry.findEndpointByRoute('GET', '/api/test');
354
- expect(result?.api.id).toBe('first');
355
- expect(result?.endpoint.id).toBe('first_endpoint');
356
- });
357
- });
358
- describe('last-wins strategy', () => {
359
- beforeEach(() => {
360
- registry = new ApiRegistry(logger, 'last-wins');
361
- });
362
- it('should use last registered endpoint', () => {
363
- const api1 = {
364
- id: 'first',
365
- name: 'First API',
366
- type: 'rest',
367
- version: 'v1',
368
- basePath: '/api',
369
- endpoints: [
370
- {
371
- id: 'first_endpoint',
372
- method: 'GET',
373
- path: '/api/test',
374
- responses: [],
375
- },
376
- ],
377
- };
378
- const api2 = {
379
- id: 'second',
380
- name: 'Second API',
381
- type: 'rest',
382
- version: 'v1',
383
- basePath: '/api',
384
- endpoints: [
385
- {
386
- id: 'second_endpoint',
387
- method: 'GET',
388
- path: '/api/test',
389
- responses: [],
390
- },
391
- ],
392
- };
393
- registry.registerApi(api1);
394
- registry.registerApi(api2);
395
- const result = registry.findEndpointByRoute('GET', '/api/test');
396
- expect(result?.api.id).toBe('second');
397
- expect(result?.endpoint.id).toBe('second_endpoint');
398
- });
399
- });
400
- });
401
- describe('findApis', () => {
402
- beforeEach(() => {
403
- // Register multiple APIs for testing
404
- registry.registerApi({
405
- id: 'rest_api',
406
- name: 'REST API',
407
- type: 'rest',
408
- version: 'v1',
409
- basePath: '/api/v1/rest',
410
- endpoints: [],
411
- metadata: {
412
- status: 'active',
413
- tags: ['data', 'crud'],
414
- },
415
- });
416
- registry.registerApi({
417
- id: 'graphql_api',
418
- name: 'GraphQL API',
419
- type: 'graphql',
420
- version: 'v1',
421
- basePath: '/graphql',
422
- endpoints: [],
423
- metadata: {
424
- status: 'active',
425
- tags: ['query', 'data'],
426
- },
427
- });
428
- registry.registerApi({
429
- id: 'deprecated_api',
430
- name: 'Deprecated API',
431
- type: 'rest',
432
- version: 'v0',
433
- basePath: '/api/v0/old',
434
- endpoints: [],
435
- metadata: {
436
- status: 'deprecated',
437
- tags: ['legacy'],
438
- },
439
- });
440
- });
441
- it('should find all APIs with empty query', () => {
442
- const result = registry.findApis({});
443
- expect(result.total).toBe(3);
444
- expect(result.apis.length).toBe(3);
445
- });
446
- it('should filter by type', () => {
447
- const result = registry.findApis({ type: 'rest' });
448
- expect(result.total).toBe(2);
449
- expect(result.apis.every((api) => api.type === 'rest')).toBe(true);
450
- });
451
- it('should filter by status', () => {
452
- const result = registry.findApis({ status: 'active' });
453
- expect(result.total).toBe(2);
454
- expect(result.apis.every((api) => api.metadata?.status === 'active')).toBe(true);
455
- });
456
- it('should filter by version', () => {
457
- const result = registry.findApis({ version: 'v1' });
458
- expect(result.total).toBe(2);
459
- expect(result.apis.every((api) => api.version === 'v1')).toBe(true);
460
- });
461
- it('should filter by tags (ANY match)', () => {
462
- const result = registry.findApis({ tags: ['data'] });
463
- expect(result.total).toBe(2);
464
- });
465
- it('should search in name and description', () => {
466
- const result = registry.findApis({ search: 'graphql' });
467
- expect(result.total).toBe(1);
468
- expect(result.apis[0].id).toBe('graphql_api');
469
- });
470
- it('should combine multiple filters', () => {
471
- const result = registry.findApis({
472
- type: 'rest',
473
- status: 'active',
474
- tags: ['crud'],
475
- });
476
- expect(result.total).toBe(1);
477
- expect(result.apis[0].id).toBe('rest_api');
478
- });
479
- });
480
- describe('getEndpoint', () => {
481
- it('should get endpoint by API and endpoint ID', () => {
482
- const api = {
483
- id: 'test_api',
484
- name: 'Test API',
485
- type: 'rest',
486
- version: 'v1',
487
- basePath: '/api/test',
488
- endpoints: [
489
- {
490
- id: 'test_endpoint',
491
- method: 'GET',
492
- path: '/api/test/hello',
493
- summary: 'Test endpoint',
494
- responses: [],
495
- },
496
- ],
497
- };
498
- registry.registerApi(api);
499
- const endpoint = registry.getEndpoint('test_api', 'test_endpoint');
500
- expect(endpoint).toBeDefined();
501
- expect(endpoint?.summary).toBe('Test endpoint');
502
- });
503
- it('should return undefined for non-existent endpoint', () => {
504
- const endpoint = registry.getEndpoint('nonexistent', 'also_nonexistent');
505
- expect(endpoint).toBeUndefined();
506
- });
507
- });
508
- describe('findEndpointByRoute', () => {
509
- it('should find endpoint by method and path', () => {
510
- const api = {
511
- id: 'route_api',
512
- name: 'Route API',
513
- type: 'rest',
514
- version: 'v1',
515
- basePath: '/api',
516
- endpoints: [
517
- {
518
- id: 'get_users',
519
- method: 'GET',
520
- path: '/api/users',
521
- responses: [],
522
- },
523
- ],
524
- };
525
- registry.registerApi(api);
526
- const result = registry.findEndpointByRoute('GET', '/api/users');
527
- expect(result).toBeDefined();
528
- expect(result?.api.id).toBe('route_api');
529
- expect(result?.endpoint.id).toBe('get_users');
530
- });
531
- it('should return undefined for non-existent route', () => {
532
- const result = registry.findEndpointByRoute('POST', '/nonexistent');
533
- expect(result).toBeUndefined();
534
- });
535
- });
536
- describe('getRegistry', () => {
537
- it('should return complete registry snapshot', () => {
538
- registry.registerApi({
539
- id: 'api1',
540
- name: 'API 1',
541
- type: 'rest',
542
- version: 'v1',
543
- basePath: '/api/v1',
544
- endpoints: [
545
- { id: 'e1', path: '/api/v1/test', responses: [] },
546
- ],
547
- });
548
- const snapshot = registry.getRegistry();
549
- expect(snapshot.version).toBe('1.0.0');
550
- expect(snapshot.conflictResolution).toBe('error');
551
- expect(snapshot.totalApis).toBe(1);
552
- expect(snapshot.totalEndpoints).toBe(1);
553
- expect(snapshot.byType).toBeDefined();
554
- expect(snapshot.byStatus).toBeDefined();
555
- expect(snapshot.updatedAt).toBeDefined();
556
- });
557
- it('should group APIs by type', () => {
558
- registry.registerApi({
559
- id: 'rest1',
560
- name: 'REST 1',
561
- type: 'rest',
562
- version: 'v1',
563
- basePath: '/api/rest1',
564
- endpoints: [],
565
- });
566
- registry.registerApi({
567
- id: 'rest2',
568
- name: 'REST 2',
569
- type: 'rest',
570
- version: 'v1',
571
- basePath: '/api/rest2',
572
- endpoints: [],
573
- });
574
- registry.registerApi({
575
- id: 'graphql1',
576
- name: 'GraphQL 1',
577
- type: 'graphql',
578
- version: 'v1',
579
- basePath: '/graphql',
580
- endpoints: [],
581
- });
582
- const snapshot = registry.getRegistry();
583
- expect(snapshot.byType?.rest?.length).toBe(2);
584
- expect(snapshot.byType?.graphql?.length).toBe(1);
585
- });
586
- });
587
- describe('clear', () => {
588
- it('should clear all registered APIs', () => {
589
- registry.registerApi({
590
- id: 'test',
591
- name: 'Test',
592
- type: 'rest',
593
- version: 'v1',
594
- basePath: '/test',
595
- endpoints: [{ id: 'e1', path: '/test', responses: [] }],
596
- });
597
- expect(registry.getStats().totalApis).toBe(1);
598
- registry.clear();
599
- expect(registry.getStats().totalApis).toBe(0);
600
- expect(registry.getStats().totalEndpoints).toBe(0);
601
- expect(registry.getStats().totalRoutes).toBe(0);
602
- });
603
- });
604
- describe('getStats', () => {
605
- it('should return accurate statistics', () => {
606
- registry.registerApi({
607
- id: 'api1',
608
- name: 'API 1',
609
- type: 'rest',
610
- version: 'v1',
611
- basePath: '/api1',
612
- endpoints: [
613
- { id: 'e1', path: '/api1/e1', responses: [] },
614
- { id: 'e2', path: '/api1/e2', responses: [] },
615
- ],
616
- });
617
- registry.registerApi({
618
- id: 'api2',
619
- name: 'API 2',
620
- type: 'graphql',
621
- version: 'v1',
622
- basePath: '/graphql',
623
- endpoints: [
624
- { id: 'query', path: '/graphql', responses: [] },
625
- ],
626
- });
627
- const stats = registry.getStats();
628
- expect(stats.totalApis).toBe(2);
629
- expect(stats.totalEndpoints).toBe(3);
630
- expect(stats.totalRoutes).toBe(3);
631
- expect(stats.apisByType.rest).toBe(1);
632
- expect(stats.apisByType.graphql).toBe(1);
633
- expect(stats.endpointsByApi.api1).toBe(2);
634
- expect(stats.endpointsByApi.api2).toBe(1);
635
- });
636
- });
637
- describe('Multi-protocol Support', () => {
638
- it('should register GraphQL API', () => {
639
- const api = {
640
- id: 'graphql',
641
- name: 'GraphQL API',
642
- type: 'graphql',
643
- version: 'v1',
644
- basePath: '/graphql',
645
- endpoints: [
646
- {
647
- id: 'query',
648
- path: '/graphql',
649
- summary: 'GraphQL Query',
650
- responses: [],
651
- },
652
- ],
653
- };
654
- registry.registerApi(api);
655
- expect(registry.getApi('graphql')?.type).toBe('graphql');
656
- });
657
- it('should register WebSocket API', () => {
658
- const api = {
659
- id: 'websocket',
660
- name: 'WebSocket API',
661
- type: 'websocket',
662
- version: 'v1',
663
- basePath: '/ws',
664
- endpoints: [
665
- {
666
- id: 'subscribe',
667
- path: '/ws/events',
668
- summary: 'Subscribe to events',
669
- protocolConfig: {
670
- subProtocol: 'websocket',
671
- eventName: 'data.updated',
672
- direction: 'server-to-client',
673
- },
674
- responses: [],
675
- },
676
- ],
677
- };
678
- registry.registerApi(api);
679
- const endpoint = registry.getEndpoint('websocket', 'subscribe');
680
- expect(endpoint?.protocolConfig?.subProtocol).toBe('websocket');
681
- });
682
- it('should register Plugin API', () => {
683
- const api = {
684
- id: 'custom_plugin',
685
- name: 'Custom Plugin API',
686
- type: 'plugin',
687
- version: '1.0.0',
688
- basePath: '/plugins/custom',
689
- endpoints: [
690
- {
691
- id: 'custom_action',
692
- method: 'POST',
693
- path: '/plugins/custom/action',
694
- summary: 'Custom plugin action',
695
- responses: [],
696
- },
697
- ],
698
- metadata: {
699
- pluginSource: 'custom_plugin_package',
700
- status: 'active',
701
- },
702
- };
703
- registry.registerApi(api);
704
- const result = registry.findApis({ pluginSource: 'custom_plugin_package' });
705
- expect(result.total).toBe(1);
706
- });
707
- });
708
- describe('Performance Optimizations', () => {
709
- it('should use indices for fast type-based lookups', () => {
710
- // Register multiple APIs with different types
711
- registry.registerApi({
712
- id: 'rest_api_1',
713
- name: 'REST API 1',
714
- type: 'rest',
715
- version: 'v1',
716
- basePath: '/api/rest1',
717
- endpoints: [{ id: 'e1', path: '/api/rest1', responses: [] }],
718
- });
719
- registry.registerApi({
720
- id: 'rest_api_2',
721
- name: 'REST API 2',
722
- type: 'rest',
723
- version: 'v1',
724
- basePath: '/api/rest2',
725
- endpoints: [{ id: 'e2', path: '/api/rest2', responses: [] }],
726
- });
727
- registry.registerApi({
728
- id: 'graphql_api',
729
- name: 'GraphQL API',
730
- type: 'graphql',
731
- version: 'v1',
732
- basePath: '/graphql',
733
- endpoints: [{ id: 'e3', path: '/graphql', responses: [] }],
734
- });
735
- // Should efficiently find all REST APIs
736
- const restApis = registry.findApis({ type: 'rest' });
737
- expect(restApis.total).toBe(2);
738
- expect(restApis.apis.every(api => api.type === 'rest')).toBe(true);
739
- // Should efficiently find GraphQL APIs
740
- const graphqlApis = registry.findApis({ type: 'graphql' });
741
- expect(graphqlApis.total).toBe(1);
742
- expect(graphqlApis.apis[0].id).toBe('graphql_api');
743
- });
744
- it('should use indices for fast tag-based lookups', () => {
745
- registry.registerApi({
746
- id: 'api_1',
747
- name: 'API 1',
748
- type: 'rest',
749
- version: 'v1',
750
- basePath: '/api1',
751
- endpoints: [{ id: 'e1', path: '/api1', responses: [] }],
752
- metadata: { tags: ['customer', 'crm'] },
753
- });
754
- registry.registerApi({
755
- id: 'api_2',
756
- name: 'API 2',
757
- type: 'rest',
758
- version: 'v1',
759
- basePath: '/api2',
760
- endpoints: [{ id: 'e2', path: '/api2', responses: [] }],
761
- metadata: { tags: ['order', 'sales'] },
762
- });
763
- registry.registerApi({
764
- id: 'api_3',
765
- name: 'API 3',
766
- type: 'rest',
767
- version: 'v1',
768
- basePath: '/api3',
769
- endpoints: [{ id: 'e3', path: '/api3', responses: [] }],
770
- metadata: { tags: ['customer', 'analytics'] },
771
- });
772
- // Should efficiently find APIs by tag
773
- const customerApis = registry.findApis({ tags: ['customer'] });
774
- expect(customerApis.total).toBe(2);
775
- expect(customerApis.apis.map(a => a.id).sort()).toEqual(['api_1', 'api_3']);
776
- // Should support multiple tags (ANY match)
777
- const multiTagApis = registry.findApis({ tags: ['crm', 'sales'] });
778
- expect(multiTagApis.total).toBe(2);
779
- });
780
- it('should use indices for fast status-based lookups', () => {
781
- registry.registerApi({
782
- id: 'active_api',
783
- name: 'Active API',
784
- type: 'rest',
785
- version: 'v1',
786
- basePath: '/active',
787
- endpoints: [{ id: 'e1', path: '/active', responses: [] }],
788
- metadata: { status: 'active' },
789
- });
790
- registry.registerApi({
791
- id: 'beta_api',
792
- name: 'Beta API',
793
- type: 'rest',
794
- version: 'v1',
795
- basePath: '/beta',
796
- endpoints: [{ id: 'e2', path: '/beta', responses: [] }],
797
- metadata: { status: 'beta' },
798
- });
799
- registry.registerApi({
800
- id: 'deprecated_api',
801
- name: 'Deprecated API',
802
- type: 'rest',
803
- version: 'v1',
804
- basePath: '/deprecated',
805
- endpoints: [{ id: 'e3', path: '/deprecated', responses: [] }],
806
- metadata: { status: 'deprecated' },
807
- });
808
- // Should efficiently find by status
809
- const activeApis = registry.findApis({ status: 'active' });
810
- expect(activeApis.total).toBe(1);
811
- expect(activeApis.apis[0].id).toBe('active_api');
812
- const betaApis = registry.findApis({ status: 'beta' });
813
- expect(betaApis.total).toBe(1);
814
- });
815
- it('should combine multiple indexed filters efficiently', () => {
816
- registry.registerApi({
817
- id: 'rest_crm_active',
818
- name: 'REST CRM Active',
819
- type: 'rest',
820
- version: 'v1',
821
- basePath: '/crm',
822
- endpoints: [{ id: 'e1', path: '/crm', responses: [] }],
823
- metadata: { status: 'active', tags: ['crm', 'customer'] },
824
- });
825
- registry.registerApi({
826
- id: 'rest_crm_beta',
827
- name: 'REST CRM Beta',
828
- type: 'rest',
829
- version: 'v1',
830
- basePath: '/crm-beta',
831
- endpoints: [{ id: 'e2', path: '/crm-beta', responses: [] }],
832
- metadata: { status: 'beta', tags: ['crm'] },
833
- });
834
- registry.registerApi({
835
- id: 'graphql_crm_active',
836
- name: 'GraphQL CRM Active',
837
- type: 'graphql',
838
- version: 'v1',
839
- basePath: '/graphql',
840
- endpoints: [{ id: 'e3', path: '/graphql', responses: [] }],
841
- metadata: { status: 'active', tags: ['crm'] },
842
- });
843
- // Combine type + status + tags filters
844
- const result = registry.findApis({
845
- type: 'rest',
846
- status: 'active',
847
- tags: ['crm'],
848
- });
849
- expect(result.total).toBe(1);
850
- expect(result.apis[0].id).toBe('rest_crm_active');
851
- });
852
- it('should maintain indices when APIs are unregistered', () => {
853
- registry.registerApi({
854
- id: 'temp_api',
855
- name: 'Temporary API',
856
- type: 'rest',
857
- version: 'v1',
858
- basePath: '/temp',
859
- endpoints: [{ id: 'e1', path: '/temp', responses: [] }],
860
- metadata: { status: 'beta', tags: ['temp', 'test'] },
861
- });
862
- // Verify it's in indices
863
- expect(registry.findApis({ type: 'rest' }).total).toBe(1);
864
- expect(registry.findApis({ status: 'beta' }).total).toBe(1);
865
- expect(registry.findApis({ tags: ['temp'] }).total).toBe(1);
866
- // Unregister
867
- registry.unregisterApi('temp_api');
868
- // Verify removed from indices
869
- expect(registry.findApis({ type: 'rest' }).total).toBe(0);
870
- expect(registry.findApis({ status: 'beta' }).total).toBe(0);
871
- expect(registry.findApis({ tags: ['temp'] }).total).toBe(0);
872
- });
873
- });
874
- describe('Safety Guards', () => {
875
- it('should allow clear() in non-production environment', () => {
876
- const originalEnv = process.env.NODE_ENV;
877
- try {
878
- process.env.NODE_ENV = 'test';
879
- registry.registerApi({
880
- id: 'test_api',
881
- name: 'Test API',
882
- type: 'rest',
883
- version: 'v1',
884
- basePath: '/test',
885
- endpoints: [{ id: 'e1', path: '/test', responses: [] }],
886
- });
887
- expect(registry.getStats().totalApis).toBe(1);
888
- // Should work without force flag in non-production
889
- registry.clear();
890
- expect(registry.getStats().totalApis).toBe(0);
891
- }
892
- finally {
893
- process.env.NODE_ENV = originalEnv;
894
- }
895
- });
896
- it('should prevent clear() in production without force flag', () => {
897
- const originalEnv = process.env.NODE_ENV;
898
- try {
899
- process.env.NODE_ENV = 'production';
900
- registry.registerApi({
901
- id: 'prod_api',
902
- name: 'Production API',
903
- type: 'rest',
904
- version: 'v1',
905
- basePath: '/prod',
906
- endpoints: [{ id: 'e1', path: '/prod', responses: [] }],
907
- });
908
- // Should throw error in production without force flag
909
- expect(() => registry.clear()).toThrow('Cannot clear registry in production environment without force flag');
910
- // API should still exist
911
- expect(registry.getStats().totalApis).toBe(1);
912
- }
913
- finally {
914
- process.env.NODE_ENV = originalEnv;
915
- }
916
- });
917
- it('should allow clear() in production with force flag', () => {
918
- const originalEnv = process.env.NODE_ENV;
919
- try {
920
- process.env.NODE_ENV = 'production';
921
- registry.registerApi({
922
- id: 'prod_api',
923
- name: 'Production API',
924
- type: 'rest',
925
- version: 'v1',
926
- basePath: '/prod',
927
- endpoints: [{ id: 'e1', path: '/prod', responses: [] }],
928
- });
929
- expect(registry.getStats().totalApis).toBe(1);
930
- // Should work with force flag
931
- registry.clear({ force: true });
932
- expect(registry.getStats().totalApis).toBe(0);
933
- // Verify logger warned about forced clear
934
- expect(logger.warn).toHaveBeenCalledWith('API registry forcefully cleared in production', { force: true });
935
- }
936
- finally {
937
- process.env.NODE_ENV = originalEnv;
938
- }
939
- });
940
- it('should clear all indices when clear() is called', () => {
941
- registry.registerApi({
942
- id: 'api_1',
943
- name: 'API 1',
944
- type: 'rest',
945
- version: 'v1',
946
- basePath: '/api1',
947
- endpoints: [{ id: 'e1', path: '/api1', responses: [] }],
948
- metadata: { status: 'active', tags: ['test'] },
949
- });
950
- registry.clear();
951
- // All lookups should return empty
952
- expect(registry.findApis({ type: 'rest' }).total).toBe(0);
953
- expect(registry.findApis({ status: 'active' }).total).toBe(0);
954
- expect(registry.findApis({ tags: ['test'] }).total).toBe(0);
955
- });
956
- });
957
- });