@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.
- package/README.md +95 -10
- package/dist/index.cjs +169 -507
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -223
- package/dist/index.d.ts +24 -223
- package/dist/index.js +175 -505
- package/dist/index.js.map +1 -1
- package/dist/logger.cjs +177 -0
- package/dist/logger.cjs.map +1 -0
- package/dist/logger.d.cts +26 -0
- package/dist/logger.d.ts +26 -0
- package/dist/logger.js +158 -0
- package/dist/logger.js.map +1 -0
- package/package.json +36 -15
- package/.turbo/turbo-build.log +0 -22
- package/ADVANCED_FEATURES.md +0 -380
- package/API_REGISTRY.md +0 -392
- package/CHANGELOG.md +0 -465
- package/PHASE2_IMPLEMENTATION.md +0 -388
- package/REFACTORING_SUMMARY.md +0 -40
- package/examples/api-registry-example.ts +0 -559
- package/examples/kernel-features-example.ts +0 -311
- package/examples/phase2-integration.ts +0 -357
- package/src/api-registry-plugin.test.ts +0 -393
- package/src/api-registry-plugin.ts +0 -89
- package/src/api-registry.test.ts +0 -1089
- package/src/api-registry.ts +0 -739
- package/src/contracts/data-engine.ts +0 -57
- package/src/contracts/http-server.ts +0 -151
- package/src/contracts/logger.ts +0 -72
- package/src/dependency-resolver.test.ts +0 -287
- package/src/dependency-resolver.ts +0 -390
- package/src/fallbacks/fallbacks.test.ts +0 -281
- package/src/fallbacks/index.ts +0 -26
- package/src/fallbacks/memory-cache.ts +0 -34
- package/src/fallbacks/memory-i18n.ts +0 -112
- package/src/fallbacks/memory-job.ts +0 -23
- package/src/fallbacks/memory-metadata.ts +0 -50
- package/src/fallbacks/memory-queue.ts +0 -28
- package/src/health-monitor.test.ts +0 -81
- package/src/health-monitor.ts +0 -318
- package/src/hot-reload.ts +0 -382
- package/src/index.ts +0 -50
- package/src/kernel-base.ts +0 -273
- package/src/kernel.test.ts +0 -624
- package/src/kernel.ts +0 -631
- package/src/lite-kernel.test.ts +0 -248
- package/src/lite-kernel.ts +0 -137
- package/src/logger.test.ts +0 -116
- package/src/logger.ts +0 -355
- package/src/namespace-resolver.test.ts +0 -130
- package/src/namespace-resolver.ts +0 -188
- package/src/package-manager.test.ts +0 -225
- package/src/package-manager.ts +0 -428
- package/src/plugin-loader.test.ts +0 -421
- package/src/plugin-loader.ts +0 -484
- package/src/qa/adapter.ts +0 -16
- package/src/qa/http-adapter.ts +0 -116
- package/src/qa/index.ts +0 -5
- package/src/qa/runner.ts +0 -189
- package/src/security/index.ts +0 -50
- package/src/security/permission-manager.test.ts +0 -256
- package/src/security/permission-manager.ts +0 -338
- package/src/security/plugin-config-validator.test.ts +0 -276
- package/src/security/plugin-config-validator.ts +0 -193
- package/src/security/plugin-permission-enforcer.test.ts +0 -251
- package/src/security/plugin-permission-enforcer.ts +0 -436
- package/src/security/plugin-signature-verifier.ts +0 -403
- package/src/security/sandbox-runtime.ts +0 -462
- package/src/security/security-scanner.ts +0 -367
- package/src/types.ts +0 -120
- package/src/utils/env.test.ts +0 -62
- package/src/utils/env.ts +0 -53
- package/tsconfig.json +0 -10
- package/vitest.config.ts +0 -10
package/src/hot-reload.ts
DELETED
|
@@ -1,382 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
HotReloadConfig,
|
|
7
|
-
PluginStateSnapshot
|
|
8
|
-
} from '@objectstack/spec/kernel';
|
|
9
|
-
import type { ObjectLogger } from './logger.js';
|
|
10
|
-
import type { Plugin } from './types.js';
|
|
11
|
-
|
|
12
|
-
// Polyfill for UUID generation to support both Node.js and Browser
|
|
13
|
-
const generateUUID = () => {
|
|
14
|
-
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
15
|
-
return crypto.randomUUID();
|
|
16
|
-
}
|
|
17
|
-
// Basic UUID v4 fallback
|
|
18
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
19
|
-
const r = Math.random() * 16 | 0;
|
|
20
|
-
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
21
|
-
return v.toString(16);
|
|
22
|
-
});
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Plugin State Manager
|
|
27
|
-
*
|
|
28
|
-
* Handles state persistence and restoration during hot reloads
|
|
29
|
-
*/
|
|
30
|
-
class PluginStateManager {
|
|
31
|
-
private logger: ObjectLogger;
|
|
32
|
-
private stateSnapshots = new Map<string, PluginStateSnapshot>();
|
|
33
|
-
private memoryStore = new Map<string, any>();
|
|
34
|
-
|
|
35
|
-
constructor(logger: ObjectLogger) {
|
|
36
|
-
this.logger = logger.child({ component: 'StateManager' });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Save plugin state before reload
|
|
41
|
-
*/
|
|
42
|
-
async saveState(
|
|
43
|
-
pluginId: string,
|
|
44
|
-
version: string,
|
|
45
|
-
state: Record<string, any>,
|
|
46
|
-
config: HotReloadConfig
|
|
47
|
-
): Promise<string> {
|
|
48
|
-
const snapshot: PluginStateSnapshot = {
|
|
49
|
-
pluginId,
|
|
50
|
-
version,
|
|
51
|
-
timestamp: new Date().toISOString(),
|
|
52
|
-
state,
|
|
53
|
-
metadata: {
|
|
54
|
-
checksum: this.calculateChecksum(state),
|
|
55
|
-
compressed: false,
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const snapshotId = generateUUID();
|
|
60
|
-
|
|
61
|
-
switch (config.stateStrategy) {
|
|
62
|
-
case 'memory':
|
|
63
|
-
this.memoryStore.set(snapshotId, snapshot);
|
|
64
|
-
this.logger.debug('State saved to memory', { pluginId, snapshotId });
|
|
65
|
-
break;
|
|
66
|
-
|
|
67
|
-
case 'disk':
|
|
68
|
-
// For disk storage, we would write to file system
|
|
69
|
-
// For now, store in memory as fallback
|
|
70
|
-
this.memoryStore.set(snapshotId, snapshot);
|
|
71
|
-
this.logger.debug('State saved to disk (memory fallback)', { pluginId, snapshotId });
|
|
72
|
-
break;
|
|
73
|
-
|
|
74
|
-
case 'distributed':
|
|
75
|
-
// For distributed storage, would use Redis/etcd
|
|
76
|
-
// For now, store in memory as fallback
|
|
77
|
-
this.memoryStore.set(snapshotId, snapshot);
|
|
78
|
-
this.logger.debug('State saved to distributed store (memory fallback)', {
|
|
79
|
-
pluginId,
|
|
80
|
-
snapshotId
|
|
81
|
-
});
|
|
82
|
-
break;
|
|
83
|
-
|
|
84
|
-
case 'none':
|
|
85
|
-
this.logger.debug('State persistence disabled', { pluginId });
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
this.stateSnapshots.set(pluginId, snapshot);
|
|
90
|
-
return snapshotId;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Restore plugin state after reload
|
|
95
|
-
*/
|
|
96
|
-
async restoreState(
|
|
97
|
-
pluginId: string,
|
|
98
|
-
snapshotId?: string
|
|
99
|
-
): Promise<Record<string, any> | undefined> {
|
|
100
|
-
// Try to get from snapshot ID first, otherwise use latest for plugin
|
|
101
|
-
let snapshot: PluginStateSnapshot | undefined;
|
|
102
|
-
|
|
103
|
-
if (snapshotId) {
|
|
104
|
-
snapshot = this.memoryStore.get(snapshotId);
|
|
105
|
-
} else {
|
|
106
|
-
snapshot = this.stateSnapshots.get(pluginId);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (!snapshot) {
|
|
110
|
-
this.logger.warn('No state snapshot found', { pluginId, snapshotId });
|
|
111
|
-
return undefined;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Verify checksum if available
|
|
115
|
-
if (snapshot.metadata?.checksum) {
|
|
116
|
-
const currentChecksum = this.calculateChecksum(snapshot.state);
|
|
117
|
-
if (currentChecksum !== snapshot.metadata.checksum) {
|
|
118
|
-
this.logger.error('State checksum mismatch - data may be corrupted', {
|
|
119
|
-
pluginId,
|
|
120
|
-
expected: snapshot.metadata.checksum,
|
|
121
|
-
actual: currentChecksum
|
|
122
|
-
});
|
|
123
|
-
return undefined;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
this.logger.debug('State restored', { pluginId, version: snapshot.version });
|
|
128
|
-
return snapshot.state;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Clear state for a plugin
|
|
133
|
-
*/
|
|
134
|
-
clearState(pluginId: string): void {
|
|
135
|
-
this.stateSnapshots.delete(pluginId);
|
|
136
|
-
// Note: We don't clear memory store as it might have multiple snapshots
|
|
137
|
-
this.logger.debug('State cleared', { pluginId });
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Calculate checksum for state verification using SHA-256.
|
|
142
|
-
*/
|
|
143
|
-
private calculateChecksum(state: Record<string, any>): string {
|
|
144
|
-
const stateStr = JSON.stringify(state);
|
|
145
|
-
return createHash('sha256').update(stateStr).digest('hex');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Shutdown state manager
|
|
150
|
-
*/
|
|
151
|
-
shutdown(): void {
|
|
152
|
-
this.stateSnapshots.clear();
|
|
153
|
-
this.memoryStore.clear();
|
|
154
|
-
this.logger.info('State manager shutdown complete');
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Hot Reload Manager
|
|
160
|
-
*
|
|
161
|
-
* Manages hot reloading of plugins with state preservation
|
|
162
|
-
*/
|
|
163
|
-
export class HotReloadManager {
|
|
164
|
-
private logger: ObjectLogger;
|
|
165
|
-
private stateManager: PluginStateManager;
|
|
166
|
-
private reloadConfigs = new Map<string, HotReloadConfig>();
|
|
167
|
-
private watchHandles = new Map<string, any>();
|
|
168
|
-
private reloadTimers = new Map<string, NodeJS.Timeout>();
|
|
169
|
-
|
|
170
|
-
constructor(logger: ObjectLogger) {
|
|
171
|
-
this.logger = logger.child({ component: 'HotReload' });
|
|
172
|
-
this.stateManager = new PluginStateManager(logger);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Register a plugin for hot reload
|
|
177
|
-
*/
|
|
178
|
-
registerPlugin(pluginName: string, config: HotReloadConfig): void {
|
|
179
|
-
if (!config.enabled) {
|
|
180
|
-
this.logger.debug('Hot reload disabled for plugin', { plugin: pluginName });
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
this.reloadConfigs.set(pluginName, config);
|
|
185
|
-
this.logger.info('Plugin registered for hot reload', {
|
|
186
|
-
plugin: pluginName,
|
|
187
|
-
watchPatterns: config.watchPatterns,
|
|
188
|
-
stateStrategy: config.stateStrategy
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Start watching for changes (requires file system integration)
|
|
194
|
-
*/
|
|
195
|
-
startWatching(pluginName: string): void {
|
|
196
|
-
const config = this.reloadConfigs.get(pluginName);
|
|
197
|
-
if (!config || !config.enabled) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Note: Actual file watching would require chokidar or similar
|
|
202
|
-
// This is a placeholder for the integration point
|
|
203
|
-
this.logger.info('File watching started', {
|
|
204
|
-
plugin: pluginName,
|
|
205
|
-
patterns: config.watchPatterns
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Stop watching for changes
|
|
211
|
-
*/
|
|
212
|
-
stopWatching(pluginName: string): void {
|
|
213
|
-
const handle = this.watchHandles.get(pluginName);
|
|
214
|
-
if (handle) {
|
|
215
|
-
// Stop watching (would call chokidar close())
|
|
216
|
-
this.watchHandles.delete(pluginName);
|
|
217
|
-
this.logger.info('File watching stopped', { plugin: pluginName });
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Clear any pending reload timers
|
|
221
|
-
const timer = this.reloadTimers.get(pluginName);
|
|
222
|
-
if (timer) {
|
|
223
|
-
clearTimeout(timer);
|
|
224
|
-
this.reloadTimers.delete(pluginName);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Trigger hot reload for a plugin
|
|
230
|
-
*/
|
|
231
|
-
async reloadPlugin(
|
|
232
|
-
pluginName: string,
|
|
233
|
-
plugin: Plugin,
|
|
234
|
-
version: string,
|
|
235
|
-
getPluginState: () => Record<string, any>,
|
|
236
|
-
restorePluginState: (state: Record<string, any>) => void
|
|
237
|
-
): Promise<boolean> {
|
|
238
|
-
const config = this.reloadConfigs.get(pluginName);
|
|
239
|
-
if (!config) {
|
|
240
|
-
this.logger.warn('Cannot reload - plugin not registered', { plugin: pluginName });
|
|
241
|
-
return false;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
this.logger.info('Starting hot reload', { plugin: pluginName });
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
// Call before reload hooks
|
|
248
|
-
if (config.beforeReload) {
|
|
249
|
-
this.logger.debug('Executing before reload hooks', {
|
|
250
|
-
plugin: pluginName,
|
|
251
|
-
hooks: config.beforeReload
|
|
252
|
-
});
|
|
253
|
-
// Hook execution would be done through kernel's hook system
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Save state if configured
|
|
257
|
-
let snapshotId: string | undefined;
|
|
258
|
-
if (config.preserveState && config.stateStrategy !== 'none') {
|
|
259
|
-
const state = getPluginState();
|
|
260
|
-
snapshotId = await this.stateManager.saveState(
|
|
261
|
-
pluginName,
|
|
262
|
-
version,
|
|
263
|
-
state,
|
|
264
|
-
config
|
|
265
|
-
);
|
|
266
|
-
this.logger.debug('Plugin state saved', { plugin: pluginName, snapshotId });
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Gracefully shutdown the plugin
|
|
270
|
-
if (plugin.destroy) {
|
|
271
|
-
this.logger.debug('Destroying plugin', { plugin: pluginName });
|
|
272
|
-
|
|
273
|
-
const shutdownPromise = plugin.destroy();
|
|
274
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
275
|
-
setTimeout(() => reject(new Error('Shutdown timeout')), config.shutdownTimeout);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
await Promise.race([shutdownPromise, timeoutPromise]);
|
|
279
|
-
this.logger.debug('Plugin destroyed successfully', { plugin: pluginName });
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// At this point, the kernel would reload the plugin module
|
|
283
|
-
// This would be handled by the plugin loader
|
|
284
|
-
this.logger.debug('Plugin module would be reloaded here', { plugin: pluginName });
|
|
285
|
-
|
|
286
|
-
// Restore state if we saved it
|
|
287
|
-
if (snapshotId && config.preserveState) {
|
|
288
|
-
const restoredState = await this.stateManager.restoreState(pluginName, snapshotId);
|
|
289
|
-
if (restoredState) {
|
|
290
|
-
restorePluginState(restoredState);
|
|
291
|
-
this.logger.debug('Plugin state restored', { plugin: pluginName });
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Call after reload hooks
|
|
296
|
-
if (config.afterReload) {
|
|
297
|
-
this.logger.debug('Executing after reload hooks', {
|
|
298
|
-
plugin: pluginName,
|
|
299
|
-
hooks: config.afterReload
|
|
300
|
-
});
|
|
301
|
-
// Hook execution would be done through kernel's hook system
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
this.logger.info('Hot reload completed successfully', { plugin: pluginName });
|
|
305
|
-
return true;
|
|
306
|
-
} catch (error) {
|
|
307
|
-
this.logger.error('Hot reload failed', {
|
|
308
|
-
plugin: pluginName,
|
|
309
|
-
error
|
|
310
|
-
});
|
|
311
|
-
return false;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Schedule a reload with debouncing
|
|
317
|
-
*/
|
|
318
|
-
scheduleReload(
|
|
319
|
-
pluginName: string,
|
|
320
|
-
reloadFn: () => Promise<void>
|
|
321
|
-
): void {
|
|
322
|
-
const config = this.reloadConfigs.get(pluginName);
|
|
323
|
-
if (!config) {
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Clear existing timer
|
|
328
|
-
const existingTimer = this.reloadTimers.get(pluginName);
|
|
329
|
-
if (existingTimer) {
|
|
330
|
-
clearTimeout(existingTimer);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Schedule new reload with debounce
|
|
334
|
-
const timer = setTimeout(() => {
|
|
335
|
-
this.logger.debug('Debounce period elapsed, executing reload', {
|
|
336
|
-
plugin: pluginName
|
|
337
|
-
});
|
|
338
|
-
reloadFn().catch(error => {
|
|
339
|
-
this.logger.error('Scheduled reload failed', {
|
|
340
|
-
plugin: pluginName,
|
|
341
|
-
error
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
this.reloadTimers.delete(pluginName);
|
|
345
|
-
}, config.debounceDelay);
|
|
346
|
-
|
|
347
|
-
this.reloadTimers.set(pluginName, timer);
|
|
348
|
-
this.logger.debug('Reload scheduled with debounce', {
|
|
349
|
-
plugin: pluginName,
|
|
350
|
-
delay: config.debounceDelay
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Get state manager for direct access
|
|
356
|
-
*/
|
|
357
|
-
getStateManager(): PluginStateManager {
|
|
358
|
-
return this.stateManager;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Shutdown hot reload manager
|
|
363
|
-
*/
|
|
364
|
-
shutdown(): void {
|
|
365
|
-
// Stop all watching
|
|
366
|
-
for (const pluginName of this.watchHandles.keys()) {
|
|
367
|
-
this.stopWatching(pluginName);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Clear all timers
|
|
371
|
-
for (const timer of this.reloadTimers.values()) {
|
|
372
|
-
clearTimeout(timer);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
this.reloadConfigs.clear();
|
|
376
|
-
this.watchHandles.clear();
|
|
377
|
-
this.reloadTimers.clear();
|
|
378
|
-
this.stateManager.shutdown();
|
|
379
|
-
|
|
380
|
-
this.logger.info('Hot reload manager shutdown complete');
|
|
381
|
-
}
|
|
382
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @objectstack/core
|
|
5
|
-
*
|
|
6
|
-
* Core runtime for ObjectStack microkernel architecture.
|
|
7
|
-
* Provides plugin system, dependency injection, and lifecycle management.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export * from './kernel-base.js';
|
|
11
|
-
export * from './kernel.js';
|
|
12
|
-
export * from './lite-kernel.js';
|
|
13
|
-
export * from './types.js';
|
|
14
|
-
export * from './logger.js';
|
|
15
|
-
export * from './plugin-loader.js';
|
|
16
|
-
export * from './api-registry.js';
|
|
17
|
-
export * from './api-registry-plugin.js';
|
|
18
|
-
export * as QA from './qa/index.js';
|
|
19
|
-
|
|
20
|
-
// Export security utilities
|
|
21
|
-
export * from './security/index.js';
|
|
22
|
-
|
|
23
|
-
// Export environment utilities
|
|
24
|
-
export * from './utils/env.js';
|
|
25
|
-
|
|
26
|
-
// Export in-memory fallbacks for core-criticality services
|
|
27
|
-
export * from './fallbacks/index.js';
|
|
28
|
-
|
|
29
|
-
// Export Phase 2 components - Advanced lifecycle management
|
|
30
|
-
export * from './health-monitor.js';
|
|
31
|
-
export * from './hot-reload.js';
|
|
32
|
-
export * from './dependency-resolver.js';
|
|
33
|
-
|
|
34
|
-
// Export Phase 3 components - Package lifecycle management
|
|
35
|
-
export * from './namespace-resolver.js';
|
|
36
|
-
export * from './package-manager.js';
|
|
37
|
-
|
|
38
|
-
// Re-export contracts from @objectstack/spec for backward compatibility
|
|
39
|
-
export type {
|
|
40
|
-
Logger,
|
|
41
|
-
IHttpServer,
|
|
42
|
-
IHttpRequest,
|
|
43
|
-
IHttpResponse,
|
|
44
|
-
RouteHandler,
|
|
45
|
-
Middleware,
|
|
46
|
-
IDataEngine,
|
|
47
|
-
IDataDriver,
|
|
48
|
-
/** @deprecated Use `IDataDriver` instead */
|
|
49
|
-
DriverInterface
|
|
50
|
-
} from '@objectstack/spec/contracts';
|
package/src/kernel-base.ts
DELETED
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import type { Plugin, PluginContext } from './types.js';
|
|
4
|
-
import type { Logger } from '@objectstack/spec/contracts';
|
|
5
|
-
import type { IServiceRegistry } from '@objectstack/spec/contracts';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Kernel state machine
|
|
9
|
-
*/
|
|
10
|
-
export type KernelState = 'idle' | 'initializing' | 'running' | 'stopping' | 'stopped';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* ObjectKernelBase - Abstract Base Class for Microkernel
|
|
14
|
-
*
|
|
15
|
-
* Provides common functionality for ObjectKernel and LiteKernel:
|
|
16
|
-
* - Plugin management (Map storage)
|
|
17
|
-
* - Dependency resolution (topological sort)
|
|
18
|
-
* - Hook/Event system
|
|
19
|
-
* - Context creation
|
|
20
|
-
* - State validation
|
|
21
|
-
*
|
|
22
|
-
* This eliminates code duplication between the implementations.
|
|
23
|
-
*/
|
|
24
|
-
export abstract class ObjectKernelBase {
|
|
25
|
-
protected plugins: Map<string, Plugin> = new Map();
|
|
26
|
-
protected services: IServiceRegistry | Map<string, any> = new Map();
|
|
27
|
-
protected hooks: Map<string, Array<(...args: any[]) => void | Promise<void>>> = new Map();
|
|
28
|
-
protected state: KernelState = 'idle';
|
|
29
|
-
protected logger: Logger;
|
|
30
|
-
protected context!: PluginContext;
|
|
31
|
-
|
|
32
|
-
constructor(logger: Logger) {
|
|
33
|
-
this.logger = logger;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Validate kernel state
|
|
38
|
-
* @param requiredState - Required state for the operation
|
|
39
|
-
* @throws Error if current state doesn't match
|
|
40
|
-
*/
|
|
41
|
-
protected validateState(requiredState: KernelState): void {
|
|
42
|
-
if (this.state !== requiredState) {
|
|
43
|
-
throw new Error(
|
|
44
|
-
`[Kernel] Invalid state: expected '${requiredState}', got '${this.state}'`
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Validate kernel is in idle state (for plugin registration)
|
|
51
|
-
*/
|
|
52
|
-
protected validateIdle(): void {
|
|
53
|
-
if (this.state !== 'idle') {
|
|
54
|
-
throw new Error('[Kernel] Cannot register plugins after bootstrap has started');
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Create the plugin context
|
|
60
|
-
* Subclasses can override to customize context creation
|
|
61
|
-
*/
|
|
62
|
-
protected createContext(): PluginContext {
|
|
63
|
-
return {
|
|
64
|
-
registerService: (name, service) => {
|
|
65
|
-
if (this.services instanceof Map) {
|
|
66
|
-
if (this.services.has(name)) {
|
|
67
|
-
throw new Error(`[Kernel] Service '${name}' already registered`);
|
|
68
|
-
}
|
|
69
|
-
this.services.set(name, service);
|
|
70
|
-
} else {
|
|
71
|
-
// IServiceRegistry implementation
|
|
72
|
-
this.services.register(name, service);
|
|
73
|
-
}
|
|
74
|
-
this.logger.info(`Service '${name}' registered`, { service: name });
|
|
75
|
-
},
|
|
76
|
-
getService: <T>(name: string): T => {
|
|
77
|
-
if (this.services instanceof Map) {
|
|
78
|
-
const service = this.services.get(name);
|
|
79
|
-
if (!service) {
|
|
80
|
-
throw new Error(`[Kernel] Service '${name}' not found`);
|
|
81
|
-
}
|
|
82
|
-
return service as T;
|
|
83
|
-
} else {
|
|
84
|
-
// IServiceRegistry implementation
|
|
85
|
-
return this.services.get<T>(name);
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
replaceService: <T>(name: string, implementation: T): void => {
|
|
89
|
-
if (this.services instanceof Map) {
|
|
90
|
-
if (!this.services.has(name)) {
|
|
91
|
-
throw new Error(`[Kernel] Service '${name}' not found. Use registerService() to add new services.`);
|
|
92
|
-
}
|
|
93
|
-
this.services.set(name, implementation);
|
|
94
|
-
} else {
|
|
95
|
-
// IServiceRegistry implementation
|
|
96
|
-
if (!this.services.has(name)) {
|
|
97
|
-
throw new Error(`[Kernel] Service '${name}' not found. Use registerService() to add new services.`);
|
|
98
|
-
}
|
|
99
|
-
this.services.register(name, implementation);
|
|
100
|
-
}
|
|
101
|
-
this.logger.info(`Service '${name}' replaced`, { service: name });
|
|
102
|
-
},
|
|
103
|
-
hook: (name, handler) => {
|
|
104
|
-
if (!this.hooks.has(name)) {
|
|
105
|
-
this.hooks.set(name, []);
|
|
106
|
-
}
|
|
107
|
-
this.hooks.get(name)!.push(handler);
|
|
108
|
-
},
|
|
109
|
-
trigger: async (name, ...args) => {
|
|
110
|
-
const handlers = this.hooks.get(name) || [];
|
|
111
|
-
for (const handler of handlers) {
|
|
112
|
-
await handler(...args);
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
getServices: () => {
|
|
116
|
-
if (this.services instanceof Map) {
|
|
117
|
-
return new Map(this.services);
|
|
118
|
-
} else {
|
|
119
|
-
// For IServiceRegistry, we need to return the underlying Map
|
|
120
|
-
// This is a compatibility method
|
|
121
|
-
return new Map();
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
logger: this.logger,
|
|
125
|
-
getKernel: () => this as any,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Resolve plugin dependencies using topological sort
|
|
131
|
-
* @returns Ordered list of plugins (dependencies first)
|
|
132
|
-
*/
|
|
133
|
-
protected resolveDependencies(): Plugin[] {
|
|
134
|
-
const resolved: Plugin[] = [];
|
|
135
|
-
const visited = new Set<string>();
|
|
136
|
-
const visiting = new Set<string>();
|
|
137
|
-
|
|
138
|
-
const visit = (pluginName: string) => {
|
|
139
|
-
if (visited.has(pluginName)) return;
|
|
140
|
-
|
|
141
|
-
if (visiting.has(pluginName)) {
|
|
142
|
-
throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const plugin = this.plugins.get(pluginName);
|
|
146
|
-
if (!plugin) {
|
|
147
|
-
throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
visiting.add(pluginName);
|
|
151
|
-
|
|
152
|
-
// Visit dependencies first
|
|
153
|
-
const deps = plugin.dependencies || [];
|
|
154
|
-
for (const dep of deps) {
|
|
155
|
-
if (!this.plugins.has(dep)) {
|
|
156
|
-
throw new Error(
|
|
157
|
-
`[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
visit(dep);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
visiting.delete(pluginName);
|
|
164
|
-
visited.add(pluginName);
|
|
165
|
-
resolved.push(plugin);
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
// Visit all plugins
|
|
169
|
-
for (const pluginName of this.plugins.keys()) {
|
|
170
|
-
visit(pluginName);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return resolved;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Run plugin init phase
|
|
178
|
-
* @param plugin - Plugin to initialize
|
|
179
|
-
*/
|
|
180
|
-
protected async runPluginInit(plugin: Plugin): Promise<void> {
|
|
181
|
-
const pluginName = plugin.name;
|
|
182
|
-
this.logger.info(`Initializing plugin: ${pluginName}`);
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
await plugin.init(this.context);
|
|
186
|
-
this.logger.info(`Plugin initialized: ${pluginName}`);
|
|
187
|
-
} catch (error) {
|
|
188
|
-
this.logger.error(`Plugin init failed: ${pluginName}`, error as Error);
|
|
189
|
-
throw error;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Run plugin start phase
|
|
195
|
-
* @param plugin - Plugin to start
|
|
196
|
-
*/
|
|
197
|
-
protected async runPluginStart(plugin: Plugin): Promise<void> {
|
|
198
|
-
if (!plugin.start) return;
|
|
199
|
-
|
|
200
|
-
const pluginName = plugin.name;
|
|
201
|
-
this.logger.info(`Starting plugin: ${pluginName}`);
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
await plugin.start(this.context);
|
|
205
|
-
this.logger.info(`Plugin started: ${pluginName}`);
|
|
206
|
-
} catch (error) {
|
|
207
|
-
this.logger.error(`Plugin start failed: ${pluginName}`, error as Error);
|
|
208
|
-
throw error;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Run plugin destroy phase
|
|
214
|
-
* @param plugin - Plugin to destroy
|
|
215
|
-
*/
|
|
216
|
-
protected async runPluginDestroy(plugin: Plugin): Promise<void> {
|
|
217
|
-
if (!plugin.destroy) return;
|
|
218
|
-
|
|
219
|
-
const pluginName = plugin.name;
|
|
220
|
-
this.logger.info(`Destroying plugin: ${pluginName}`);
|
|
221
|
-
|
|
222
|
-
try {
|
|
223
|
-
await plugin.destroy();
|
|
224
|
-
this.logger.info(`Plugin destroyed: ${pluginName}`);
|
|
225
|
-
} catch (error) {
|
|
226
|
-
this.logger.error(`Plugin destroy failed: ${pluginName}`, error as Error);
|
|
227
|
-
throw error;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Trigger a hook with all registered handlers
|
|
233
|
-
* @param name - Hook name
|
|
234
|
-
* @param args - Arguments to pass to handlers
|
|
235
|
-
*/
|
|
236
|
-
protected async triggerHook(name: string, ...args: any[]): Promise<void> {
|
|
237
|
-
const handlers = this.hooks.get(name) || [];
|
|
238
|
-
this.logger.debug(`Triggering hook: ${name}`, {
|
|
239
|
-
hook: name,
|
|
240
|
-
handlerCount: handlers.length
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
for (const handler of handlers) {
|
|
244
|
-
try {
|
|
245
|
-
await handler(...args);
|
|
246
|
-
} catch (error) {
|
|
247
|
-
this.logger.error(`Hook handler failed: ${name}`, error as Error);
|
|
248
|
-
// Continue with other handlers even if one fails
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Get current kernel state
|
|
255
|
-
*/
|
|
256
|
-
getState(): KernelState {
|
|
257
|
-
return this.state;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Get all registered plugins
|
|
262
|
-
*/
|
|
263
|
-
getPlugins(): Map<string, Plugin> {
|
|
264
|
-
return new Map(this.plugins);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Abstract methods to be implemented by subclasses
|
|
269
|
-
*/
|
|
270
|
-
abstract use(plugin: Plugin): this | Promise<this>;
|
|
271
|
-
abstract bootstrap(): Promise<void>;
|
|
272
|
-
abstract destroy(): Promise<void>;
|
|
273
|
-
}
|