@objectstack/core 0.9.1 → 0.9.2

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 (89) hide show
  1. package/{ENHANCED_FEATURES.md → ADVANCED_FEATURES.md} +13 -13
  2. package/CHANGELOG.md +7 -0
  3. package/PHASE2_IMPLEMENTATION.md +388 -0
  4. package/README.md +24 -11
  5. package/REFACTORING_SUMMARY.md +40 -0
  6. package/dist/api-registry-plugin.test.js +20 -20
  7. package/dist/dependency-resolver.d.ts +62 -0
  8. package/dist/dependency-resolver.d.ts.map +1 -0
  9. package/dist/dependency-resolver.js +317 -0
  10. package/dist/dependency-resolver.test.d.ts +2 -0
  11. package/dist/dependency-resolver.test.d.ts.map +1 -0
  12. package/dist/dependency-resolver.test.js +241 -0
  13. package/dist/health-monitor.d.ts +65 -0
  14. package/dist/health-monitor.d.ts.map +1 -0
  15. package/dist/health-monitor.js +269 -0
  16. package/dist/health-monitor.test.d.ts +2 -0
  17. package/dist/health-monitor.test.d.ts.map +1 -0
  18. package/dist/health-monitor.test.js +68 -0
  19. package/dist/hot-reload.d.ts +79 -0
  20. package/dist/hot-reload.d.ts.map +1 -0
  21. package/dist/hot-reload.js +313 -0
  22. package/dist/index.d.ts +4 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +5 -1
  25. package/dist/kernel-base.d.ts +2 -2
  26. package/dist/kernel-base.js +2 -2
  27. package/dist/kernel.d.ts +79 -31
  28. package/dist/kernel.d.ts.map +1 -1
  29. package/dist/kernel.js +383 -73
  30. package/dist/kernel.test.js +373 -122
  31. package/dist/lite-kernel.d.ts +55 -0
  32. package/dist/lite-kernel.d.ts.map +1 -0
  33. package/dist/lite-kernel.js +112 -0
  34. package/dist/lite-kernel.test.d.ts +2 -0
  35. package/dist/lite-kernel.test.d.ts.map +1 -0
  36. package/dist/lite-kernel.test.js +161 -0
  37. package/dist/logger.d.ts +2 -2
  38. package/dist/logger.d.ts.map +1 -1
  39. package/dist/logger.js +26 -7
  40. package/dist/plugin-loader.d.ts +11 -0
  41. package/dist/plugin-loader.d.ts.map +1 -1
  42. package/dist/plugin-loader.js +34 -10
  43. package/dist/plugin-loader.test.js +9 -0
  44. package/dist/security/index.d.ts +3 -0
  45. package/dist/security/index.d.ts.map +1 -1
  46. package/dist/security/index.js +4 -0
  47. package/dist/security/permission-manager.d.ts +96 -0
  48. package/dist/security/permission-manager.d.ts.map +1 -0
  49. package/dist/security/permission-manager.js +235 -0
  50. package/dist/security/permission-manager.test.d.ts +2 -0
  51. package/dist/security/permission-manager.test.d.ts.map +1 -0
  52. package/dist/security/permission-manager.test.js +220 -0
  53. package/dist/security/sandbox-runtime.d.ts +115 -0
  54. package/dist/security/sandbox-runtime.d.ts.map +1 -0
  55. package/dist/security/sandbox-runtime.js +310 -0
  56. package/dist/security/security-scanner.d.ts +92 -0
  57. package/dist/security/security-scanner.d.ts.map +1 -0
  58. package/dist/security/security-scanner.js +273 -0
  59. package/examples/{enhanced-kernel-example.ts → kernel-features-example.ts} +6 -6
  60. package/examples/phase2-integration.ts +355 -0
  61. package/package.json +2 -2
  62. package/src/api-registry-plugin.test.ts +20 -20
  63. package/src/dependency-resolver.test.ts +287 -0
  64. package/src/dependency-resolver.ts +388 -0
  65. package/src/health-monitor.test.ts +81 -0
  66. package/src/health-monitor.ts +316 -0
  67. package/src/hot-reload.ts +388 -0
  68. package/src/index.ts +6 -1
  69. package/src/kernel-base.ts +2 -2
  70. package/src/kernel.test.ts +469 -134
  71. package/src/kernel.ts +464 -78
  72. package/src/lite-kernel.test.ts +200 -0
  73. package/src/lite-kernel.ts +135 -0
  74. package/src/logger.ts +28 -7
  75. package/src/plugin-loader.test.ts +10 -1
  76. package/src/plugin-loader.ts +42 -13
  77. package/src/security/index.ts +19 -0
  78. package/src/security/permission-manager.test.ts +256 -0
  79. package/src/security/permission-manager.ts +336 -0
  80. package/src/security/sandbox-runtime.ts +432 -0
  81. package/src/security/security-scanner.ts +365 -0
  82. package/dist/enhanced-kernel.d.ts +0 -103
  83. package/dist/enhanced-kernel.d.ts.map +0 -1
  84. package/dist/enhanced-kernel.js +0 -403
  85. package/dist/enhanced-kernel.test.d.ts +0 -2
  86. package/dist/enhanced-kernel.test.d.ts.map +0 -1
  87. package/dist/enhanced-kernel.test.js +0 -412
  88. package/src/enhanced-kernel.test.ts +0 -535
  89. package/src/enhanced-kernel.ts +0 -496
package/dist/kernel.js CHANGED
@@ -1,112 +1,422 @@
1
1
  import { createLogger } from './logger.js';
2
- import { ObjectKernelBase } from './kernel-base.js';
2
+ import { PluginLoader, ServiceLifecycle } from './plugin-loader.js';
3
3
  /**
4
- * ObjectKernel - MiniKernel Architecture
4
+ * Enhanced ObjectKernel with Advanced Plugin Management
5
5
  *
6
- * A highly modular, plugin-based microkernel that:
7
- * - Manages plugin lifecycle (init, start, destroy)
8
- * - Provides dependency injection via service registry
9
- * - Implements event/hook system for inter-plugin communication
10
- * - Handles dependency resolution (topological sort)
11
- * - Provides configurable logging for server and browser
12
- *
13
- * Core philosophy:
14
- * - Business logic is completely separated into plugins
15
- * - Kernel only manages lifecycle, DI, and hooks
16
- * - Plugins are loaded as equal building blocks
6
+ * Extends the basic ObjectKernel with:
7
+ * - Async plugin loading with validation
8
+ * - Version compatibility checking
9
+ * - Plugin signature verification
10
+ * - Configuration validation (Zod)
11
+ * - Factory-based dependency injection
12
+ * - Service lifecycle management (singleton/transient/scoped)
13
+ * - Circular dependency detection
14
+ * - Lazy loading services
15
+ * - Graceful shutdown
16
+ * - Plugin startup timeout control
17
+ * - Startup failure rollback
18
+ * - Plugin health checks
17
19
  */
18
- export class ObjectKernel extends ObjectKernelBase {
19
- constructor(config) {
20
- const logger = createLogger(config?.logger);
21
- super(logger);
22
- // Initialize context after logger is created
23
- this.context = this.createContext();
20
+ export class ObjectKernel {
21
+ constructor(config = {}) {
22
+ this.plugins = new Map();
23
+ this.services = new Map();
24
+ this.hooks = new Map();
25
+ this.state = 'idle';
26
+ this.startedPlugins = new Set();
27
+ this.pluginStartTimes = new Map();
28
+ this.shutdownHandlers = [];
29
+ this.config = {
30
+ defaultStartupTimeout: 30000, // 30 seconds
31
+ gracefulShutdown: true,
32
+ shutdownTimeout: 60000, // 60 seconds
33
+ rollbackOnFailure: true,
34
+ ...config,
35
+ };
36
+ this.logger = createLogger(config.logger);
37
+ this.pluginLoader = new PluginLoader(this.logger);
38
+ // Initialize context
39
+ this.context = {
40
+ registerService: (name, service) => {
41
+ if (this.services.has(name)) {
42
+ throw new Error(`[Kernel] Service '${name}' already registered`);
43
+ }
44
+ this.services.set(name, service);
45
+ this.pluginLoader.registerService(name, service);
46
+ this.logger.info(`Service '${name}' registered`, { service: name });
47
+ },
48
+ getService: (name) => {
49
+ // 1. Try direct service map first (synchronous cache)
50
+ const service = this.services.get(name);
51
+ if (service) {
52
+ return service;
53
+ }
54
+ // 2. Try to get from plugin loader cache (Sync access to factories)
55
+ const loaderService = this.pluginLoader.getServiceInstance(name);
56
+ if (loaderService) {
57
+ // Cache it locally for faster next access
58
+ this.services.set(name, loaderService);
59
+ return loaderService;
60
+ }
61
+ // 3. Try to get from plugin loader (support async factories)
62
+ try {
63
+ const service = this.pluginLoader.getService(name);
64
+ if (service instanceof Promise) {
65
+ // If we found it in the loader but not in the sync map, it's likely a factory-based service or still loading
66
+ throw new Error(`Service '${name}' is async - use await`);
67
+ }
68
+ return service;
69
+ }
70
+ catch (error) {
71
+ if (error.message?.includes('is async')) {
72
+ throw error;
73
+ }
74
+ // Re-throw critical factory errors instead of masking them as "not found"
75
+ // If the error came from the factory execution (e.g. database connection failed), we must see it.
76
+ // "Service '${name}' not found" comes from PluginLoader.getService fallback.
77
+ const isNotFoundError = error.message === `Service '${name}' not found`;
78
+ if (!isNotFoundError) {
79
+ throw error;
80
+ }
81
+ throw new Error(`[Kernel] Service '${name}' not found`);
82
+ }
83
+ },
84
+ hook: (name, handler) => {
85
+ if (!this.hooks.has(name)) {
86
+ this.hooks.set(name, []);
87
+ }
88
+ this.hooks.get(name).push(handler);
89
+ },
90
+ trigger: async (name, ...args) => {
91
+ const handlers = this.hooks.get(name) || [];
92
+ for (const handler of handlers) {
93
+ await handler(...args);
94
+ }
95
+ },
96
+ getServices: () => {
97
+ return new Map(this.services);
98
+ },
99
+ logger: this.logger,
100
+ getKernel: () => this, // Type compatibility
101
+ };
102
+ this.pluginLoader.setContext(this.context);
103
+ // Register shutdown handler
104
+ if (this.config.gracefulShutdown) {
105
+ this.registerShutdownSignals();
106
+ }
24
107
  }
25
108
  /**
26
- * Register a plugin
27
- * @param plugin - Plugin instance
109
+ * Register a plugin with enhanced validation
28
110
  */
29
- use(plugin) {
30
- this.validateIdle();
31
- const pluginName = plugin.name;
32
- if (this.plugins.has(pluginName)) {
33
- throw new Error(`[Kernel] Plugin '${pluginName}' already registered`);
111
+ async use(plugin) {
112
+ if (this.state !== 'idle') {
113
+ throw new Error('[Kernel] Cannot register plugins after bootstrap has started');
34
114
  }
35
- this.plugins.set(pluginName, plugin);
115
+ // Load plugin through enhanced loader
116
+ const result = await this.pluginLoader.loadPlugin(plugin);
117
+ if (!result.success || !result.plugin) {
118
+ throw new Error(`Failed to load plugin: ${plugin.name} - ${result.error?.message}`);
119
+ }
120
+ const pluginMeta = result.plugin;
121
+ this.plugins.set(pluginMeta.name, pluginMeta);
122
+ this.logger.info(`Plugin registered: ${pluginMeta.name}@${pluginMeta.version}`, {
123
+ plugin: pluginMeta.name,
124
+ version: pluginMeta.version,
125
+ });
36
126
  return this;
37
127
  }
38
128
  /**
39
- * Bootstrap the kernel
40
- * 1. Resolve dependencies (topological sort)
41
- * 2. Init phase - plugins register services
42
- * 3. Start phase - plugins execute business logic
43
- * 4. Trigger 'kernel:ready' hook
129
+ * Register a service factory with lifecycle management
130
+ */
131
+ registerServiceFactory(name, factory, lifecycle = ServiceLifecycle.SINGLETON, dependencies) {
132
+ this.pluginLoader.registerServiceFactory({
133
+ name,
134
+ factory,
135
+ lifecycle,
136
+ dependencies,
137
+ });
138
+ return this;
139
+ }
140
+ /**
141
+ * Bootstrap the kernel with enhanced features
44
142
  */
45
143
  async bootstrap() {
46
- this.validateState('idle');
144
+ if (this.state !== 'idle') {
145
+ throw new Error('[Kernel] Kernel already bootstrapped');
146
+ }
47
147
  this.state = 'initializing';
48
148
  this.logger.info('Bootstrap started');
49
- // Resolve dependencies
50
- const orderedPlugins = this.resolveDependencies();
51
- // Phase 1: Init - Plugins register services
52
- this.logger.info('Phase 1: Init plugins');
53
- for (const plugin of orderedPlugins) {
54
- await this.runPluginInit(plugin);
149
+ try {
150
+ // Check for circular dependencies
151
+ const cycles = this.pluginLoader.detectCircularDependencies();
152
+ if (cycles.length > 0) {
153
+ this.logger.warn('Circular service dependencies detected:', { cycles });
154
+ }
155
+ // Resolve plugin dependencies
156
+ const orderedPlugins = this.resolveDependencies();
157
+ // Phase 1: Init - Plugins register services
158
+ this.logger.info('Phase 1: Init plugins');
159
+ for (const plugin of orderedPlugins) {
160
+ await this.initPluginWithTimeout(plugin);
161
+ }
162
+ // Phase 2: Start - Plugins execute business logic
163
+ this.logger.info('Phase 2: Start plugins');
164
+ this.state = 'running';
165
+ for (const plugin of orderedPlugins) {
166
+ const result = await this.startPluginWithTimeout(plugin);
167
+ if (!result.success) {
168
+ this.logger.error(`Plugin startup failed: ${plugin.name}`, result.error);
169
+ if (this.config.rollbackOnFailure) {
170
+ this.logger.warn('Rolling back started plugins...');
171
+ await this.rollbackStartedPlugins();
172
+ throw new Error(`Plugin ${plugin.name} failed to start - rollback complete`);
173
+ }
174
+ }
175
+ }
176
+ // Phase 3: Trigger kernel:ready hook
177
+ this.logger.debug('Triggering kernel:ready hook');
178
+ await this.context.trigger('kernel:ready');
179
+ this.logger.info('✅ Bootstrap complete');
55
180
  }
56
- // Phase 2: Start - Plugins execute business logic
57
- this.logger.info('Phase 2: Start plugins');
58
- this.state = 'running';
59
- for (const plugin of orderedPlugins) {
60
- await this.runPluginStart(plugin);
181
+ catch (error) {
182
+ this.state = 'stopped';
183
+ throw error;
61
184
  }
62
- // Trigger ready hook
63
- await this.triggerHook('kernel:ready');
64
- this.logger.info('✅ Bootstrap complete', {
65
- pluginCount: this.plugins.size
66
- });
67
185
  }
68
186
  /**
69
- * Shutdown the kernel
70
- * Calls destroy on all plugins in reverse order
187
+ * Graceful shutdown with timeout
71
188
  */
72
189
  async shutdown() {
73
- await this.destroy();
74
- }
75
- /**
76
- * Graceful shutdown - destroy all plugins in reverse order
77
- */
78
- async destroy() {
79
- if (this.state === 'stopped') {
80
- this.logger.warn('Kernel already stopped');
190
+ if (this.state === 'stopped' || this.state === 'stopping') {
191
+ this.logger.warn('Kernel already stopped or stopping');
81
192
  return;
82
193
  }
194
+ if (this.state !== 'running') {
195
+ throw new Error('[Kernel] Kernel not running');
196
+ }
83
197
  this.state = 'stopping';
84
- this.logger.info('Shutdown started');
85
- // Trigger shutdown hook
86
- await this.triggerHook('kernel:shutdown');
87
- // Destroy plugins in reverse order
88
- const orderedPlugins = this.resolveDependencies();
89
- for (const plugin of orderedPlugins.reverse()) {
90
- await this.runPluginDestroy(plugin);
91
- }
92
- this.state = 'stopped';
93
- this.logger.info('✅ Shutdown complete');
94
- // Cleanup logger resources
95
- if (this.logger && typeof this.logger.destroy === 'function') {
198
+ this.logger.info('Graceful shutdown started');
199
+ try {
200
+ // Create shutdown promise with timeout
201
+ const shutdownPromise = this.performShutdown();
202
+ const timeoutPromise = new Promise((_, reject) => {
203
+ setTimeout(() => {
204
+ reject(new Error('Shutdown timeout exceeded'));
205
+ }, this.config.shutdownTimeout);
206
+ });
207
+ // Race between shutdown and timeout
208
+ await Promise.race([shutdownPromise, timeoutPromise]);
209
+ this.state = 'stopped';
210
+ this.logger.info('✅ Graceful shutdown complete');
211
+ }
212
+ catch (error) {
213
+ this.logger.error('Shutdown error - forcing stop', error);
214
+ this.state = 'stopped';
215
+ throw error;
216
+ }
217
+ finally {
218
+ // Cleanup logger resources
96
219
  await this.logger.destroy();
97
220
  }
98
221
  }
99
222
  /**
100
- * Get a service from the registry
101
- * Convenience method for external access
223
+ * Check health of a specific plugin
224
+ */
225
+ async checkPluginHealth(pluginName) {
226
+ return await this.pluginLoader.checkPluginHealth(pluginName);
227
+ }
228
+ /**
229
+ * Check health of all plugins
230
+ */
231
+ async checkAllPluginsHealth() {
232
+ const results = new Map();
233
+ for (const pluginName of this.plugins.keys()) {
234
+ const health = await this.checkPluginHealth(pluginName);
235
+ results.set(pluginName, health);
236
+ }
237
+ return results;
238
+ }
239
+ /**
240
+ * Get plugin startup metrics
241
+ */
242
+ getPluginMetrics() {
243
+ return new Map(this.pluginStartTimes);
244
+ }
245
+ /**
246
+ * Get a service (sync helper)
102
247
  */
103
248
  getService(name) {
104
249
  return this.context.getService(name);
105
250
  }
251
+ /**
252
+ * Get a service asynchronously (supports factories)
253
+ */
254
+ async getServiceAsync(name, scopeId) {
255
+ return await this.pluginLoader.getService(name, scopeId);
256
+ }
106
257
  /**
107
258
  * Check if kernel is running
108
259
  */
109
260
  isRunning() {
110
261
  return this.state === 'running';
111
262
  }
263
+ /**
264
+ * Get kernel state
265
+ */
266
+ getState() {
267
+ return this.state;
268
+ }
269
+ // Private methods
270
+ async initPluginWithTimeout(plugin) {
271
+ const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout;
272
+ this.logger.debug(`Init: ${plugin.name}`, { plugin: plugin.name });
273
+ const initPromise = plugin.init(this.context);
274
+ const timeoutPromise = new Promise((_, reject) => {
275
+ setTimeout(() => {
276
+ reject(new Error(`Plugin ${plugin.name} init timeout after ${timeout}ms`));
277
+ }, timeout);
278
+ });
279
+ await Promise.race([initPromise, timeoutPromise]);
280
+ }
281
+ async startPluginWithTimeout(plugin) {
282
+ if (!plugin.start) {
283
+ return { success: true, pluginName: plugin.name };
284
+ }
285
+ const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout;
286
+ const startTime = Date.now();
287
+ this.logger.debug(`Start: ${plugin.name}`, { plugin: plugin.name });
288
+ try {
289
+ const startPromise = plugin.start(this.context);
290
+ const timeoutPromise = new Promise((_, reject) => {
291
+ setTimeout(() => {
292
+ reject(new Error(`Plugin ${plugin.name} start timeout after ${timeout}ms`));
293
+ }, timeout);
294
+ });
295
+ await Promise.race([startPromise, timeoutPromise]);
296
+ const duration = Date.now() - startTime;
297
+ this.startedPlugins.add(plugin.name);
298
+ this.pluginStartTimes.set(plugin.name, duration);
299
+ this.logger.debug(`Plugin started: ${plugin.name} (${duration}ms)`);
300
+ return {
301
+ success: true,
302
+ pluginName: plugin.name,
303
+ startTime: duration,
304
+ };
305
+ }
306
+ catch (error) {
307
+ const duration = Date.now() - startTime;
308
+ const isTimeout = error.message.includes('timeout');
309
+ return {
310
+ success: false,
311
+ pluginName: plugin.name,
312
+ error: error,
313
+ startTime: duration,
314
+ timedOut: isTimeout,
315
+ };
316
+ }
317
+ }
318
+ async rollbackStartedPlugins() {
319
+ const pluginsToRollback = Array.from(this.startedPlugins).reverse();
320
+ for (const pluginName of pluginsToRollback) {
321
+ const plugin = this.plugins.get(pluginName);
322
+ if (plugin?.destroy) {
323
+ try {
324
+ this.logger.debug(`Rollback: ${pluginName}`);
325
+ await plugin.destroy();
326
+ }
327
+ catch (error) {
328
+ this.logger.error(`Rollback failed for ${pluginName}`, error);
329
+ }
330
+ }
331
+ }
332
+ this.startedPlugins.clear();
333
+ }
334
+ async performShutdown() {
335
+ // Trigger shutdown hook
336
+ await this.context.trigger('kernel:shutdown');
337
+ // Destroy plugins in reverse order
338
+ const orderedPlugins = Array.from(this.plugins.values()).reverse();
339
+ for (const plugin of orderedPlugins) {
340
+ if (plugin.destroy) {
341
+ this.logger.debug(`Destroy: ${plugin.name}`, { plugin: plugin.name });
342
+ try {
343
+ await plugin.destroy();
344
+ }
345
+ catch (error) {
346
+ this.logger.error(`Error destroying plugin ${plugin.name}`, error);
347
+ }
348
+ }
349
+ }
350
+ // Execute custom shutdown handlers
351
+ for (const handler of this.shutdownHandlers) {
352
+ try {
353
+ await handler();
354
+ }
355
+ catch (error) {
356
+ this.logger.error('Shutdown handler error', error);
357
+ }
358
+ }
359
+ }
360
+ resolveDependencies() {
361
+ const resolved = [];
362
+ const visited = new Set();
363
+ const visiting = new Set();
364
+ const visit = (pluginName) => {
365
+ if (visited.has(pluginName))
366
+ return;
367
+ if (visiting.has(pluginName)) {
368
+ throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
369
+ }
370
+ const plugin = this.plugins.get(pluginName);
371
+ if (!plugin) {
372
+ throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
373
+ }
374
+ visiting.add(pluginName);
375
+ // Visit dependencies first
376
+ const deps = plugin.dependencies || [];
377
+ for (const dep of deps) {
378
+ if (!this.plugins.has(dep)) {
379
+ throw new Error(`[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`);
380
+ }
381
+ visit(dep);
382
+ }
383
+ visiting.delete(pluginName);
384
+ visited.add(pluginName);
385
+ resolved.push(plugin);
386
+ };
387
+ // Visit all plugins
388
+ for (const pluginName of this.plugins.keys()) {
389
+ visit(pluginName);
390
+ }
391
+ return resolved;
392
+ }
393
+ registerShutdownSignals() {
394
+ const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
395
+ let shutdownInProgress = false;
396
+ const handleShutdown = async (signal) => {
397
+ if (shutdownInProgress) {
398
+ this.logger.warn(`Shutdown already in progress, ignoring ${signal}`);
399
+ return;
400
+ }
401
+ shutdownInProgress = true;
402
+ this.logger.info(`Received ${signal} - initiating graceful shutdown`);
403
+ try {
404
+ await this.shutdown();
405
+ process.exit(0);
406
+ }
407
+ catch (error) {
408
+ this.logger.error('Shutdown failed', error);
409
+ process.exit(1);
410
+ }
411
+ };
412
+ for (const signal of signals) {
413
+ process.on(signal, () => handleShutdown(signal));
414
+ }
415
+ }
416
+ /**
417
+ * Register a custom shutdown handler
418
+ */
419
+ onShutdown(handler) {
420
+ this.shutdownHandlers.push(handler);
421
+ }
112
422
  }