@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
@@ -0,0 +1,388 @@
1
+ import type {
2
+ HotReloadConfig,
3
+ PluginStateSnapshot
4
+ } from '@objectstack/spec/system';
5
+ import type { ObjectLogger } from './logger.js';
6
+ import type { Plugin } from './types.js';
7
+
8
+ // Polyfill for UUID generation to support both Node.js and Browser
9
+ const generateUUID = () => {
10
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
11
+ return crypto.randomUUID();
12
+ }
13
+ // Basic UUID v4 fallback
14
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
15
+ const r = Math.random() * 16 | 0;
16
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
17
+ return v.toString(16);
18
+ });
19
+ };
20
+
21
+ /**
22
+ * Plugin State Manager
23
+ *
24
+ * Handles state persistence and restoration during hot reloads
25
+ */
26
+ class PluginStateManager {
27
+ private logger: ObjectLogger;
28
+ private stateSnapshots = new Map<string, PluginStateSnapshot>();
29
+ private memoryStore = new Map<string, any>();
30
+
31
+ constructor(logger: ObjectLogger) {
32
+ this.logger = logger.child({ component: 'StateManager' });
33
+ }
34
+
35
+ /**
36
+ * Save plugin state before reload
37
+ */
38
+ async saveState(
39
+ pluginId: string,
40
+ version: string,
41
+ state: Record<string, any>,
42
+ config: HotReloadConfig
43
+ ): Promise<string> {
44
+ const snapshot: PluginStateSnapshot = {
45
+ pluginId,
46
+ version,
47
+ timestamp: new Date().toISOString(),
48
+ state,
49
+ metadata: {
50
+ checksum: this.calculateChecksum(state),
51
+ compressed: false,
52
+ },
53
+ };
54
+
55
+ const snapshotId = generateUUID();
56
+
57
+ switch (config.stateStrategy) {
58
+ case 'memory':
59
+ this.memoryStore.set(snapshotId, snapshot);
60
+ this.logger.debug('State saved to memory', { pluginId, snapshotId });
61
+ break;
62
+
63
+ case 'disk':
64
+ // For disk storage, we would write to file system
65
+ // For now, store in memory as fallback
66
+ this.memoryStore.set(snapshotId, snapshot);
67
+ this.logger.debug('State saved to disk (memory fallback)', { pluginId, snapshotId });
68
+ break;
69
+
70
+ case 'distributed':
71
+ // For distributed storage, would use Redis/etcd
72
+ // For now, store in memory as fallback
73
+ this.memoryStore.set(snapshotId, snapshot);
74
+ this.logger.debug('State saved to distributed store (memory fallback)', {
75
+ pluginId,
76
+ snapshotId
77
+ });
78
+ break;
79
+
80
+ case 'none':
81
+ this.logger.debug('State persistence disabled', { pluginId });
82
+ break;
83
+ }
84
+
85
+ this.stateSnapshots.set(pluginId, snapshot);
86
+ return snapshotId;
87
+ }
88
+
89
+ /**
90
+ * Restore plugin state after reload
91
+ */
92
+ async restoreState(
93
+ pluginId: string,
94
+ snapshotId?: string
95
+ ): Promise<Record<string, any> | undefined> {
96
+ // Try to get from snapshot ID first, otherwise use latest for plugin
97
+ let snapshot: PluginStateSnapshot | undefined;
98
+
99
+ if (snapshotId) {
100
+ snapshot = this.memoryStore.get(snapshotId);
101
+ } else {
102
+ snapshot = this.stateSnapshots.get(pluginId);
103
+ }
104
+
105
+ if (!snapshot) {
106
+ this.logger.warn('No state snapshot found', { pluginId, snapshotId });
107
+ return undefined;
108
+ }
109
+
110
+ // Verify checksum if available
111
+ if (snapshot.metadata?.checksum) {
112
+ const currentChecksum = this.calculateChecksum(snapshot.state);
113
+ if (currentChecksum !== snapshot.metadata.checksum) {
114
+ this.logger.error('State checksum mismatch - data may be corrupted', {
115
+ pluginId,
116
+ expected: snapshot.metadata.checksum,
117
+ actual: currentChecksum
118
+ });
119
+ return undefined;
120
+ }
121
+ }
122
+
123
+ this.logger.debug('State restored', { pluginId, version: snapshot.version });
124
+ return snapshot.state;
125
+ }
126
+
127
+ /**
128
+ * Clear state for a plugin
129
+ */
130
+ clearState(pluginId: string): void {
131
+ this.stateSnapshots.delete(pluginId);
132
+ // Note: We don't clear memory store as it might have multiple snapshots
133
+ this.logger.debug('State cleared', { pluginId });
134
+ }
135
+
136
+ /**
137
+ * Calculate simple checksum for state verification
138
+ * WARNING: This is a simple hash for demo purposes.
139
+ * In production, use a cryptographic hash like SHA-256.
140
+ */
141
+ private calculateChecksum(state: Record<string, any>): string {
142
+ // Simple checksum using JSON serialization
143
+ // TODO: Replace with crypto.createHash('sha256') for production
144
+ const stateStr = JSON.stringify(state);
145
+ let hash = 0;
146
+ for (let i = 0; i < stateStr.length; i++) {
147
+ const char = stateStr.charCodeAt(i);
148
+ hash = ((hash << 5) - hash) + char;
149
+ hash = hash & hash; // Convert to 32-bit integer
150
+ }
151
+ return hash.toString(16);
152
+ }
153
+
154
+ /**
155
+ * Shutdown state manager
156
+ */
157
+ shutdown(): void {
158
+ this.stateSnapshots.clear();
159
+ this.memoryStore.clear();
160
+ this.logger.info('State manager shutdown complete');
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Hot Reload Manager
166
+ *
167
+ * Manages hot reloading of plugins with state preservation
168
+ */
169
+ export class HotReloadManager {
170
+ private logger: ObjectLogger;
171
+ private stateManager: PluginStateManager;
172
+ private reloadConfigs = new Map<string, HotReloadConfig>();
173
+ private watchHandles = new Map<string, any>();
174
+ private reloadTimers = new Map<string, NodeJS.Timeout>();
175
+
176
+ constructor(logger: ObjectLogger) {
177
+ this.logger = logger.child({ component: 'HotReload' });
178
+ this.stateManager = new PluginStateManager(logger);
179
+ }
180
+
181
+ /**
182
+ * Register a plugin for hot reload
183
+ */
184
+ registerPlugin(pluginName: string, config: HotReloadConfig): void {
185
+ if (!config.enabled) {
186
+ this.logger.debug('Hot reload disabled for plugin', { plugin: pluginName });
187
+ return;
188
+ }
189
+
190
+ this.reloadConfigs.set(pluginName, config);
191
+ this.logger.info('Plugin registered for hot reload', {
192
+ plugin: pluginName,
193
+ watchPatterns: config.watchPatterns,
194
+ stateStrategy: config.stateStrategy
195
+ });
196
+ }
197
+
198
+ /**
199
+ * Start watching for changes (requires file system integration)
200
+ */
201
+ startWatching(pluginName: string): void {
202
+ const config = this.reloadConfigs.get(pluginName);
203
+ if (!config || !config.enabled) {
204
+ return;
205
+ }
206
+
207
+ // Note: Actual file watching would require chokidar or similar
208
+ // This is a placeholder for the integration point
209
+ this.logger.info('File watching started', {
210
+ plugin: pluginName,
211
+ patterns: config.watchPatterns
212
+ });
213
+ }
214
+
215
+ /**
216
+ * Stop watching for changes
217
+ */
218
+ stopWatching(pluginName: string): void {
219
+ const handle = this.watchHandles.get(pluginName);
220
+ if (handle) {
221
+ // Stop watching (would call chokidar close())
222
+ this.watchHandles.delete(pluginName);
223
+ this.logger.info('File watching stopped', { plugin: pluginName });
224
+ }
225
+
226
+ // Clear any pending reload timers
227
+ const timer = this.reloadTimers.get(pluginName);
228
+ if (timer) {
229
+ clearTimeout(timer);
230
+ this.reloadTimers.delete(pluginName);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Trigger hot reload for a plugin
236
+ */
237
+ async reloadPlugin(
238
+ pluginName: string,
239
+ plugin: Plugin,
240
+ version: string,
241
+ getPluginState: () => Record<string, any>,
242
+ restorePluginState: (state: Record<string, any>) => void
243
+ ): Promise<boolean> {
244
+ const config = this.reloadConfigs.get(pluginName);
245
+ if (!config) {
246
+ this.logger.warn('Cannot reload - plugin not registered', { plugin: pluginName });
247
+ return false;
248
+ }
249
+
250
+ this.logger.info('Starting hot reload', { plugin: pluginName });
251
+
252
+ try {
253
+ // Call before reload hooks
254
+ if (config.beforeReload) {
255
+ this.logger.debug('Executing before reload hooks', {
256
+ plugin: pluginName,
257
+ hooks: config.beforeReload
258
+ });
259
+ // Hook execution would be done through kernel's hook system
260
+ }
261
+
262
+ // Save state if configured
263
+ let snapshotId: string | undefined;
264
+ if (config.preserveState && config.stateStrategy !== 'none') {
265
+ const state = getPluginState();
266
+ snapshotId = await this.stateManager.saveState(
267
+ pluginName,
268
+ version,
269
+ state,
270
+ config
271
+ );
272
+ this.logger.debug('Plugin state saved', { plugin: pluginName, snapshotId });
273
+ }
274
+
275
+ // Gracefully shutdown the plugin
276
+ if (plugin.destroy) {
277
+ this.logger.debug('Destroying plugin', { plugin: pluginName });
278
+
279
+ const shutdownPromise = plugin.destroy();
280
+ const timeoutPromise = new Promise((_, reject) => {
281
+ setTimeout(() => reject(new Error('Shutdown timeout')), config.shutdownTimeout);
282
+ });
283
+
284
+ await Promise.race([shutdownPromise, timeoutPromise]);
285
+ this.logger.debug('Plugin destroyed successfully', { plugin: pluginName });
286
+ }
287
+
288
+ // At this point, the kernel would reload the plugin module
289
+ // This would be handled by the plugin loader
290
+ this.logger.debug('Plugin module would be reloaded here', { plugin: pluginName });
291
+
292
+ // Restore state if we saved it
293
+ if (snapshotId && config.preserveState) {
294
+ const restoredState = await this.stateManager.restoreState(pluginName, snapshotId);
295
+ if (restoredState) {
296
+ restorePluginState(restoredState);
297
+ this.logger.debug('Plugin state restored', { plugin: pluginName });
298
+ }
299
+ }
300
+
301
+ // Call after reload hooks
302
+ if (config.afterReload) {
303
+ this.logger.debug('Executing after reload hooks', {
304
+ plugin: pluginName,
305
+ hooks: config.afterReload
306
+ });
307
+ // Hook execution would be done through kernel's hook system
308
+ }
309
+
310
+ this.logger.info('Hot reload completed successfully', { plugin: pluginName });
311
+ return true;
312
+ } catch (error) {
313
+ this.logger.error('Hot reload failed', {
314
+ plugin: pluginName,
315
+ error
316
+ });
317
+ return false;
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Schedule a reload with debouncing
323
+ */
324
+ scheduleReload(
325
+ pluginName: string,
326
+ reloadFn: () => Promise<void>
327
+ ): void {
328
+ const config = this.reloadConfigs.get(pluginName);
329
+ if (!config) {
330
+ return;
331
+ }
332
+
333
+ // Clear existing timer
334
+ const existingTimer = this.reloadTimers.get(pluginName);
335
+ if (existingTimer) {
336
+ clearTimeout(existingTimer);
337
+ }
338
+
339
+ // Schedule new reload with debounce
340
+ const timer = setTimeout(() => {
341
+ this.logger.debug('Debounce period elapsed, executing reload', {
342
+ plugin: pluginName
343
+ });
344
+ reloadFn().catch(error => {
345
+ this.logger.error('Scheduled reload failed', {
346
+ plugin: pluginName,
347
+ error
348
+ });
349
+ });
350
+ this.reloadTimers.delete(pluginName);
351
+ }, config.debounceDelay);
352
+
353
+ this.reloadTimers.set(pluginName, timer);
354
+ this.logger.debug('Reload scheduled with debounce', {
355
+ plugin: pluginName,
356
+ delay: config.debounceDelay
357
+ });
358
+ }
359
+
360
+ /**
361
+ * Get state manager for direct access
362
+ */
363
+ getStateManager(): PluginStateManager {
364
+ return this.stateManager;
365
+ }
366
+
367
+ /**
368
+ * Shutdown hot reload manager
369
+ */
370
+ shutdown(): void {
371
+ // Stop all watching
372
+ for (const pluginName of this.watchHandles.keys()) {
373
+ this.stopWatching(pluginName);
374
+ }
375
+
376
+ // Clear all timers
377
+ for (const timer of this.reloadTimers.values()) {
378
+ clearTimeout(timer);
379
+ }
380
+
381
+ this.reloadConfigs.clear();
382
+ this.watchHandles.clear();
383
+ this.reloadTimers.clear();
384
+ this.stateManager.shutdown();
385
+
386
+ this.logger.info('Hot reload manager shutdown complete');
387
+ }
388
+ }
package/src/index.ts CHANGED
@@ -7,10 +7,10 @@
7
7
 
8
8
  export * from './kernel-base.js';
9
9
  export * from './kernel.js';
10
+ export * from './lite-kernel.js';
10
11
  export * from './types.js';
11
12
  export * from './logger.js';
12
13
  export * from './plugin-loader.js';
13
- export * from './enhanced-kernel.js';
14
14
  export * from './api-registry.js';
15
15
  export * from './api-registry-plugin.js';
16
16
  export * as QA from './qa/index.js';
@@ -18,6 +18,11 @@ export * as QA from './qa/index.js';
18
18
  // Export security utilities
19
19
  export * from './security/index.js';
20
20
 
21
+ // Export Phase 2 components - Advanced lifecycle management
22
+ export * from './health-monitor.js';
23
+ export * from './hot-reload.js';
24
+ export * from './dependency-resolver.js';
25
+
21
26
  // Re-export contracts from @objectstack/spec for backward compatibility
22
27
  export type {
23
28
  Logger,
@@ -10,14 +10,14 @@ export type KernelState = 'idle' | 'initializing' | 'running' | 'stopping' | 'st
10
10
  /**
11
11
  * ObjectKernelBase - Abstract Base Class for Microkernel
12
12
  *
13
- * Provides common functionality for both ObjectKernel and EnhancedObjectKernel:
13
+ * Provides common functionality for ObjectKernel and LiteKernel:
14
14
  * - Plugin management (Map storage)
15
15
  * - Dependency resolution (topological sort)
16
16
  * - Hook/Event system
17
17
  * - Context creation
18
18
  * - State validation
19
19
  *
20
- * This eliminates ~120 lines of duplicate code between the two implementations.
20
+ * This eliminates code duplication between the implementations.
21
21
  */
22
22
  export abstract class ObjectKernelBase {
23
23
  protected plugins: Map<string, Plugin> = new Map();