@objectstack/core 0.9.1 → 1.0.0

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 (94) hide show
  1. package/{ENHANCED_FEATURES.md → ADVANCED_FEATURES.md} +13 -13
  2. package/CHANGELOG.md +21 -0
  3. package/PHASE2_IMPLEMENTATION.md +388 -0
  4. package/README.md +12 -341
  5. package/REFACTORING_SUMMARY.md +40 -0
  6. package/dist/api-registry-plugin.test.js +23 -21
  7. package/dist/api-registry.test.js +2 -2
  8. package/dist/dependency-resolver.d.ts +62 -0
  9. package/dist/dependency-resolver.d.ts.map +1 -0
  10. package/dist/dependency-resolver.js +317 -0
  11. package/dist/dependency-resolver.test.d.ts +2 -0
  12. package/dist/dependency-resolver.test.d.ts.map +1 -0
  13. package/dist/dependency-resolver.test.js +241 -0
  14. package/dist/health-monitor.d.ts +65 -0
  15. package/dist/health-monitor.d.ts.map +1 -0
  16. package/dist/health-monitor.js +269 -0
  17. package/dist/health-monitor.test.d.ts +2 -0
  18. package/dist/health-monitor.test.d.ts.map +1 -0
  19. package/dist/health-monitor.test.js +68 -0
  20. package/dist/hot-reload.d.ts +79 -0
  21. package/dist/hot-reload.d.ts.map +1 -0
  22. package/dist/hot-reload.js +313 -0
  23. package/dist/index.d.ts +4 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +5 -1
  26. package/dist/kernel-base.d.ts +2 -2
  27. package/dist/kernel-base.js +2 -2
  28. package/dist/kernel.d.ts +89 -31
  29. package/dist/kernel.d.ts.map +1 -1
  30. package/dist/kernel.js +430 -73
  31. package/dist/kernel.test.js +375 -122
  32. package/dist/lite-kernel.d.ts +55 -0
  33. package/dist/lite-kernel.d.ts.map +1 -0
  34. package/dist/lite-kernel.js +112 -0
  35. package/dist/lite-kernel.test.d.ts +2 -0
  36. package/dist/lite-kernel.test.d.ts.map +1 -0
  37. package/dist/lite-kernel.test.js +161 -0
  38. package/dist/logger.d.ts +2 -2
  39. package/dist/logger.d.ts.map +1 -1
  40. package/dist/logger.js +26 -7
  41. package/dist/plugin-loader.d.ts +15 -0
  42. package/dist/plugin-loader.d.ts.map +1 -1
  43. package/dist/plugin-loader.js +40 -10
  44. package/dist/plugin-loader.test.js +9 -0
  45. package/dist/security/index.d.ts +3 -0
  46. package/dist/security/index.d.ts.map +1 -1
  47. package/dist/security/index.js +4 -0
  48. package/dist/security/permission-manager.d.ts +96 -0
  49. package/dist/security/permission-manager.d.ts.map +1 -0
  50. package/dist/security/permission-manager.js +235 -0
  51. package/dist/security/permission-manager.test.d.ts +2 -0
  52. package/dist/security/permission-manager.test.d.ts.map +1 -0
  53. package/dist/security/permission-manager.test.js +220 -0
  54. package/dist/security/plugin-permission-enforcer.d.ts +1 -1
  55. package/dist/security/sandbox-runtime.d.ts +115 -0
  56. package/dist/security/sandbox-runtime.d.ts.map +1 -0
  57. package/dist/security/sandbox-runtime.js +310 -0
  58. package/dist/security/security-scanner.d.ts +92 -0
  59. package/dist/security/security-scanner.d.ts.map +1 -0
  60. package/dist/security/security-scanner.js +273 -0
  61. package/examples/{enhanced-kernel-example.ts → kernel-features-example.ts} +6 -6
  62. package/examples/phase2-integration.ts +355 -0
  63. package/package.json +3 -2
  64. package/src/api-registry-plugin.test.ts +23 -21
  65. package/src/api-registry.test.ts +2 -2
  66. package/src/dependency-resolver.test.ts +287 -0
  67. package/src/dependency-resolver.ts +388 -0
  68. package/src/health-monitor.test.ts +81 -0
  69. package/src/health-monitor.ts +316 -0
  70. package/src/hot-reload.ts +388 -0
  71. package/src/index.ts +6 -1
  72. package/src/kernel-base.ts +2 -2
  73. package/src/kernel.test.ts +471 -134
  74. package/src/kernel.ts +518 -76
  75. package/src/lite-kernel.test.ts +200 -0
  76. package/src/lite-kernel.ts +135 -0
  77. package/src/logger.ts +28 -7
  78. package/src/plugin-loader.test.ts +10 -1
  79. package/src/plugin-loader.ts +49 -13
  80. package/src/security/index.ts +19 -0
  81. package/src/security/permission-manager.test.ts +256 -0
  82. package/src/security/permission-manager.ts +336 -0
  83. package/src/security/plugin-permission-enforcer.test.ts +1 -1
  84. package/src/security/plugin-permission-enforcer.ts +1 -1
  85. package/src/security/sandbox-runtime.ts +432 -0
  86. package/src/security/security-scanner.ts +365 -0
  87. package/dist/enhanced-kernel.d.ts +0 -103
  88. package/dist/enhanced-kernel.d.ts.map +0 -1
  89. package/dist/enhanced-kernel.js +0 -403
  90. package/dist/enhanced-kernel.test.d.ts +0 -2
  91. package/dist/enhanced-kernel.test.d.ts.map +0 -1
  92. package/dist/enhanced-kernel.test.js +0 -412
  93. package/src/enhanced-kernel.test.ts +0 -535
  94. package/src/enhanced-kernel.ts +0 -496
@@ -1,200 +1,537 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
2
  import { ObjectKernel } from './kernel';
3
+ import { ServiceLifecycle, PluginMetadata } from './plugin-loader';
3
4
  import type { Plugin } from './types';
4
5
 
5
- describe('ObjectKernel with Configurable Logger', () => {
6
+ describe('ObjectKernel', () => {
6
7
  let kernel: ObjectKernel;
7
8
 
8
9
  beforeEach(() => {
9
- kernel = new ObjectKernel();
10
+ kernel = new ObjectKernel({
11
+ logger: { level: 'error' }, // Suppress logs in tests
12
+ gracefulShutdown: false, // Disable for tests
13
+ skipSystemValidation: true,
14
+ });
10
15
  });
11
16
 
12
- describe('Logger Configuration', () => {
13
- it('should create kernel with default logger', () => {
14
- expect(kernel).toBeDefined();
17
+ describe('Plugin Registration and Loading', () => {
18
+ it('should register a plugin with version', async () => {
19
+ const plugin: Plugin = {
20
+ name: 'versioned-plugin',
21
+ version: '1.2.3',
22
+ init: async () => {},
23
+ };
24
+
25
+ await kernel.use(plugin);
26
+ await kernel.bootstrap();
27
+
28
+ expect(kernel.isRunning()).toBe(true);
29
+
30
+ await kernel.shutdown();
15
31
  });
16
32
 
17
- it('should create kernel with custom logger config', async () => {
18
- const customKernel = new ObjectKernel({
19
- logger: {
20
- level: 'debug',
21
- format: 'pretty',
22
- sourceLocation: true
23
- }
24
- });
25
-
26
- expect(customKernel).toBeDefined();
27
-
28
- // Cleanup
29
- await customKernel.bootstrap();
30
- await customKernel.shutdown();
31
- });
32
-
33
- it('should create kernel with file logging config', async () => {
34
- const fileKernel = new ObjectKernel({
35
- logger: {
36
- level: 'info',
37
- format: 'json',
38
- file: '/tmp/test-kernel.log'
39
- }
40
- });
41
-
42
- expect(fileKernel).toBeDefined();
43
-
44
- // Cleanup
45
- await fileKernel.bootstrap();
46
- await fileKernel.shutdown();
33
+ it('should validate plugin during registration', async () => {
34
+ const invalidPlugin: any = {
35
+ name: '',
36
+ init: async () => {},
37
+ };
38
+
39
+ await expect(async () => {
40
+ await kernel.use(invalidPlugin);
41
+ }).rejects.toThrow();
42
+ });
43
+
44
+ it('should reject plugin registration after bootstrap', async () => {
45
+ await kernel.bootstrap();
46
+
47
+ const plugin: Plugin = {
48
+ name: 'late-plugin',
49
+ init: async () => {},
50
+ };
51
+
52
+ await expect(async () => {
53
+ await kernel.use(plugin);
54
+ }).rejects.toThrow('Cannot register plugins after bootstrap');
55
+
56
+ await kernel.shutdown();
47
57
  });
48
58
  });
49
59
 
50
- describe('Plugin Context Logger', () => {
51
- it('should provide logger to plugins', async () => {
52
- let loggerReceived = false;
60
+ describe('Service Factory Registration', () => {
61
+ it('should register singleton service factory', async () => {
62
+ let callCount = 0;
53
63
 
54
- const testPlugin: Plugin = {
55
- name: 'test-plugin',
56
- init: async (ctx) => {
57
- if (ctx.logger) {
58
- loggerReceived = true;
59
- ctx.logger.info('Plugin initialized', { plugin: 'test-plugin' });
60
- }
61
- }
62
- };
64
+ kernel.registerServiceFactory(
65
+ 'counter',
66
+ () => {
67
+ callCount++;
68
+ return { count: callCount };
69
+ },
70
+ ServiceLifecycle.SINGLETON
71
+ );
63
72
 
64
- kernel.use(testPlugin);
65
73
  await kernel.bootstrap();
66
74
 
67
- expect(loggerReceived).toBe(true);
68
-
75
+ const service1 = await kernel.getServiceAsync('counter');
76
+ const service2 = await kernel.getServiceAsync('counter');
77
+
78
+ expect(callCount).toBe(1);
79
+ expect(service1).toBe(service2);
80
+
69
81
  await kernel.shutdown();
70
82
  });
71
83
 
72
- it('should allow plugins to use all log levels', async () => {
73
- const logCalls: string[] = [];
84
+ it('should register transient service factory', async () => {
85
+ let callCount = 0;
74
86
 
75
- const loggingPlugin: Plugin = {
76
- name: 'logging-plugin',
77
- init: async (ctx) => {
78
- ctx.logger.debug('Debug message');
79
- logCalls.push('debug');
80
-
81
- ctx.logger.info('Info message');
82
- logCalls.push('info');
83
-
84
- ctx.logger.warn('Warning message');
85
- logCalls.push('warn');
86
-
87
- ctx.logger.error('Error message');
88
- logCalls.push('error');
89
- }
90
- };
91
-
92
- kernel.use(loggingPlugin);
93
- await kernel.bootstrap();
94
-
95
- expect(logCalls).toContain('debug');
96
- expect(logCalls).toContain('info');
97
- expect(logCalls).toContain('warn');
98
- expect(logCalls).toContain('error');
87
+ kernel.registerServiceFactory(
88
+ 'transient',
89
+ () => {
90
+ callCount++;
91
+ return { count: callCount };
92
+ },
93
+ ServiceLifecycle.TRANSIENT
94
+ );
95
+
96
+ await kernel.bootstrap();
97
+
98
+ const service1 = await kernel.getServiceAsync('transient');
99
+ const service2 = await kernel.getServiceAsync('transient');
100
+
101
+ expect(callCount).toBe(2);
102
+ expect(service1).not.toBe(service2);
103
+
104
+ await kernel.shutdown();
105
+ });
106
+
107
+ it('should register scoped service factory', async () => {
108
+ let callCount = 0;
99
109
 
110
+ kernel.registerServiceFactory(
111
+ 'scoped',
112
+ () => {
113
+ callCount++;
114
+ return { count: callCount };
115
+ },
116
+ ServiceLifecycle.SCOPED
117
+ );
118
+
119
+ await kernel.bootstrap();
120
+
121
+ const service1 = await kernel.getServiceAsync('scoped', 'request-1');
122
+ const service2 = await kernel.getServiceAsync('scoped', 'request-1');
123
+ const service3 = await kernel.getServiceAsync('scoped', 'request-2');
124
+
125
+ expect(callCount).toBe(2); // Once per scope
126
+ expect(service1).toBe(service2); // Same within scope
127
+ expect(service1).not.toBe(service3); // Different across scopes
128
+
100
129
  await kernel.shutdown();
101
130
  });
131
+ });
102
132
 
103
- it('should support metadata in logs', async () => {
104
- const metadataPlugin: Plugin = {
105
- name: 'metadata-plugin',
106
- init: async (ctx) => {
107
- ctx.logger.info('User action', {
108
- userId: '123',
109
- action: 'create',
110
- resource: 'document'
111
- });
112
- }
133
+ describe('Plugin Lifecycle with Timeout', () => {
134
+ it('should timeout plugin init if it takes too long', async () => {
135
+ const plugin: PluginMetadata = {
136
+ name: 'slow-init',
137
+ version: '1.0.0',
138
+ init: async () => {
139
+ await new Promise(resolve => setTimeout(resolve, 5000)); // 5 seconds
140
+ },
141
+ startupTimeout: 100, // 100ms timeout
142
+ };
143
+
144
+ await kernel.use(plugin);
145
+
146
+ await expect(async () => {
147
+ await kernel.bootstrap();
148
+ }).rejects.toThrow('timeout');
149
+ }, 1000); // Test should complete in 1 second
150
+
151
+ it('should timeout plugin start if it takes too long', async () => {
152
+ const plugin: PluginMetadata = {
153
+ name: 'slow-start',
154
+ version: '1.0.0',
155
+ init: async () => {},
156
+ start: async () => {
157
+ await new Promise(resolve => setTimeout(resolve, 5000)); // 5 seconds
158
+ },
159
+ startupTimeout: 100, // 100ms timeout
113
160
  };
114
161
 
115
- kernel.use(metadataPlugin);
162
+ await kernel.use(plugin);
163
+
164
+ await expect(async () => {
165
+ await kernel.bootstrap();
166
+ }).rejects.toThrow();
167
+ }, 1000); // Test should complete in 1 second
168
+
169
+ it('should complete plugin startup within timeout', async () => {
170
+ const plugin: PluginMetadata = {
171
+ name: 'fast-plugin',
172
+ version: '1.0.0',
173
+ init: async () => {
174
+ await new Promise(resolve => setTimeout(resolve, 10));
175
+ },
176
+ start: async () => {
177
+ await new Promise(resolve => setTimeout(resolve, 10));
178
+ },
179
+ startupTimeout: 1000,
180
+ };
181
+
182
+ await kernel.use(plugin);
116
183
  await kernel.bootstrap();
117
-
184
+
185
+ expect(kernel.isRunning()).toBe(true);
186
+
118
187
  await kernel.shutdown();
119
188
  });
120
189
  });
121
190
 
122
- describe('Kernel Lifecycle Logging', () => {
123
- it('should log bootstrap process', async () => {
191
+ describe('Startup Failure Rollback', () => {
192
+ it('should rollback started plugins on failure', async () => {
193
+ let plugin1Destroyed = false;
194
+
195
+ const plugin1: Plugin = {
196
+ name: 'plugin-1',
197
+ version: '1.0.0',
198
+ init: async () => {},
199
+ start: async () => {},
200
+ destroy: async () => {
201
+ plugin1Destroyed = true;
202
+ },
203
+ };
204
+
205
+ const plugin2: Plugin = {
206
+ name: 'plugin-2',
207
+ version: '1.0.0',
208
+ init: async () => {},
209
+ start: async () => {
210
+ throw new Error('Startup failed');
211
+ },
212
+ };
213
+
214
+ await kernel.use(plugin1);
215
+ await kernel.use(plugin2);
216
+
217
+ await expect(async () => {
218
+ await kernel.bootstrap();
219
+ }).rejects.toThrow('failed to start');
220
+
221
+ // Plugin 1 should be rolled back
222
+ expect(plugin1Destroyed).toBe(true);
223
+ });
224
+
225
+ it('should not rollback if disabled', async () => {
226
+ const noRollbackKernel = new ObjectKernel({
227
+ logger: { level: 'error' },
228
+ rollbackOnFailure: false,
229
+ gracefulShutdown: false,
230
+ skipSystemValidation: true,
231
+ });
232
+
233
+ let plugin1Destroyed = false;
234
+
235
+ const plugin1: Plugin = {
236
+ name: 'plugin-1',
237
+ version: '1.0.0',
238
+ init: async () => {},
239
+ start: async () => {},
240
+ destroy: async () => {
241
+ plugin1Destroyed = true;
242
+ },
243
+ };
244
+
245
+ const plugin2: Plugin = {
246
+ name: 'plugin-2',
247
+ version: '1.0.0',
248
+ init: async () => {},
249
+ start: async () => {
250
+ throw new Error('Startup failed');
251
+ },
252
+ };
253
+
254
+ await noRollbackKernel.use(plugin1);
255
+ await noRollbackKernel.use(plugin2);
256
+
257
+ // Should not throw since rollback is disabled
258
+ await noRollbackKernel.bootstrap();
259
+
260
+ // Plugin 1 should NOT be destroyed
261
+ expect(plugin1Destroyed).toBe(false);
262
+ });
263
+ });
264
+
265
+ describe('Plugin Health Checks', () => {
266
+ it('should check individual plugin health', async () => {
124
267
  const plugin: Plugin = {
125
- name: 'lifecycle-test',
126
- init: async () => {
127
- // Init logic
268
+ name: 'healthy-plugin',
269
+ version: '1.0.0',
270
+ init: async () => {},
271
+ };
272
+
273
+ await kernel.use(plugin);
274
+ await kernel.bootstrap();
275
+
276
+ const health = await kernel.checkPluginHealth('healthy-plugin');
277
+
278
+ expect(health.healthy).toBe(true);
279
+ expect(health.lastCheck).toBeInstanceOf(Date);
280
+
281
+ await kernel.shutdown();
282
+ });
283
+
284
+ it('should check all plugins health', async () => {
285
+ const plugin1: Plugin = {
286
+ name: 'plugin-1',
287
+ version: '1.0.0',
288
+ init: async () => {},
289
+ };
290
+
291
+ const plugin2: Plugin = {
292
+ name: 'plugin-2',
293
+ version: '1.0.0',
294
+ init: async () => {},
295
+ };
296
+
297
+ await kernel.use(plugin1);
298
+ await kernel.use(plugin2);
299
+ await kernel.bootstrap();
300
+
301
+ const allHealth = await kernel.checkAllPluginsHealth();
302
+
303
+ expect(allHealth.size).toBe(2);
304
+ expect(allHealth.get('plugin-1').healthy).toBe(true);
305
+ expect(allHealth.get('plugin-2').healthy).toBe(true);
306
+
307
+ await kernel.shutdown();
308
+ });
309
+ });
310
+
311
+ describe('Plugin Metrics', () => {
312
+ it('should track plugin startup times', async () => {
313
+ const plugin1: Plugin = {
314
+ name: 'plugin-1',
315
+ version: '1.0.0',
316
+ init: async () => {},
317
+ start: async () => {
318
+ await new Promise(resolve => setTimeout(resolve, 50));
128
319
  },
320
+ };
321
+
322
+ const plugin2: Plugin = {
323
+ name: 'plugin-2',
324
+ version: '1.0.0',
325
+ init: async () => {},
129
326
  start: async () => {
130
- // Start logic
131
- }
327
+ await new Promise(resolve => setTimeout(resolve, 30));
328
+ },
132
329
  };
133
330
 
134
- kernel.use(plugin);
331
+ await kernel.use(plugin1);
332
+ await kernel.use(plugin2);
135
333
  await kernel.bootstrap();
136
-
137
- expect(kernel.isRunning()).toBe(true);
138
-
334
+
335
+ const metrics = kernel.getPluginMetrics();
336
+
337
+ expect(metrics.size).toBe(2);
338
+ expect(metrics.get('plugin-1')).toBeGreaterThan(0);
339
+ expect(metrics.get('plugin-2')).toBeGreaterThan(0);
340
+
139
341
  await kernel.shutdown();
140
342
  });
141
343
 
142
- it('should log shutdown process', async () => {
344
+ it('should not track metrics for plugins without start', async () => {
143
345
  const plugin: Plugin = {
144
- name: 'shutdown-test',
346
+ name: 'no-start',
347
+ version: '1.0.0',
348
+ init: async () => {},
349
+ };
350
+
351
+ await kernel.use(plugin);
352
+ await kernel.bootstrap();
353
+
354
+ const metrics = kernel.getPluginMetrics();
355
+
356
+ expect(metrics.has('no-start')).toBe(false);
357
+
358
+ await kernel.shutdown();
359
+ });
360
+ });
361
+
362
+ describe('Graceful Shutdown', () => {
363
+ it('should call destroy on all plugins', async () => {
364
+ let plugin1Destroyed = false;
365
+ let plugin2Destroyed = false;
366
+
367
+ const plugin1: Plugin = {
368
+ name: 'plugin-1',
369
+ version: '1.0.0',
145
370
  init: async () => {},
146
371
  destroy: async () => {
147
- // Cleanup
148
- }
372
+ plugin1Destroyed = true;
373
+ },
149
374
  };
150
375
 
151
- kernel.use(plugin);
376
+ const plugin2: Plugin = {
377
+ name: 'plugin-2',
378
+ version: '1.0.0',
379
+ init: async () => {},
380
+ destroy: async () => {
381
+ plugin2Destroyed = true;
382
+ },
383
+ };
384
+
385
+ await kernel.use(plugin1);
386
+ await kernel.use(plugin2);
152
387
  await kernel.bootstrap();
153
388
  await kernel.shutdown();
154
-
389
+
390
+ expect(plugin1Destroyed).toBe(true);
391
+ expect(plugin2Destroyed).toBe(true);
392
+ });
393
+
394
+ it('should handle plugin destroy errors gracefully', async () => {
395
+ const plugin1: Plugin = {
396
+ name: 'error-destroy',
397
+ version: '1.0.0',
398
+ init: async () => {},
399
+ destroy: async () => {
400
+ throw new Error('Destroy failed');
401
+ },
402
+ };
403
+
404
+ const plugin2: Plugin = {
405
+ name: 'normal-plugin',
406
+ version: '1.0.0',
407
+ init: async () => {},
408
+ };
409
+
410
+ await kernel.use(plugin1);
411
+ await kernel.use(plugin2);
412
+ await kernel.bootstrap();
413
+
414
+ // Should not throw even if one plugin fails to destroy
415
+ await kernel.shutdown();
416
+
155
417
  expect(kernel.getState()).toBe('stopped');
156
418
  });
157
- });
158
419
 
159
- describe('Environment Compatibility', () => {
160
- it('should work in Node.js environment', async () => {
161
- const nodeKernel = new ObjectKernel({
162
- logger: {
163
- level: 'info',
164
- format: 'json'
165
- }
166
- });
420
+ it('should trigger shutdown hook', async () => {
421
+ let hookCalled = false;
167
422
 
168
423
  const plugin: Plugin = {
169
- name: 'node-test',
424
+ name: 'hook-plugin',
425
+ version: '1.0.0',
170
426
  init: async (ctx) => {
171
- ctx.logger.info('Running in Node.js');
172
- }
427
+ ctx.hook('kernel:shutdown', async () => {
428
+ hookCalled = true;
429
+ });
430
+ },
173
431
  };
174
432
 
175
- nodeKernel.use(plugin);
176
- await nodeKernel.bootstrap();
177
- await nodeKernel.shutdown();
433
+ await kernel.use(plugin);
434
+ await kernel.bootstrap();
435
+ await kernel.shutdown();
436
+
437
+ expect(hookCalled).toBe(true);
178
438
  });
179
439
 
180
- it('should support browser-friendly logging', async () => {
181
- const browserKernel = new ObjectKernel({
182
- logger: {
183
- level: 'info',
184
- format: 'pretty'
185
- }
440
+ it('should execute custom shutdown handlers', async () => {
441
+ let handlerCalled = false;
442
+
443
+ kernel.onShutdown(async () => {
444
+ handlerCalled = true;
186
445
  });
187
446
 
188
- const plugin: Plugin = {
189
- name: 'browser-test',
190
- init: async (ctx) => {
191
- ctx.logger.info('Browser-friendly format');
192
- }
447
+ await kernel.bootstrap();
448
+ await kernel.shutdown();
449
+
450
+ expect(handlerCalled).toBe(true);
451
+ });
452
+ });
453
+
454
+ describe('Dependency Resolution', () => {
455
+ it('should resolve plugin dependencies in correct order', async () => {
456
+ const initOrder: string[] = [];
457
+
458
+ const pluginA: Plugin = {
459
+ name: 'plugin-a',
460
+ version: '1.0.0',
461
+ dependencies: ['plugin-b'],
462
+ init: async () => {
463
+ initOrder.push('plugin-a');
464
+ },
465
+ };
466
+
467
+ const pluginB: Plugin = {
468
+ name: 'plugin-b',
469
+ version: '1.0.0',
470
+ init: async () => {
471
+ initOrder.push('plugin-b');
472
+ },
473
+ };
474
+
475
+ await kernel.use(pluginA);
476
+ await kernel.use(pluginB);
477
+ await kernel.bootstrap();
478
+
479
+ expect(initOrder).toEqual(['plugin-b', 'plugin-a']);
480
+
481
+ await kernel.shutdown();
482
+ });
483
+
484
+ it('should detect circular plugin dependencies', async () => {
485
+ const pluginA: Plugin = {
486
+ name: 'plugin-a',
487
+ version: '1.0.0',
488
+ dependencies: ['plugin-b'],
489
+ init: async () => {},
193
490
  };
194
491
 
195
- browserKernel.use(plugin);
196
- await browserKernel.bootstrap();
197
- await browserKernel.shutdown();
492
+ const pluginB: Plugin = {
493
+ name: 'plugin-b',
494
+ version: '1.0.0',
495
+ dependencies: ['plugin-a'],
496
+ init: async () => {},
497
+ };
498
+
499
+ await kernel.use(pluginA);
500
+ await kernel.use(pluginB);
501
+
502
+ await expect(async () => {
503
+ await kernel.bootstrap();
504
+ }).rejects.toThrow('Circular dependency');
505
+ });
506
+ });
507
+
508
+ describe('State Management', () => {
509
+ it('should track kernel state correctly', async () => {
510
+ expect(kernel.getState()).toBe('idle');
511
+
512
+ await kernel.bootstrap();
513
+ expect(kernel.getState()).toBe('running');
514
+ expect(kernel.isRunning()).toBe(true);
515
+
516
+ await kernel.shutdown();
517
+ expect(kernel.getState()).toBe('stopped');
518
+ expect(kernel.isRunning()).toBe(false);
519
+ });
520
+
521
+ it('should not allow double bootstrap', async () => {
522
+ await kernel.bootstrap();
523
+
524
+ await expect(async () => {
525
+ await kernel.bootstrap();
526
+ }).rejects.toThrow('already bootstrapped');
527
+
528
+ await kernel.shutdown();
529
+ });
530
+
531
+ it('should not allow shutdown before bootstrap', async () => {
532
+ await expect(async () => {
533
+ await kernel.shutdown();
534
+ }).rejects.toThrow('not running');
198
535
  });
199
536
  });
200
537
  });