@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,496 +0,0 @@
1
- import { Plugin, PluginContext } from './types.js';
2
- import { createLogger, ObjectLogger } from './logger.js';
3
- import type { LoggerConfig } from '@objectstack/spec/system';
4
- import { PluginLoader, PluginMetadata, ServiceLifecycle, ServiceFactory, PluginStartupResult } from './plugin-loader.js';
5
-
6
- /**
7
- * Enhanced Kernel Configuration
8
- */
9
- export interface EnhancedKernelConfig {
10
- logger?: Partial<LoggerConfig>;
11
-
12
- /** Default plugin startup timeout in milliseconds */
13
- defaultStartupTimeout?: number;
14
-
15
- /** Whether to enable graceful shutdown */
16
- gracefulShutdown?: boolean;
17
-
18
- /** Graceful shutdown timeout in milliseconds */
19
- shutdownTimeout?: number;
20
-
21
- /** Whether to rollback on startup failure */
22
- rollbackOnFailure?: boolean;
23
- }
24
-
25
- /**
26
- * Enhanced ObjectKernel with Advanced Plugin Management
27
- *
28
- * Extends the basic ObjectKernel with:
29
- * - Async plugin loading with validation
30
- * - Version compatibility checking
31
- * - Plugin signature verification
32
- * - Configuration validation (Zod)
33
- * - Factory-based dependency injection
34
- * - Service lifecycle management (singleton/transient/scoped)
35
- * - Circular dependency detection
36
- * - Lazy loading services
37
- * - Graceful shutdown
38
- * - Plugin startup timeout control
39
- * - Startup failure rollback
40
- * - Plugin health checks
41
- */
42
- export class EnhancedObjectKernel {
43
- private plugins: Map<string, PluginMetadata> = new Map();
44
- private services: Map<string, any> = new Map();
45
- private hooks: Map<string, Array<(...args: any[]) => void | Promise<void>>> = new Map();
46
- private state: 'idle' | 'initializing' | 'running' | 'stopping' | 'stopped' = 'idle';
47
- private logger: ObjectLogger;
48
- private context: PluginContext;
49
- private pluginLoader: PluginLoader;
50
- private config: EnhancedKernelConfig;
51
- private startedPlugins: Set<string> = new Set();
52
- private pluginStartTimes: Map<string, number> = new Map();
53
- private shutdownHandlers: Array<() => Promise<void>> = [];
54
-
55
- constructor(config: EnhancedKernelConfig = {}) {
56
- this.config = {
57
- defaultStartupTimeout: 30000, // 30 seconds
58
- gracefulShutdown: true,
59
- shutdownTimeout: 60000, // 60 seconds
60
- rollbackOnFailure: true,
61
- ...config,
62
- };
63
-
64
- this.logger = createLogger(config.logger);
65
- this.pluginLoader = new PluginLoader(this.logger);
66
-
67
- // Initialize context
68
- this.context = {
69
- registerService: (name, service) => {
70
- if (this.services.has(name)) {
71
- throw new Error(`[Kernel] Service '${name}' already registered`);
72
- }
73
- this.services.set(name, service);
74
- this.pluginLoader.registerService(name, service);
75
- this.logger.info(`Service '${name}' registered`, { service: name });
76
- },
77
- getService: <T>(name: string) => {
78
- // Try to get from plugin loader (supports factories and lifecycle)
79
- try {
80
- const service = this.pluginLoader.getService(name);
81
- if (service instanceof Promise) {
82
- throw new Error(`Service '${name}' is async - use await`);
83
- }
84
- return service as T;
85
- } catch {
86
- // Fall back to direct service map
87
- const service = this.services.get(name);
88
- if (!service) {
89
- throw new Error(`[Kernel] Service '${name}' not found`);
90
- }
91
- return service as T;
92
- }
93
- },
94
- hook: (name, handler) => {
95
- if (!this.hooks.has(name)) {
96
- this.hooks.set(name, []);
97
- }
98
- this.hooks.get(name)!.push(handler);
99
- },
100
- trigger: async (name, ...args) => {
101
- const handlers = this.hooks.get(name) || [];
102
- for (const handler of handlers) {
103
- await handler(...args);
104
- }
105
- },
106
- getServices: () => {
107
- return new Map(this.services);
108
- },
109
- logger: this.logger,
110
- getKernel: () => this as any, // Type compatibility
111
- };
112
-
113
- // Register shutdown handler
114
- if (this.config.gracefulShutdown) {
115
- this.registerShutdownSignals();
116
- }
117
- }
118
-
119
- /**
120
- * Register a plugin with enhanced validation
121
- */
122
- async use(plugin: Plugin): Promise<this> {
123
- if (this.state !== 'idle') {
124
- throw new Error('[Kernel] Cannot register plugins after bootstrap has started');
125
- }
126
-
127
- // Load plugin through enhanced loader
128
- const result = await this.pluginLoader.loadPlugin(plugin);
129
-
130
- if (!result.success || !result.plugin) {
131
- throw new Error(`Failed to load plugin: ${plugin.name} - ${result.error?.message}`);
132
- }
133
-
134
- const pluginMeta = result.plugin;
135
- this.plugins.set(pluginMeta.name, pluginMeta);
136
-
137
- this.logger.info(`Plugin registered: ${pluginMeta.name}@${pluginMeta.version}`, {
138
- plugin: pluginMeta.name,
139
- version: pluginMeta.version,
140
- });
141
-
142
- return this;
143
- }
144
-
145
- /**
146
- * Register a service factory with lifecycle management
147
- */
148
- registerServiceFactory<T>(
149
- name: string,
150
- factory: ServiceFactory<T>,
151
- lifecycle: ServiceLifecycle = ServiceLifecycle.SINGLETON,
152
- dependencies?: string[]
153
- ): this {
154
- this.pluginLoader.registerServiceFactory({
155
- name,
156
- factory,
157
- lifecycle,
158
- dependencies,
159
- });
160
- return this;
161
- }
162
-
163
- /**
164
- * Bootstrap the kernel with enhanced features
165
- */
166
- async bootstrap(): Promise<void> {
167
- if (this.state !== 'idle') {
168
- throw new Error('[Kernel] Kernel already bootstrapped');
169
- }
170
-
171
- this.state = 'initializing';
172
- this.logger.info('Bootstrap started');
173
-
174
- try {
175
- // Check for circular dependencies
176
- const cycles = this.pluginLoader.detectCircularDependencies();
177
- if (cycles.length > 0) {
178
- this.logger.warn('Circular service dependencies detected:', { cycles });
179
- }
180
-
181
- // Resolve plugin dependencies
182
- const orderedPlugins = this.resolveDependencies();
183
-
184
- // Phase 1: Init - Plugins register services
185
- this.logger.info('Phase 1: Init plugins');
186
- for (const plugin of orderedPlugins) {
187
- await this.initPluginWithTimeout(plugin);
188
- }
189
-
190
- // Phase 2: Start - Plugins execute business logic
191
- this.logger.info('Phase 2: Start plugins');
192
- this.state = 'running';
193
-
194
- for (const plugin of orderedPlugins) {
195
- const result = await this.startPluginWithTimeout(plugin);
196
-
197
- if (!result.success) {
198
- this.logger.error(`Plugin startup failed: ${plugin.name}`, result.error);
199
-
200
- if (this.config.rollbackOnFailure) {
201
- this.logger.warn('Rolling back started plugins...');
202
- await this.rollbackStartedPlugins();
203
- throw new Error(`Plugin ${plugin.name} failed to start - rollback complete`);
204
- }
205
- }
206
- }
207
-
208
- // Phase 3: Trigger kernel:ready hook
209
- this.logger.debug('Triggering kernel:ready hook');
210
- await this.context.trigger('kernel:ready');
211
-
212
- this.logger.info('✅ Bootstrap complete');
213
- } catch (error) {
214
- this.state = 'stopped';
215
- throw error;
216
- }
217
- }
218
-
219
- /**
220
- * Graceful shutdown with timeout
221
- */
222
- async shutdown(): Promise<void> {
223
- if (this.state === 'stopped' || this.state === 'stopping') {
224
- this.logger.warn('Kernel already stopped or stopping');
225
- return;
226
- }
227
-
228
- if (this.state !== 'running') {
229
- throw new Error('[Kernel] Kernel not running');
230
- }
231
-
232
- this.state = 'stopping';
233
- this.logger.info('Graceful shutdown started');
234
-
235
- try {
236
- // Create shutdown promise with timeout
237
- const shutdownPromise = this.performShutdown();
238
- const timeoutPromise = new Promise<void>((_, reject) => {
239
- setTimeout(() => {
240
- reject(new Error('Shutdown timeout exceeded'));
241
- }, this.config.shutdownTimeout);
242
- });
243
-
244
- // Race between shutdown and timeout
245
- await Promise.race([shutdownPromise, timeoutPromise]);
246
-
247
- this.state = 'stopped';
248
- this.logger.info('✅ Graceful shutdown complete');
249
- } catch (error) {
250
- this.logger.error('Shutdown error - forcing stop', error as Error);
251
- this.state = 'stopped';
252
- throw error;
253
- } finally {
254
- // Cleanup logger resources
255
- await this.logger.destroy();
256
- }
257
- }
258
-
259
- /**
260
- * Check health of a specific plugin
261
- */
262
- async checkPluginHealth(pluginName: string): Promise<any> {
263
- return await this.pluginLoader.checkPluginHealth(pluginName);
264
- }
265
-
266
- /**
267
- * Check health of all plugins
268
- */
269
- async checkAllPluginsHealth(): Promise<Map<string, any>> {
270
- const results = new Map();
271
-
272
- for (const pluginName of this.plugins.keys()) {
273
- const health = await this.checkPluginHealth(pluginName);
274
- results.set(pluginName, health);
275
- }
276
-
277
- return results;
278
- }
279
-
280
- /**
281
- * Get plugin startup metrics
282
- */
283
- getPluginMetrics(): Map<string, number> {
284
- return new Map(this.pluginStartTimes);
285
- }
286
-
287
- /**
288
- * Get a service (sync helper)
289
- */
290
- getService<T>(name: string): T {
291
- return this.context.getService<T>(name);
292
- }
293
-
294
- /**
295
- * Get a service asynchronously (supports factories)
296
- */
297
- async getServiceAsync<T>(name: string, scopeId?: string): Promise<T> {
298
- return await this.pluginLoader.getService<T>(name, scopeId);
299
- }
300
-
301
- /**
302
- * Check if kernel is running
303
- */
304
- isRunning(): boolean {
305
- return this.state === 'running';
306
- }
307
-
308
- /**
309
- * Get kernel state
310
- */
311
- getState(): string {
312
- return this.state;
313
- }
314
-
315
- // Private methods
316
-
317
- private async initPluginWithTimeout(plugin: PluginMetadata): Promise<void> {
318
- const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout!;
319
-
320
- this.logger.debug(`Init: ${plugin.name}`, { plugin: plugin.name });
321
-
322
- const initPromise = plugin.init(this.context);
323
- const timeoutPromise = new Promise<void>((_, reject) => {
324
- setTimeout(() => {
325
- reject(new Error(`Plugin ${plugin.name} init timeout after ${timeout}ms`));
326
- }, timeout);
327
- });
328
-
329
- await Promise.race([initPromise, timeoutPromise]);
330
- }
331
-
332
- private async startPluginWithTimeout(plugin: PluginMetadata): Promise<PluginStartupResult> {
333
- if (!plugin.start) {
334
- return { success: true, pluginName: plugin.name };
335
- }
336
-
337
- const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout!;
338
- const startTime = Date.now();
339
-
340
- this.logger.debug(`Start: ${plugin.name}`, { plugin: plugin.name });
341
-
342
- try {
343
- const startPromise = plugin.start(this.context);
344
- const timeoutPromise = new Promise<void>((_, reject) => {
345
- setTimeout(() => {
346
- reject(new Error(`Plugin ${plugin.name} start timeout after ${timeout}ms`));
347
- }, timeout);
348
- });
349
-
350
- await Promise.race([startPromise, timeoutPromise]);
351
-
352
- const duration = Date.now() - startTime;
353
- this.startedPlugins.add(plugin.name);
354
- this.pluginStartTimes.set(plugin.name, duration);
355
-
356
- this.logger.debug(`Plugin started: ${plugin.name} (${duration}ms)`);
357
-
358
- return {
359
- success: true,
360
- pluginName: plugin.name,
361
- startTime: duration,
362
- };
363
- } catch (error) {
364
- const duration = Date.now() - startTime;
365
- const isTimeout = (error as Error).message.includes('timeout');
366
-
367
- return {
368
- success: false,
369
- pluginName: plugin.name,
370
- error: error as Error,
371
- startTime: duration,
372
- timedOut: isTimeout,
373
- };
374
- }
375
- }
376
-
377
- private async rollbackStartedPlugins(): Promise<void> {
378
- const pluginsToRollback = Array.from(this.startedPlugins).reverse();
379
-
380
- for (const pluginName of pluginsToRollback) {
381
- const plugin = this.plugins.get(pluginName);
382
- if (plugin?.destroy) {
383
- try {
384
- this.logger.debug(`Rollback: ${pluginName}`);
385
- await plugin.destroy();
386
- } catch (error) {
387
- this.logger.error(`Rollback failed for ${pluginName}`, error as Error);
388
- }
389
- }
390
- }
391
-
392
- this.startedPlugins.clear();
393
- }
394
-
395
- private async performShutdown(): Promise<void> {
396
- // Trigger shutdown hook
397
- await this.context.trigger('kernel:shutdown');
398
-
399
- // Destroy plugins in reverse order
400
- const orderedPlugins = Array.from(this.plugins.values()).reverse();
401
- for (const plugin of orderedPlugins) {
402
- if (plugin.destroy) {
403
- this.logger.debug(`Destroy: ${plugin.name}`, { plugin: plugin.name });
404
- try {
405
- await plugin.destroy();
406
- } catch (error) {
407
- this.logger.error(`Error destroying plugin ${plugin.name}`, error as Error);
408
- }
409
- }
410
- }
411
-
412
- // Execute custom shutdown handlers
413
- for (const handler of this.shutdownHandlers) {
414
- try {
415
- await handler();
416
- } catch (error) {
417
- this.logger.error('Shutdown handler error', error as Error);
418
- }
419
- }
420
- }
421
-
422
- private resolveDependencies(): PluginMetadata[] {
423
- const resolved: PluginMetadata[] = [];
424
- const visited = new Set<string>();
425
- const visiting = new Set<string>();
426
-
427
- const visit = (pluginName: string) => {
428
- if (visited.has(pluginName)) return;
429
-
430
- if (visiting.has(pluginName)) {
431
- throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
432
- }
433
-
434
- const plugin = this.plugins.get(pluginName);
435
- if (!plugin) {
436
- throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
437
- }
438
-
439
- visiting.add(pluginName);
440
-
441
- // Visit dependencies first
442
- const deps = plugin.dependencies || [];
443
- for (const dep of deps) {
444
- if (!this.plugins.has(dep)) {
445
- throw new Error(`[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`);
446
- }
447
- visit(dep);
448
- }
449
-
450
- visiting.delete(pluginName);
451
- visited.add(pluginName);
452
- resolved.push(plugin);
453
- };
454
-
455
- // Visit all plugins
456
- for (const pluginName of this.plugins.keys()) {
457
- visit(pluginName);
458
- }
459
-
460
- return resolved;
461
- }
462
-
463
- private registerShutdownSignals(): void {
464
- const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
465
- let shutdownInProgress = false;
466
-
467
- const handleShutdown = async (signal: string) => {
468
- if (shutdownInProgress) {
469
- this.logger.warn(`Shutdown already in progress, ignoring ${signal}`);
470
- return;
471
- }
472
-
473
- shutdownInProgress = true;
474
- this.logger.info(`Received ${signal} - initiating graceful shutdown`);
475
-
476
- try {
477
- await this.shutdown();
478
- process.exit(0);
479
- } catch (error) {
480
- this.logger.error('Shutdown failed', error as Error);
481
- process.exit(1);
482
- }
483
- };
484
-
485
- for (const signal of signals) {
486
- process.on(signal, () => handleShutdown(signal));
487
- }
488
- }
489
-
490
- /**
491
- * Register a custom shutdown handler
492
- */
493
- onShutdown(handler: () => Promise<void>): void {
494
- this.shutdownHandlers.push(handler);
495
- }
496
- }