@objectstack/core 1.0.4 → 1.0.6

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 (127) hide show
  1. package/.turbo/turbo-build.log +22 -0
  2. package/CHANGELOG.md +19 -0
  3. package/dist/index.cjs +4304 -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 +5 -5
  10. package/src/logger.ts +2 -2
  11. package/src/security/plugin-signature-verifier.ts +12 -11
  12. package/tsconfig.json +1 -3
  13. package/dist/api-registry-plugin.d.ts +0 -54
  14. package/dist/api-registry-plugin.d.ts.map +0 -1
  15. package/dist/api-registry-plugin.js +0 -53
  16. package/dist/api-registry-plugin.test.d.ts +0 -2
  17. package/dist/api-registry-plugin.test.d.ts.map +0 -1
  18. package/dist/api-registry-plugin.test.js +0 -334
  19. package/dist/api-registry.d.ts +0 -259
  20. package/dist/api-registry.d.ts.map +0 -1
  21. package/dist/api-registry.js +0 -600
  22. package/dist/api-registry.test.d.ts +0 -2
  23. package/dist/api-registry.test.d.ts.map +0 -1
  24. package/dist/api-registry.test.js +0 -957
  25. package/dist/contracts/data-engine.d.ts +0 -62
  26. package/dist/contracts/data-engine.d.ts.map +0 -1
  27. package/dist/contracts/data-engine.js +0 -1
  28. package/dist/contracts/http-server.d.ts +0 -119
  29. package/dist/contracts/http-server.d.ts.map +0 -1
  30. package/dist/contracts/http-server.js +0 -11
  31. package/dist/contracts/logger.d.ts +0 -63
  32. package/dist/contracts/logger.d.ts.map +0 -1
  33. package/dist/contracts/logger.js +0 -1
  34. package/dist/dependency-resolver.d.ts +0 -62
  35. package/dist/dependency-resolver.d.ts.map +0 -1
  36. package/dist/dependency-resolver.js +0 -317
  37. package/dist/dependency-resolver.test.d.ts +0 -2
  38. package/dist/dependency-resolver.test.d.ts.map +0 -1
  39. package/dist/dependency-resolver.test.js +0 -241
  40. package/dist/health-monitor.d.ts +0 -65
  41. package/dist/health-monitor.d.ts.map +0 -1
  42. package/dist/health-monitor.js +0 -269
  43. package/dist/health-monitor.test.d.ts +0 -2
  44. package/dist/health-monitor.test.d.ts.map +0 -1
  45. package/dist/health-monitor.test.js +0 -68
  46. package/dist/hot-reload.d.ts +0 -79
  47. package/dist/hot-reload.d.ts.map +0 -1
  48. package/dist/hot-reload.js +0 -313
  49. package/dist/index.d.ts.map +0 -1
  50. package/dist/kernel-base.d.ts +0 -84
  51. package/dist/kernel-base.d.ts.map +0 -1
  52. package/dist/kernel-base.js +0 -219
  53. package/dist/kernel.d.ts +0 -113
  54. package/dist/kernel.d.ts.map +0 -1
  55. package/dist/kernel.js +0 -472
  56. package/dist/kernel.test.d.ts +0 -2
  57. package/dist/kernel.test.d.ts.map +0 -1
  58. package/dist/kernel.test.js +0 -414
  59. package/dist/lite-kernel.d.ts +0 -55
  60. package/dist/lite-kernel.d.ts.map +0 -1
  61. package/dist/lite-kernel.js +0 -112
  62. package/dist/lite-kernel.test.d.ts +0 -2
  63. package/dist/lite-kernel.test.d.ts.map +0 -1
  64. package/dist/lite-kernel.test.js +0 -161
  65. package/dist/logger.d.ts +0 -71
  66. package/dist/logger.d.ts.map +0 -1
  67. package/dist/logger.js +0 -312
  68. package/dist/logger.test.d.ts +0 -2
  69. package/dist/logger.test.d.ts.map +0 -1
  70. package/dist/logger.test.js +0 -92
  71. package/dist/plugin-loader.d.ts +0 -164
  72. package/dist/plugin-loader.d.ts.map +0 -1
  73. package/dist/plugin-loader.js +0 -319
  74. package/dist/plugin-loader.test.d.ts +0 -2
  75. package/dist/plugin-loader.test.d.ts.map +0 -1
  76. package/dist/plugin-loader.test.js +0 -348
  77. package/dist/qa/adapter.d.ts +0 -14
  78. package/dist/qa/adapter.d.ts.map +0 -1
  79. package/dist/qa/adapter.js +0 -1
  80. package/dist/qa/http-adapter.d.ts +0 -16
  81. package/dist/qa/http-adapter.d.ts.map +0 -1
  82. package/dist/qa/http-adapter.js +0 -107
  83. package/dist/qa/index.d.ts +0 -4
  84. package/dist/qa/index.d.ts.map +0 -1
  85. package/dist/qa/index.js +0 -3
  86. package/dist/qa/runner.d.ts +0 -27
  87. package/dist/qa/runner.d.ts.map +0 -1
  88. package/dist/qa/runner.js +0 -157
  89. package/dist/security/index.d.ts +0 -17
  90. package/dist/security/index.d.ts.map +0 -1
  91. package/dist/security/index.js +0 -17
  92. package/dist/security/permission-manager.d.ts +0 -96
  93. package/dist/security/permission-manager.d.ts.map +0 -1
  94. package/dist/security/permission-manager.js +0 -235
  95. package/dist/security/permission-manager.test.d.ts +0 -2
  96. package/dist/security/permission-manager.test.d.ts.map +0 -1
  97. package/dist/security/permission-manager.test.js +0 -220
  98. package/dist/security/plugin-config-validator.d.ts +0 -79
  99. package/dist/security/plugin-config-validator.d.ts.map +0 -1
  100. package/dist/security/plugin-config-validator.js +0 -166
  101. package/dist/security/plugin-config-validator.test.d.ts +0 -2
  102. package/dist/security/plugin-config-validator.test.d.ts.map +0 -1
  103. package/dist/security/plugin-config-validator.test.js +0 -223
  104. package/dist/security/plugin-permission-enforcer.d.ts +0 -154
  105. package/dist/security/plugin-permission-enforcer.d.ts.map +0 -1
  106. package/dist/security/plugin-permission-enforcer.js +0 -323
  107. package/dist/security/plugin-permission-enforcer.test.d.ts +0 -2
  108. package/dist/security/plugin-permission-enforcer.test.d.ts.map +0 -1
  109. package/dist/security/plugin-permission-enforcer.test.js +0 -205
  110. package/dist/security/plugin-signature-verifier.d.ts +0 -96
  111. package/dist/security/plugin-signature-verifier.d.ts.map +0 -1
  112. package/dist/security/plugin-signature-verifier.js +0 -250
  113. package/dist/security/sandbox-runtime.d.ts +0 -115
  114. package/dist/security/sandbox-runtime.d.ts.map +0 -1
  115. package/dist/security/sandbox-runtime.js +0 -311
  116. package/dist/security/security-scanner.d.ts +0 -92
  117. package/dist/security/security-scanner.d.ts.map +0 -1
  118. package/dist/security/security-scanner.js +0 -273
  119. package/dist/types.d.ts +0 -89
  120. package/dist/types.d.ts.map +0 -1
  121. package/dist/types.js +0 -1
  122. package/dist/utils/env.d.ts +0 -20
  123. package/dist/utils/env.d.ts.map +0 -1
  124. package/dist/utils/env.js +0 -46
  125. package/dist/utils/env.test.d.ts +0 -2
  126. package/dist/utils/env.test.d.ts.map +0 -1
  127. 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
- });