@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
package/src/kernel.ts CHANGED
@@ -1,135 +1,577 @@
1
- import { Plugin } from './types.js';
1
+ import { Plugin, PluginContext } from './types.js';
2
2
  import { createLogger, ObjectLogger } from './logger.js';
3
3
  import type { LoggerConfig } from '@objectstack/spec/system';
4
- import { ObjectKernelBase } from './kernel-base.js';
4
+ import { ServiceRequirementDef } from '@objectstack/spec/system';
5
+ import { PluginLoader, PluginMetadata, ServiceLifecycle, ServiceFactory, PluginStartupResult } from './plugin-loader.js';
5
6
 
6
7
  /**
7
- * ObjectKernel - MiniKernel Architecture
8
- *
9
- * A highly modular, plugin-based microkernel that:
10
- * - Manages plugin lifecycle (init, start, destroy)
11
- * - Provides dependency injection via service registry
12
- * - Implements event/hook system for inter-plugin communication
13
- * - Handles dependency resolution (topological sort)
14
- * - Provides configurable logging for server and browser
8
+ * Enhanced Kernel Configuration
9
+ */
10
+ export interface ObjectKernelConfig {
11
+ logger?: Partial<LoggerConfig>;
12
+
13
+ /** Default plugin startup timeout in milliseconds */
14
+ defaultStartupTimeout?: number;
15
+
16
+ /** Whether to enable graceful shutdown */
17
+ gracefulShutdown?: boolean;
18
+
19
+ /** Graceful shutdown timeout in milliseconds */
20
+ shutdownTimeout?: number;
21
+
22
+ /** Whether to rollback on startup failure */
23
+ rollbackOnFailure?: boolean;
24
+
25
+ /** Whether to skip strict system requirement validation (Critical for testing) */
26
+ skipSystemValidation?: boolean;
27
+ }
28
+
29
+ /**
30
+ * Enhanced ObjectKernel with Advanced Plugin Management
15
31
  *
16
- * Core philosophy:
17
- * - Business logic is completely separated into plugins
18
- * - Kernel only manages lifecycle, DI, and hooks
19
- * - Plugins are loaded as equal building blocks
32
+ * Extends the basic ObjectKernel with:
33
+ * - Async plugin loading with validation
34
+ * - Version compatibility checking
35
+ * - Plugin signature verification
36
+ * - Configuration validation (Zod)
37
+ * - Factory-based dependency injection
38
+ * - Service lifecycle management (singleton/transient/scoped)
39
+ * - Circular dependency detection
40
+ * - Lazy loading services
41
+ * - Graceful shutdown
42
+ * - Plugin startup timeout control
43
+ * - Startup failure rollback
44
+ * - Plugin health checks
20
45
  */
21
- export class ObjectKernel extends ObjectKernelBase {
22
- constructor(config?: { logger?: Partial<LoggerConfig> }) {
23
- const logger = createLogger(config?.logger);
24
- super(logger);
46
+ export class ObjectKernel {
47
+ private plugins: Map<string, PluginMetadata> = new Map();
48
+ private services: Map<string, any> = new Map();
49
+ private hooks: Map<string, Array<(...args: any[]) => void | Promise<void>>> = new Map();
50
+ private state: 'idle' | 'initializing' | 'running' | 'stopping' | 'stopped' = 'idle';
51
+ private logger: ObjectLogger;
52
+ private context: PluginContext;
53
+ private pluginLoader: PluginLoader;
54
+ private config: ObjectKernelConfig;
55
+ private startedPlugins: Set<string> = new Set();
56
+ private pluginStartTimes: Map<string, number> = new Map();
57
+ private shutdownHandlers: Array<() => Promise<void>> = [];
58
+
59
+ constructor(config: ObjectKernelConfig = {}) {
60
+ this.config = {
61
+ defaultStartupTimeout: 30000, // 30 seconds
62
+ gracefulShutdown: true,
63
+ shutdownTimeout: 60000, // 60 seconds
64
+ rollbackOnFailure: true,
65
+ ...config,
66
+ };
67
+
68
+ this.logger = createLogger(config.logger);
69
+ this.pluginLoader = new PluginLoader(this.logger);
25
70
 
26
- // Initialize context after logger is created
27
- this.context = this.createContext();
71
+ // Initialize context
72
+ this.context = {
73
+ registerService: (name, service) => {
74
+ this.registerService(name, service);
75
+ },
76
+ getService: <T>(name: string) => {
77
+ // 1. Try direct service map first (synchronous cache)
78
+ const service = this.services.get(name);
79
+ if (service) {
80
+ return service as T;
81
+ }
82
+
83
+ // 2. Try to get from plugin loader cache (Sync access to factories)
84
+ const loaderService = this.pluginLoader.getServiceInstance<T>(name);
85
+ if (loaderService) {
86
+ // Cache it locally for faster next access
87
+ this.services.set(name, loaderService);
88
+ return loaderService;
89
+ }
90
+
91
+ // 3. Try to get from plugin loader (support async factories)
92
+ try {
93
+ const service = this.pluginLoader.getService(name);
94
+ if (service instanceof Promise) {
95
+ // If we found it in the loader but not in the sync map, it's likely a factory-based service or still loading
96
+ throw new Error(`Service '${name}' is async - use await`);
97
+ }
98
+ return service as T;
99
+ } catch (error: any) {
100
+ if (error.message?.includes('is async')) {
101
+ throw error;
102
+ }
103
+
104
+ // Re-throw critical factory errors instead of masking them as "not found"
105
+ // If the error came from the factory execution (e.g. database connection failed), we must see it.
106
+ // "Service '${name}' not found" comes from PluginLoader.getService fallback.
107
+ const isNotFoundError = error.message === `Service '${name}' not found`;
108
+
109
+ if (!isNotFoundError) {
110
+ throw error;
111
+ }
112
+
113
+ throw new Error(`[Kernel] Service '${name}' not found`);
114
+ }
115
+ },
116
+ hook: (name, handler) => {
117
+ if (!this.hooks.has(name)) {
118
+ this.hooks.set(name, []);
119
+ }
120
+ this.hooks.get(name)!.push(handler);
121
+ },
122
+ trigger: async (name, ...args) => {
123
+ const handlers = this.hooks.get(name) || [];
124
+ for (const handler of handlers) {
125
+ await handler(...args);
126
+ }
127
+ },
128
+ getServices: () => {
129
+ return new Map(this.services);
130
+ },
131
+ logger: this.logger,
132
+ getKernel: () => this as any, // Type compatibility
133
+ };
134
+
135
+ this.pluginLoader.setContext(this.context);
136
+
137
+ // Register shutdown handler
138
+ if (this.config.gracefulShutdown) {
139
+ this.registerShutdownSignals();
140
+ }
28
141
  }
29
142
 
30
143
  /**
31
- * Register a plugin
32
- * @param plugin - Plugin instance
144
+ * Register a plugin with enhanced validation
33
145
  */
34
- use(plugin: Plugin): this {
35
- this.validateIdle();
146
+ async use(plugin: Plugin): Promise<this> {
147
+ if (this.state !== 'idle') {
148
+ throw new Error('[Kernel] Cannot register plugins after bootstrap has started');
149
+ }
36
150
 
37
- const pluginName = plugin.name;
38
- if (this.plugins.has(pluginName)) {
39
- throw new Error(`[Kernel] Plugin '${pluginName}' already registered`);
151
+ // Load plugin through enhanced loader
152
+ const result = await this.pluginLoader.loadPlugin(plugin);
153
+
154
+ if (!result.success || !result.plugin) {
155
+ throw new Error(`Failed to load plugin: ${plugin.name} - ${result.error?.message}`);
40
156
  }
41
157
 
42
- this.plugins.set(pluginName, plugin);
158
+ const pluginMeta = result.plugin;
159
+ this.plugins.set(pluginMeta.name, pluginMeta);
160
+
161
+ this.logger.info(`Plugin registered: ${pluginMeta.name}@${pluginMeta.version}`, {
162
+ plugin: pluginMeta.name,
163
+ version: pluginMeta.version,
164
+ });
165
+
43
166
  return this;
44
167
  }
45
168
 
46
169
  /**
47
- * Bootstrap the kernel
48
- * 1. Resolve dependencies (topological sort)
49
- * 2. Init phase - plugins register services
50
- * 3. Start phase - plugins execute business logic
51
- * 4. Trigger 'kernel:ready' hook
170
+ * Register a service instance directly
52
171
  */
53
- async bootstrap(): Promise<void> {
54
- this.validateState('idle');
55
-
56
- this.state = 'initializing';
57
- this.logger.info('Bootstrap started');
172
+ registerService<T>(name: string, service: T): this {
173
+ if (this.services.has(name)) {
174
+ throw new Error(`[Kernel] Service '${name}' already registered`);
175
+ }
176
+ this.services.set(name, service);
177
+ this.pluginLoader.registerService(name, service);
178
+ this.logger.info(`Service '${name}' registered`, { service: name });
179
+ return this;
180
+ }
58
181
 
59
- // Resolve dependencies
60
- const orderedPlugins = this.resolveDependencies();
182
+ /**
183
+ * Register a service factory with lifecycle management
184
+ */
185
+ registerServiceFactory<T>(
186
+ name: string,
187
+ factory: ServiceFactory<T>,
188
+ lifecycle: ServiceLifecycle = ServiceLifecycle.SINGLETON,
189
+ dependencies?: string[]
190
+ ): this {
191
+ this.pluginLoader.registerServiceFactory({
192
+ name,
193
+ factory,
194
+ lifecycle,
195
+ dependencies,
196
+ });
197
+ return this;
198
+ }
61
199
 
62
- // Phase 1: Init - Plugins register services
63
- this.logger.info('Phase 1: Init plugins');
64
- for (const plugin of orderedPlugins) {
65
- await this.runPluginInit(plugin);
200
+ /**
201
+ * Validate Critical System Requirements
202
+ */
203
+ private validateSystemRequirements() {
204
+ if (this.config.skipSystemValidation) {
205
+ this.logger.debug('System requirement validation skipped');
206
+ return;
66
207
  }
67
208
 
68
- // Phase 2: Start - Plugins execute business logic
69
- this.logger.info('Phase 2: Start plugins');
70
- this.state = 'running';
209
+ this.logger.debug('Validating system service requirements...');
210
+ const missingServices: string[] = [];
211
+ const missingCoreServices: string[] = [];
71
212
 
72
- for (const plugin of orderedPlugins) {
73
- await this.runPluginStart(plugin);
213
+ // Iterate through all defined requirements
214
+ for (const [serviceName, criticality] of Object.entries(ServiceRequirementDef)) {
215
+ const hasService = this.services.has(serviceName) || this.pluginLoader.hasService(serviceName);
216
+
217
+ if (!hasService) {
218
+ if (criticality === 'required') {
219
+ this.logger.error(`CRITICAL: Required service missing: ${serviceName}`);
220
+ missingServices.push(serviceName);
221
+ } else if (criticality === 'core') {
222
+ this.logger.warn(`CORE: Core service missing, functionality may be degraded: ${serviceName}`);
223
+ missingCoreServices.push(serviceName);
224
+ } else {
225
+ this.logger.info(`Info: Optional service not present: ${serviceName}`);
226
+ }
227
+ }
74
228
  }
75
229
 
76
- // Trigger ready hook
77
- await this.triggerHook('kernel:ready');
78
- this.logger.info('✅ Bootstrap complete', {
79
- pluginCount: this.plugins.size
80
- });
230
+ if (missingServices.length > 0) {
231
+ const errorMsg = `System failed to start. Missing critical services: ${missingServices.join(', ')}`;
232
+ this.logger.error(errorMsg);
233
+ throw new Error(errorMsg);
234
+ }
235
+
236
+ if (missingCoreServices.length > 0) {
237
+ this.logger.warn(`System started with degraded capabilities. Missing core services: ${missingCoreServices.join(', ')}`);
238
+ }
239
+
240
+ this.logger.info('System requirement check passed');
81
241
  }
82
242
 
83
243
  /**
84
- * Shutdown the kernel
85
- * Calls destroy on all plugins in reverse order
244
+ * Bootstrap the kernel with enhanced features
86
245
  */
87
- async shutdown(): Promise<void> {
88
- await this.destroy();
246
+ async bootstrap(): Promise<void> {
247
+ if (this.state !== 'idle') {
248
+ throw new Error('[Kernel] Kernel already bootstrapped');
249
+ }
250
+
251
+ this.state = 'initializing';
252
+ this.logger.info('Bootstrap started');
253
+
254
+ try {
255
+ // Check for circular dependencies
256
+ const cycles = this.pluginLoader.detectCircularDependencies();
257
+ if (cycles.length > 0) {
258
+ this.logger.warn('Circular service dependencies detected:', { cycles });
259
+ }
260
+
261
+ // Resolve plugin dependencies
262
+ const orderedPlugins = this.resolveDependencies();
263
+
264
+ // Phase 1: Init - Plugins register services
265
+ this.logger.info('Phase 1: Init plugins');
266
+ for (const plugin of orderedPlugins) {
267
+ await this.initPluginWithTimeout(plugin);
268
+ }
269
+
270
+ // Phase 2: Start - Plugins execute business logic
271
+ this.logger.info('Phase 2: Start plugins');
272
+ this.state = 'running';
273
+
274
+ for (const plugin of orderedPlugins) {
275
+ const result = await this.startPluginWithTimeout(plugin);
276
+
277
+ if (!result.success) {
278
+ this.logger.error(`Plugin startup failed: ${plugin.name}`, result.error);
279
+
280
+ if (this.config.rollbackOnFailure) {
281
+ this.logger.warn('Rolling back started plugins...');
282
+ await this.rollbackStartedPlugins();
283
+ throw new Error(`Plugin ${plugin.name} failed to start - rollback complete`);
284
+ }
285
+ }
286
+ }
287
+
288
+ // Phase 3: Trigger kernel:ready hook
289
+ this.validateSystemRequirements(); // Final check before ready
290
+ this.logger.debug('Triggering kernel:ready hook');
291
+ await this.context.trigger('kernel:ready');
292
+
293
+ this.logger.info('✅ Bootstrap complete');
294
+ } catch (error) {
295
+ this.state = 'stopped';
296
+ throw error;
297
+ }
89
298
  }
90
299
 
91
300
  /**
92
- * Graceful shutdown - destroy all plugins in reverse order
301
+ * Graceful shutdown with timeout
93
302
  */
94
- async destroy(): Promise<void> {
95
- if (this.state === 'stopped') {
96
- this.logger.warn('Kernel already stopped');
303
+ async shutdown(): Promise<void> {
304
+ if (this.state === 'stopped' || this.state === 'stopping') {
305
+ this.logger.warn('Kernel already stopped or stopping');
97
306
  return;
98
307
  }
99
308
 
309
+ if (this.state !== 'running') {
310
+ throw new Error('[Kernel] Kernel not running');
311
+ }
312
+
100
313
  this.state = 'stopping';
101
- this.logger.info('Shutdown started');
314
+ this.logger.info('Graceful shutdown started');
102
315
 
103
- // Trigger shutdown hook
104
- await this.triggerHook('kernel:shutdown');
316
+ try {
317
+ // Create shutdown promise with timeout
318
+ const shutdownPromise = this.performShutdown();
319
+ const timeoutPromise = new Promise<void>((_, reject) => {
320
+ setTimeout(() => {
321
+ reject(new Error('Shutdown timeout exceeded'));
322
+ }, this.config.shutdownTimeout);
323
+ });
105
324
 
106
- // Destroy plugins in reverse order
107
- const orderedPlugins = this.resolveDependencies();
108
- for (const plugin of orderedPlugins.reverse()) {
109
- await this.runPluginDestroy(plugin);
325
+ // Race between shutdown and timeout
326
+ await Promise.race([shutdownPromise, timeoutPromise]);
327
+
328
+ this.state = 'stopped';
329
+ this.logger.info('✅ Graceful shutdown complete');
330
+ } catch (error) {
331
+ this.logger.error('Shutdown error - forcing stop', error as Error);
332
+ this.state = 'stopped';
333
+ throw error;
334
+ } finally {
335
+ // Cleanup logger resources
336
+ await this.logger.destroy();
110
337
  }
338
+ }
339
+
340
+ /**
341
+ * Check health of a specific plugin
342
+ */
343
+ async checkPluginHealth(pluginName: string): Promise<any> {
344
+ return await this.pluginLoader.checkPluginHealth(pluginName);
345
+ }
111
346
 
112
- this.state = 'stopped';
113
- this.logger.info('✅ Shutdown complete');
347
+ /**
348
+ * Check health of all plugins
349
+ */
350
+ async checkAllPluginsHealth(): Promise<Map<string, any>> {
351
+ const results = new Map();
114
352
 
115
- // Cleanup logger resources
116
- if (this.logger && typeof (this.logger as ObjectLogger).destroy === 'function') {
117
- await (this.logger as ObjectLogger).destroy();
353
+ for (const pluginName of this.plugins.keys()) {
354
+ const health = await this.checkPluginHealth(pluginName);
355
+ results.set(pluginName, health);
118
356
  }
357
+
358
+ return results;
359
+ }
360
+
361
+ /**
362
+ * Get plugin startup metrics
363
+ */
364
+ getPluginMetrics(): Map<string, number> {
365
+ return new Map(this.pluginStartTimes);
119
366
  }
120
367
 
121
368
  /**
122
- * Get a service from the registry
123
- * Convenience method for external access
369
+ * Get a service (sync helper)
124
370
  */
125
371
  getService<T>(name: string): T {
126
372
  return this.context.getService<T>(name);
127
373
  }
128
374
 
375
+ /**
376
+ * Get a service asynchronously (supports factories)
377
+ */
378
+ async getServiceAsync<T>(name: string, scopeId?: string): Promise<T> {
379
+ return await this.pluginLoader.getService<T>(name, scopeId);
380
+ }
381
+
129
382
  /**
130
383
  * Check if kernel is running
131
384
  */
132
385
  isRunning(): boolean {
133
386
  return this.state === 'running';
134
387
  }
388
+
389
+ /**
390
+ * Get kernel state
391
+ */
392
+ getState(): string {
393
+ return this.state;
394
+ }
395
+
396
+ // Private methods
397
+
398
+ private async initPluginWithTimeout(plugin: PluginMetadata): Promise<void> {
399
+ const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout!;
400
+
401
+ this.logger.debug(`Init: ${plugin.name}`, { plugin: plugin.name });
402
+
403
+ const initPromise = plugin.init(this.context);
404
+ const timeoutPromise = new Promise<void>((_, reject) => {
405
+ setTimeout(() => {
406
+ reject(new Error(`Plugin ${plugin.name} init timeout after ${timeout}ms`));
407
+ }, timeout);
408
+ });
409
+
410
+ await Promise.race([initPromise, timeoutPromise]);
411
+ }
412
+
413
+ private async startPluginWithTimeout(plugin: PluginMetadata): Promise<PluginStartupResult> {
414
+ if (!plugin.start) {
415
+ return { success: true, pluginName: plugin.name };
416
+ }
417
+
418
+ const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout!;
419
+ const startTime = Date.now();
420
+
421
+ this.logger.debug(`Start: ${plugin.name}`, { plugin: plugin.name });
422
+
423
+ try {
424
+ const startPromise = plugin.start(this.context);
425
+ const timeoutPromise = new Promise<void>((_, reject) => {
426
+ setTimeout(() => {
427
+ reject(new Error(`Plugin ${plugin.name} start timeout after ${timeout}ms`));
428
+ }, timeout);
429
+ });
430
+
431
+ await Promise.race([startPromise, timeoutPromise]);
432
+
433
+ const duration = Date.now() - startTime;
434
+ this.startedPlugins.add(plugin.name);
435
+ this.pluginStartTimes.set(plugin.name, duration);
436
+
437
+ this.logger.debug(`Plugin started: ${plugin.name} (${duration}ms)`);
438
+
439
+ return {
440
+ success: true,
441
+ pluginName: plugin.name,
442
+ startTime: duration,
443
+ };
444
+ } catch (error) {
445
+ const duration = Date.now() - startTime;
446
+ const isTimeout = (error as Error).message.includes('timeout');
447
+
448
+ return {
449
+ success: false,
450
+ pluginName: plugin.name,
451
+ error: error as Error,
452
+ startTime: duration,
453
+ timedOut: isTimeout,
454
+ };
455
+ }
456
+ }
457
+
458
+ private async rollbackStartedPlugins(): Promise<void> {
459
+ const pluginsToRollback = Array.from(this.startedPlugins).reverse();
460
+
461
+ for (const pluginName of pluginsToRollback) {
462
+ const plugin = this.plugins.get(pluginName);
463
+ if (plugin?.destroy) {
464
+ try {
465
+ this.logger.debug(`Rollback: ${pluginName}`);
466
+ await plugin.destroy();
467
+ } catch (error) {
468
+ this.logger.error(`Rollback failed for ${pluginName}`, error as Error);
469
+ }
470
+ }
471
+ }
472
+
473
+ this.startedPlugins.clear();
474
+ }
475
+
476
+ private async performShutdown(): Promise<void> {
477
+ // Trigger shutdown hook
478
+ await this.context.trigger('kernel:shutdown');
479
+
480
+ // Destroy plugins in reverse order
481
+ const orderedPlugins = Array.from(this.plugins.values()).reverse();
482
+ for (const plugin of orderedPlugins) {
483
+ if (plugin.destroy) {
484
+ this.logger.debug(`Destroy: ${plugin.name}`, { plugin: plugin.name });
485
+ try {
486
+ await plugin.destroy();
487
+ } catch (error) {
488
+ this.logger.error(`Error destroying plugin ${plugin.name}`, error as Error);
489
+ }
490
+ }
491
+ }
492
+
493
+ // Execute custom shutdown handlers
494
+ for (const handler of this.shutdownHandlers) {
495
+ try {
496
+ await handler();
497
+ } catch (error) {
498
+ this.logger.error('Shutdown handler error', error as Error);
499
+ }
500
+ }
501
+ }
502
+
503
+ private resolveDependencies(): PluginMetadata[] {
504
+ const resolved: PluginMetadata[] = [];
505
+ const visited = new Set<string>();
506
+ const visiting = new Set<string>();
507
+
508
+ const visit = (pluginName: string) => {
509
+ if (visited.has(pluginName)) return;
510
+
511
+ if (visiting.has(pluginName)) {
512
+ throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
513
+ }
514
+
515
+ const plugin = this.plugins.get(pluginName);
516
+ if (!plugin) {
517
+ throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
518
+ }
519
+
520
+ visiting.add(pluginName);
521
+
522
+ // Visit dependencies first
523
+ const deps = plugin.dependencies || [];
524
+ for (const dep of deps) {
525
+ if (!this.plugins.has(dep)) {
526
+ throw new Error(`[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`);
527
+ }
528
+ visit(dep);
529
+ }
530
+
531
+ visiting.delete(pluginName);
532
+ visited.add(pluginName);
533
+ resolved.push(plugin);
534
+ };
535
+
536
+ // Visit all plugins
537
+ for (const pluginName of this.plugins.keys()) {
538
+ visit(pluginName);
539
+ }
540
+
541
+ return resolved;
542
+ }
543
+
544
+ private registerShutdownSignals(): void {
545
+ const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
546
+ let shutdownInProgress = false;
547
+
548
+ const handleShutdown = async (signal: string) => {
549
+ if (shutdownInProgress) {
550
+ this.logger.warn(`Shutdown already in progress, ignoring ${signal}`);
551
+ return;
552
+ }
553
+
554
+ shutdownInProgress = true;
555
+ this.logger.info(`Received ${signal} - initiating graceful shutdown`);
556
+
557
+ try {
558
+ await this.shutdown();
559
+ process.exit(0);
560
+ } catch (error) {
561
+ this.logger.error('Shutdown failed', error as Error);
562
+ process.exit(1);
563
+ }
564
+ };
565
+
566
+ for (const signal of signals) {
567
+ process.on(signal, () => handleShutdown(signal));
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Register a custom shutdown handler
573
+ */
574
+ onShutdown(handler: () => Promise<void>): void {
575
+ this.shutdownHandlers.push(handler);
576
+ }
135
577
  }