@objectstack/core 4.0.3 → 4.0.5

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