@objectstack/core 0.6.1 → 0.7.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.
- package/CHANGELOG.md +15 -0
- package/ENHANCED_FEATURES.md +380 -0
- package/README.md +299 -12
- package/dist/contracts/data-engine.d.ts +39 -22
- package/dist/contracts/data-engine.d.ts.map +1 -1
- package/dist/contracts/logger.d.ts +63 -0
- package/dist/contracts/logger.d.ts.map +1 -0
- package/dist/contracts/logger.js +1 -0
- package/dist/enhanced-kernel.d.ts +103 -0
- package/dist/enhanced-kernel.d.ts.map +1 -0
- package/dist/enhanced-kernel.js +403 -0
- package/dist/enhanced-kernel.test.d.ts +2 -0
- package/dist/enhanced-kernel.test.d.ts.map +1 -0
- package/dist/enhanced-kernel.test.js +412 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/kernel-base.d.ts +84 -0
- package/dist/kernel-base.d.ts.map +1 -0
- package/dist/kernel-base.js +219 -0
- package/dist/kernel.d.ts +11 -18
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +43 -114
- package/dist/kernel.test.d.ts +2 -0
- package/dist/kernel.test.d.ts.map +1 -0
- package/dist/kernel.test.js +161 -0
- package/dist/logger.d.ts +70 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +268 -0
- package/dist/logger.test.d.ts +2 -0
- package/dist/logger.test.d.ts.map +1 -0
- package/dist/logger.test.js +92 -0
- package/dist/plugin-loader.d.ts +148 -0
- package/dist/plugin-loader.d.ts.map +1 -0
- package/dist/plugin-loader.js +287 -0
- package/dist/plugin-loader.test.d.ts +2 -0
- package/dist/plugin-loader.test.d.ts.map +1 -0
- package/dist/plugin-loader.test.js +339 -0
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/examples/enhanced-kernel-example.ts +309 -0
- package/package.json +19 -4
- package/src/contracts/data-engine.ts +46 -24
- package/src/contracts/logger.ts +70 -0
- package/src/enhanced-kernel.test.ts +535 -0
- package/src/enhanced-kernel.ts +496 -0
- package/src/index.ts +23 -2
- package/src/kernel-base.ts +256 -0
- package/src/kernel.test.ts +200 -0
- package/src/kernel.ts +55 -129
- package/src/logger.test.ts +116 -0
- package/src/logger.ts +306 -0
- package/src/plugin-loader.test.ts +412 -0
- package/src/plugin-loader.ts +435 -0
- package/src/types.ts +2 -1
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectKernelBase - Abstract Base Class for Microkernel
|
|
3
|
+
*
|
|
4
|
+
* Provides common functionality for both ObjectKernel and EnhancedObjectKernel:
|
|
5
|
+
* - Plugin management (Map storage)
|
|
6
|
+
* - Dependency resolution (topological sort)
|
|
7
|
+
* - Hook/Event system
|
|
8
|
+
* - Context creation
|
|
9
|
+
* - State validation
|
|
10
|
+
*
|
|
11
|
+
* This eliminates ~120 lines of duplicate code between the two implementations.
|
|
12
|
+
*/
|
|
13
|
+
export class ObjectKernelBase {
|
|
14
|
+
constructor(logger) {
|
|
15
|
+
this.plugins = new Map();
|
|
16
|
+
this.services = new Map();
|
|
17
|
+
this.hooks = new Map();
|
|
18
|
+
this.state = 'idle';
|
|
19
|
+
this.logger = logger;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validate kernel state
|
|
23
|
+
* @param requiredState - Required state for the operation
|
|
24
|
+
* @throws Error if current state doesn't match
|
|
25
|
+
*/
|
|
26
|
+
validateState(requiredState) {
|
|
27
|
+
if (this.state !== requiredState) {
|
|
28
|
+
throw new Error(`[Kernel] Invalid state: expected '${requiredState}', got '${this.state}'`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Validate kernel is in idle state (for plugin registration)
|
|
33
|
+
*/
|
|
34
|
+
validateIdle() {
|
|
35
|
+
if (this.state !== 'idle') {
|
|
36
|
+
throw new Error('[Kernel] Cannot register plugins after bootstrap has started');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create the plugin context
|
|
41
|
+
* Subclasses can override to customize context creation
|
|
42
|
+
*/
|
|
43
|
+
createContext() {
|
|
44
|
+
return {
|
|
45
|
+
registerService: (name, service) => {
|
|
46
|
+
if (this.services instanceof Map) {
|
|
47
|
+
if (this.services.has(name)) {
|
|
48
|
+
throw new Error(`[Kernel] Service '${name}' already registered`);
|
|
49
|
+
}
|
|
50
|
+
this.services.set(name, service);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// IServiceRegistry implementation
|
|
54
|
+
this.services.register(name, service);
|
|
55
|
+
}
|
|
56
|
+
this.logger.info(`Service '${name}' registered`, { service: name });
|
|
57
|
+
},
|
|
58
|
+
getService: (name) => {
|
|
59
|
+
if (this.services instanceof Map) {
|
|
60
|
+
const service = this.services.get(name);
|
|
61
|
+
if (!service) {
|
|
62
|
+
throw new Error(`[Kernel] Service '${name}' not found`);
|
|
63
|
+
}
|
|
64
|
+
return service;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// IServiceRegistry implementation
|
|
68
|
+
return this.services.get(name);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
hook: (name, handler) => {
|
|
72
|
+
if (!this.hooks.has(name)) {
|
|
73
|
+
this.hooks.set(name, []);
|
|
74
|
+
}
|
|
75
|
+
this.hooks.get(name).push(handler);
|
|
76
|
+
},
|
|
77
|
+
trigger: async (name, ...args) => {
|
|
78
|
+
const handlers = this.hooks.get(name) || [];
|
|
79
|
+
for (const handler of handlers) {
|
|
80
|
+
await handler(...args);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
getServices: () => {
|
|
84
|
+
if (this.services instanceof Map) {
|
|
85
|
+
return new Map(this.services);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// For IServiceRegistry, we need to return the underlying Map
|
|
89
|
+
// This is a compatibility method
|
|
90
|
+
return new Map();
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
logger: this.logger,
|
|
94
|
+
getKernel: () => this,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Resolve plugin dependencies using topological sort
|
|
99
|
+
* @returns Ordered list of plugins (dependencies first)
|
|
100
|
+
*/
|
|
101
|
+
resolveDependencies() {
|
|
102
|
+
const resolved = [];
|
|
103
|
+
const visited = new Set();
|
|
104
|
+
const visiting = new Set();
|
|
105
|
+
const visit = (pluginName) => {
|
|
106
|
+
if (visited.has(pluginName))
|
|
107
|
+
return;
|
|
108
|
+
if (visiting.has(pluginName)) {
|
|
109
|
+
throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
|
|
110
|
+
}
|
|
111
|
+
const plugin = this.plugins.get(pluginName);
|
|
112
|
+
if (!plugin) {
|
|
113
|
+
throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
|
|
114
|
+
}
|
|
115
|
+
visiting.add(pluginName);
|
|
116
|
+
// Visit dependencies first
|
|
117
|
+
const deps = plugin.dependencies || [];
|
|
118
|
+
for (const dep of deps) {
|
|
119
|
+
if (!this.plugins.has(dep)) {
|
|
120
|
+
throw new Error(`[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`);
|
|
121
|
+
}
|
|
122
|
+
visit(dep);
|
|
123
|
+
}
|
|
124
|
+
visiting.delete(pluginName);
|
|
125
|
+
visited.add(pluginName);
|
|
126
|
+
resolved.push(plugin);
|
|
127
|
+
};
|
|
128
|
+
// Visit all plugins
|
|
129
|
+
for (const pluginName of this.plugins.keys()) {
|
|
130
|
+
visit(pluginName);
|
|
131
|
+
}
|
|
132
|
+
return resolved;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Run plugin init phase
|
|
136
|
+
* @param plugin - Plugin to initialize
|
|
137
|
+
*/
|
|
138
|
+
async runPluginInit(plugin) {
|
|
139
|
+
const pluginName = plugin.name;
|
|
140
|
+
this.logger.info(`Initializing plugin: ${pluginName}`);
|
|
141
|
+
try {
|
|
142
|
+
await plugin.init(this.context);
|
|
143
|
+
this.logger.info(`Plugin initialized: ${pluginName}`);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
this.logger.error(`Plugin init failed: ${pluginName}`, error);
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Run plugin start phase
|
|
152
|
+
* @param plugin - Plugin to start
|
|
153
|
+
*/
|
|
154
|
+
async runPluginStart(plugin) {
|
|
155
|
+
if (!plugin.start)
|
|
156
|
+
return;
|
|
157
|
+
const pluginName = plugin.name;
|
|
158
|
+
this.logger.info(`Starting plugin: ${pluginName}`);
|
|
159
|
+
try {
|
|
160
|
+
await plugin.start(this.context);
|
|
161
|
+
this.logger.info(`Plugin started: ${pluginName}`);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
this.logger.error(`Plugin start failed: ${pluginName}`, error);
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Run plugin destroy phase
|
|
170
|
+
* @param plugin - Plugin to destroy
|
|
171
|
+
*/
|
|
172
|
+
async runPluginDestroy(plugin) {
|
|
173
|
+
if (!plugin.destroy)
|
|
174
|
+
return;
|
|
175
|
+
const pluginName = plugin.name;
|
|
176
|
+
this.logger.info(`Destroying plugin: ${pluginName}`);
|
|
177
|
+
try {
|
|
178
|
+
await plugin.destroy();
|
|
179
|
+
this.logger.info(`Plugin destroyed: ${pluginName}`);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
this.logger.error(`Plugin destroy failed: ${pluginName}`, error);
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Trigger a hook with all registered handlers
|
|
188
|
+
* @param name - Hook name
|
|
189
|
+
* @param args - Arguments to pass to handlers
|
|
190
|
+
*/
|
|
191
|
+
async triggerHook(name, ...args) {
|
|
192
|
+
const handlers = this.hooks.get(name) || [];
|
|
193
|
+
this.logger.debug(`Triggering hook: ${name}`, {
|
|
194
|
+
hook: name,
|
|
195
|
+
handlerCount: handlers.length
|
|
196
|
+
});
|
|
197
|
+
for (const handler of handlers) {
|
|
198
|
+
try {
|
|
199
|
+
await handler(...args);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
this.logger.error(`Hook handler failed: ${name}`, error);
|
|
203
|
+
// Continue with other handlers even if one fails
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get current kernel state
|
|
209
|
+
*/
|
|
210
|
+
getState() {
|
|
211
|
+
return this.state;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get all registered plugins
|
|
215
|
+
*/
|
|
216
|
+
getPlugins() {
|
|
217
|
+
return new Map(this.plugins);
|
|
218
|
+
}
|
|
219
|
+
}
|
package/dist/kernel.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Plugin } from './types.js';
|
|
2
|
+
import type { LoggerConfig } from '@objectstack/spec/system';
|
|
3
|
+
import { ObjectKernelBase } from './kernel-base.js';
|
|
2
4
|
/**
|
|
3
5
|
* ObjectKernel - MiniKernel Architecture
|
|
4
6
|
*
|
|
@@ -7,31 +9,22 @@ import { Plugin } from './types.js';
|
|
|
7
9
|
* - Provides dependency injection via service registry
|
|
8
10
|
* - Implements event/hook system for inter-plugin communication
|
|
9
11
|
* - Handles dependency resolution (topological sort)
|
|
12
|
+
* - Provides configurable logging for server and browser
|
|
10
13
|
*
|
|
11
14
|
* Core philosophy:
|
|
12
15
|
* - Business logic is completely separated into plugins
|
|
13
16
|
* - Kernel only manages lifecycle, DI, and hooks
|
|
14
17
|
* - Plugins are loaded as equal building blocks
|
|
15
18
|
*/
|
|
16
|
-
export declare class ObjectKernel {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
private state;
|
|
21
|
-
/**
|
|
22
|
-
* Plugin context - shared across all plugins
|
|
23
|
-
*/
|
|
24
|
-
private context;
|
|
19
|
+
export declare class ObjectKernel extends ObjectKernelBase {
|
|
20
|
+
constructor(config?: {
|
|
21
|
+
logger?: Partial<LoggerConfig>;
|
|
22
|
+
});
|
|
25
23
|
/**
|
|
26
24
|
* Register a plugin
|
|
27
25
|
* @param plugin - Plugin instance
|
|
28
26
|
*/
|
|
29
27
|
use(plugin: Plugin): this;
|
|
30
|
-
/**
|
|
31
|
-
* Resolve plugin dependencies using topological sort
|
|
32
|
-
* @returns Ordered list of plugins
|
|
33
|
-
*/
|
|
34
|
-
private resolveDependencies;
|
|
35
28
|
/**
|
|
36
29
|
* Bootstrap the kernel
|
|
37
30
|
* 1. Resolve dependencies (topological sort)
|
|
@@ -45,6 +38,10 @@ export declare class ObjectKernel {
|
|
|
45
38
|
* Calls destroy on all plugins in reverse order
|
|
46
39
|
*/
|
|
47
40
|
shutdown(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Graceful shutdown - destroy all plugins in reverse order
|
|
43
|
+
*/
|
|
44
|
+
destroy(): Promise<void>;
|
|
48
45
|
/**
|
|
49
46
|
* Get a service from the registry
|
|
50
47
|
* Convenience method for external access
|
|
@@ -54,9 +51,5 @@ export declare class ObjectKernel {
|
|
|
54
51
|
* Check if kernel is running
|
|
55
52
|
*/
|
|
56
53
|
isRunning(): boolean;
|
|
57
|
-
/**
|
|
58
|
-
* Get kernel state
|
|
59
|
-
*/
|
|
60
|
-
getState(): string;
|
|
61
54
|
}
|
|
62
55
|
//# sourceMappingURL=kernel.d.ts.map
|
package/dist/kernel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kernel.d.ts","sourceRoot":"","sources":["../src/kernel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"kernel.d.ts","sourceRoot":"","sources":["../src/kernel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAa,SAAQ,gBAAgB;gBAClC,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;KAAE;IAQvD;;;OAGG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAYzB;;;;;;OAMG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IA8BhC;;;OAGG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B9B;;;OAGG;IACH,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC;IAI9B;;OAEG;IACH,SAAS,IAAI,OAAO;CAGvB"}
|
package/dist/kernel.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createLogger } from './logger.js';
|
|
2
|
+
import { ObjectKernelBase } from './kernel-base.js';
|
|
1
3
|
/**
|
|
2
4
|
* ObjectKernel - MiniKernel Architecture
|
|
3
5
|
*
|
|
@@ -6,63 +8,26 @@
|
|
|
6
8
|
* - Provides dependency injection via service registry
|
|
7
9
|
* - Implements event/hook system for inter-plugin communication
|
|
8
10
|
* - Handles dependency resolution (topological sort)
|
|
11
|
+
* - Provides configurable logging for server and browser
|
|
9
12
|
*
|
|
10
13
|
* Core philosophy:
|
|
11
14
|
* - Business logic is completely separated into plugins
|
|
12
15
|
* - Kernel only manages lifecycle, DI, and hooks
|
|
13
16
|
* - Plugins are loaded as equal building blocks
|
|
14
17
|
*/
|
|
15
|
-
export class ObjectKernel {
|
|
16
|
-
constructor() {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.
|
|
21
|
-
/**
|
|
22
|
-
* Plugin context - shared across all plugins
|
|
23
|
-
*/
|
|
24
|
-
this.context = {
|
|
25
|
-
registerService: (name, service) => {
|
|
26
|
-
if (this.services.has(name)) {
|
|
27
|
-
throw new Error(`[Kernel] Service '${name}' already registered`);
|
|
28
|
-
}
|
|
29
|
-
this.services.set(name, service);
|
|
30
|
-
this.context.logger.log(`[Kernel] Service '${name}' registered`);
|
|
31
|
-
},
|
|
32
|
-
getService: (name) => {
|
|
33
|
-
const service = this.services.get(name);
|
|
34
|
-
if (!service) {
|
|
35
|
-
throw new Error(`[Kernel] Service '${name}' not found`);
|
|
36
|
-
}
|
|
37
|
-
return service;
|
|
38
|
-
},
|
|
39
|
-
hook: (name, handler) => {
|
|
40
|
-
if (!this.hooks.has(name)) {
|
|
41
|
-
this.hooks.set(name, []);
|
|
42
|
-
}
|
|
43
|
-
this.hooks.get(name).push(handler);
|
|
44
|
-
},
|
|
45
|
-
trigger: async (name, ...args) => {
|
|
46
|
-
const handlers = this.hooks.get(name) || [];
|
|
47
|
-
for (const handler of handlers) {
|
|
48
|
-
await handler(...args);
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
getServices: () => {
|
|
52
|
-
return new Map(this.services);
|
|
53
|
-
},
|
|
54
|
-
logger: console,
|
|
55
|
-
getKernel: () => this,
|
|
56
|
-
};
|
|
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();
|
|
57
24
|
}
|
|
58
25
|
/**
|
|
59
26
|
* Register a plugin
|
|
60
27
|
* @param plugin - Plugin instance
|
|
61
28
|
*/
|
|
62
29
|
use(plugin) {
|
|
63
|
-
|
|
64
|
-
throw new Error('[Kernel] Cannot register plugins after bootstrap has started');
|
|
65
|
-
}
|
|
30
|
+
this.validateIdle();
|
|
66
31
|
const pluginName = plugin.name;
|
|
67
32
|
if (this.plugins.has(pluginName)) {
|
|
68
33
|
throw new Error(`[Kernel] Plugin '${pluginName}' already registered`);
|
|
@@ -70,43 +35,6 @@ export class ObjectKernel {
|
|
|
70
35
|
this.plugins.set(pluginName, plugin);
|
|
71
36
|
return this;
|
|
72
37
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Resolve plugin dependencies using topological sort
|
|
75
|
-
* @returns Ordered list of plugins
|
|
76
|
-
*/
|
|
77
|
-
resolveDependencies() {
|
|
78
|
-
const resolved = [];
|
|
79
|
-
const visited = new Set();
|
|
80
|
-
const visiting = new Set();
|
|
81
|
-
const visit = (pluginName) => {
|
|
82
|
-
if (visited.has(pluginName))
|
|
83
|
-
return;
|
|
84
|
-
if (visiting.has(pluginName)) {
|
|
85
|
-
throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
|
|
86
|
-
}
|
|
87
|
-
const plugin = this.plugins.get(pluginName);
|
|
88
|
-
if (!plugin) {
|
|
89
|
-
throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
|
|
90
|
-
}
|
|
91
|
-
visiting.add(pluginName);
|
|
92
|
-
// Visit dependencies first
|
|
93
|
-
const deps = plugin.dependencies || [];
|
|
94
|
-
for (const dep of deps) {
|
|
95
|
-
if (!this.plugins.has(dep)) {
|
|
96
|
-
throw new Error(`[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`);
|
|
97
|
-
}
|
|
98
|
-
visit(dep);
|
|
99
|
-
}
|
|
100
|
-
visiting.delete(pluginName);
|
|
101
|
-
visited.add(pluginName);
|
|
102
|
-
resolved.push(plugin);
|
|
103
|
-
};
|
|
104
|
-
// Visit all plugins
|
|
105
|
-
for (const pluginName of this.plugins.keys()) {
|
|
106
|
-
visit(pluginName);
|
|
107
|
-
}
|
|
108
|
-
return resolved;
|
|
109
|
-
}
|
|
110
38
|
/**
|
|
111
39
|
* Bootstrap the kernel
|
|
112
40
|
* 1. Resolve dependencies (topological sort)
|
|
@@ -115,51 +43,58 @@ export class ObjectKernel {
|
|
|
115
43
|
* 4. Trigger 'kernel:ready' hook
|
|
116
44
|
*/
|
|
117
45
|
async bootstrap() {
|
|
118
|
-
|
|
119
|
-
throw new Error('[Kernel] Kernel already bootstrapped');
|
|
120
|
-
}
|
|
46
|
+
this.validateState('idle');
|
|
121
47
|
this.state = 'initializing';
|
|
122
|
-
this.
|
|
48
|
+
this.logger.info('Bootstrap started');
|
|
123
49
|
// Resolve dependencies
|
|
124
50
|
const orderedPlugins = this.resolveDependencies();
|
|
125
51
|
// Phase 1: Init - Plugins register services
|
|
126
|
-
this.
|
|
52
|
+
this.logger.info('Phase 1: Init plugins');
|
|
127
53
|
for (const plugin of orderedPlugins) {
|
|
128
|
-
this.
|
|
129
|
-
await plugin.init(this.context);
|
|
54
|
+
await this.runPluginInit(plugin);
|
|
130
55
|
}
|
|
131
56
|
// Phase 2: Start - Plugins execute business logic
|
|
132
|
-
this.
|
|
57
|
+
this.logger.info('Phase 2: Start plugins');
|
|
133
58
|
this.state = 'running';
|
|
134
59
|
for (const plugin of orderedPlugins) {
|
|
135
|
-
|
|
136
|
-
this.context.logger.log(`[Kernel] Start: ${plugin.name}`);
|
|
137
|
-
await plugin.start(this.context);
|
|
138
|
-
}
|
|
60
|
+
await this.runPluginStart(plugin);
|
|
139
61
|
}
|
|
140
|
-
//
|
|
141
|
-
this.
|
|
142
|
-
|
|
143
|
-
|
|
62
|
+
// Trigger ready hook
|
|
63
|
+
await this.triggerHook('kernel:ready');
|
|
64
|
+
this.logger.info('✅ Bootstrap complete', {
|
|
65
|
+
pluginCount: this.plugins.size
|
|
66
|
+
});
|
|
144
67
|
}
|
|
145
68
|
/**
|
|
146
69
|
* Shutdown the kernel
|
|
147
70
|
* Calls destroy on all plugins in reverse order
|
|
148
71
|
*/
|
|
149
72
|
async shutdown() {
|
|
150
|
-
|
|
151
|
-
|
|
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');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
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);
|
|
152
91
|
}
|
|
153
|
-
this.context.logger.log('[Kernel] Shutdown started...');
|
|
154
92
|
this.state = 'stopped';
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
await plugin.destroy();
|
|
160
|
-
}
|
|
93
|
+
this.logger.info('✅ Shutdown complete');
|
|
94
|
+
// Cleanup logger resources
|
|
95
|
+
if (this.logger && typeof this.logger.destroy === 'function') {
|
|
96
|
+
await this.logger.destroy();
|
|
161
97
|
}
|
|
162
|
-
this.context.logger.log('[Kernel] ✅ Shutdown complete');
|
|
163
98
|
}
|
|
164
99
|
/**
|
|
165
100
|
* Get a service from the registry
|
|
@@ -174,10 +109,4 @@ export class ObjectKernel {
|
|
|
174
109
|
isRunning() {
|
|
175
110
|
return this.state === 'running';
|
|
176
111
|
}
|
|
177
|
-
/**
|
|
178
|
-
* Get kernel state
|
|
179
|
-
*/
|
|
180
|
-
getState() {
|
|
181
|
-
return this.state;
|
|
182
|
-
}
|
|
183
112
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kernel.test.d.ts","sourceRoot":"","sources":["../src/kernel.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { ObjectKernel } from './kernel';
|
|
3
|
+
describe('ObjectKernel with Configurable Logger', () => {
|
|
4
|
+
let kernel;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
kernel = new ObjectKernel();
|
|
7
|
+
});
|
|
8
|
+
describe('Logger Configuration', () => {
|
|
9
|
+
it('should create kernel with default logger', () => {
|
|
10
|
+
expect(kernel).toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
it('should create kernel with custom logger config', async () => {
|
|
13
|
+
const customKernel = new ObjectKernel({
|
|
14
|
+
logger: {
|
|
15
|
+
level: 'debug',
|
|
16
|
+
format: 'pretty',
|
|
17
|
+
sourceLocation: true
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
expect(customKernel).toBeDefined();
|
|
21
|
+
// Cleanup
|
|
22
|
+
await customKernel.bootstrap();
|
|
23
|
+
await customKernel.shutdown();
|
|
24
|
+
});
|
|
25
|
+
it('should create kernel with file logging config', async () => {
|
|
26
|
+
const fileKernel = new ObjectKernel({
|
|
27
|
+
logger: {
|
|
28
|
+
level: 'info',
|
|
29
|
+
format: 'json',
|
|
30
|
+
file: '/tmp/test-kernel.log'
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
expect(fileKernel).toBeDefined();
|
|
34
|
+
// Cleanup
|
|
35
|
+
await fileKernel.bootstrap();
|
|
36
|
+
await fileKernel.shutdown();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe('Plugin Context Logger', () => {
|
|
40
|
+
it('should provide logger to plugins', async () => {
|
|
41
|
+
let loggerReceived = false;
|
|
42
|
+
const testPlugin = {
|
|
43
|
+
name: 'test-plugin',
|
|
44
|
+
init: async (ctx) => {
|
|
45
|
+
if (ctx.logger) {
|
|
46
|
+
loggerReceived = true;
|
|
47
|
+
ctx.logger.info('Plugin initialized', { plugin: 'test-plugin' });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
kernel.use(testPlugin);
|
|
52
|
+
await kernel.bootstrap();
|
|
53
|
+
expect(loggerReceived).toBe(true);
|
|
54
|
+
await kernel.shutdown();
|
|
55
|
+
});
|
|
56
|
+
it('should allow plugins to use all log levels', async () => {
|
|
57
|
+
const logCalls = [];
|
|
58
|
+
const loggingPlugin = {
|
|
59
|
+
name: 'logging-plugin',
|
|
60
|
+
init: async (ctx) => {
|
|
61
|
+
ctx.logger.debug('Debug message');
|
|
62
|
+
logCalls.push('debug');
|
|
63
|
+
ctx.logger.info('Info message');
|
|
64
|
+
logCalls.push('info');
|
|
65
|
+
ctx.logger.warn('Warning message');
|
|
66
|
+
logCalls.push('warn');
|
|
67
|
+
ctx.logger.error('Error message');
|
|
68
|
+
logCalls.push('error');
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
kernel.use(loggingPlugin);
|
|
72
|
+
await kernel.bootstrap();
|
|
73
|
+
expect(logCalls).toContain('debug');
|
|
74
|
+
expect(logCalls).toContain('info');
|
|
75
|
+
expect(logCalls).toContain('warn');
|
|
76
|
+
expect(logCalls).toContain('error');
|
|
77
|
+
await kernel.shutdown();
|
|
78
|
+
});
|
|
79
|
+
it('should support metadata in logs', async () => {
|
|
80
|
+
const metadataPlugin = {
|
|
81
|
+
name: 'metadata-plugin',
|
|
82
|
+
init: async (ctx) => {
|
|
83
|
+
ctx.logger.info('User action', {
|
|
84
|
+
userId: '123',
|
|
85
|
+
action: 'create',
|
|
86
|
+
resource: 'document'
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
kernel.use(metadataPlugin);
|
|
91
|
+
await kernel.bootstrap();
|
|
92
|
+
await kernel.shutdown();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe('Kernel Lifecycle Logging', () => {
|
|
96
|
+
it('should log bootstrap process', async () => {
|
|
97
|
+
const plugin = {
|
|
98
|
+
name: 'lifecycle-test',
|
|
99
|
+
init: async () => {
|
|
100
|
+
// Init logic
|
|
101
|
+
},
|
|
102
|
+
start: async () => {
|
|
103
|
+
// Start logic
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
kernel.use(plugin);
|
|
107
|
+
await kernel.bootstrap();
|
|
108
|
+
expect(kernel.isRunning()).toBe(true);
|
|
109
|
+
await kernel.shutdown();
|
|
110
|
+
});
|
|
111
|
+
it('should log shutdown process', async () => {
|
|
112
|
+
const plugin = {
|
|
113
|
+
name: 'shutdown-test',
|
|
114
|
+
init: async () => { },
|
|
115
|
+
destroy: async () => {
|
|
116
|
+
// Cleanup
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
kernel.use(plugin);
|
|
120
|
+
await kernel.bootstrap();
|
|
121
|
+
await kernel.shutdown();
|
|
122
|
+
expect(kernel.getState()).toBe('stopped');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe('Environment Compatibility', () => {
|
|
126
|
+
it('should work in Node.js environment', async () => {
|
|
127
|
+
const nodeKernel = new ObjectKernel({
|
|
128
|
+
logger: {
|
|
129
|
+
level: 'info',
|
|
130
|
+
format: 'json'
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
const plugin = {
|
|
134
|
+
name: 'node-test',
|
|
135
|
+
init: async (ctx) => {
|
|
136
|
+
ctx.logger.info('Running in Node.js');
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
nodeKernel.use(plugin);
|
|
140
|
+
await nodeKernel.bootstrap();
|
|
141
|
+
await nodeKernel.shutdown();
|
|
142
|
+
});
|
|
143
|
+
it('should support browser-friendly logging', async () => {
|
|
144
|
+
const browserKernel = new ObjectKernel({
|
|
145
|
+
logger: {
|
|
146
|
+
level: 'info',
|
|
147
|
+
format: 'pretty'
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
const plugin = {
|
|
151
|
+
name: 'browser-test',
|
|
152
|
+
init: async (ctx) => {
|
|
153
|
+
ctx.logger.info('Browser-friendly format');
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
browserKernel.use(plugin);
|
|
157
|
+
await browserKernel.bootstrap();
|
|
158
|
+
await browserKernel.shutdown();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|