@objectstack/core 0.9.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{ENHANCED_FEATURES.md → ADVANCED_FEATURES.md} +13 -13
- package/CHANGELOG.md +21 -0
- package/PHASE2_IMPLEMENTATION.md +388 -0
- package/README.md +12 -341
- package/REFACTORING_SUMMARY.md +40 -0
- package/dist/api-registry-plugin.test.js +23 -21
- package/dist/api-registry.test.js +2 -2
- package/dist/dependency-resolver.d.ts +62 -0
- package/dist/dependency-resolver.d.ts.map +1 -0
- package/dist/dependency-resolver.js +317 -0
- package/dist/dependency-resolver.test.d.ts +2 -0
- package/dist/dependency-resolver.test.d.ts.map +1 -0
- package/dist/dependency-resolver.test.js +241 -0
- package/dist/health-monitor.d.ts +65 -0
- package/dist/health-monitor.d.ts.map +1 -0
- package/dist/health-monitor.js +269 -0
- package/dist/health-monitor.test.d.ts +2 -0
- package/dist/health-monitor.test.d.ts.map +1 -0
- package/dist/health-monitor.test.js +68 -0
- package/dist/hot-reload.d.ts +79 -0
- package/dist/hot-reload.d.ts.map +1 -0
- package/dist/hot-reload.js +313 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/kernel-base.d.ts +2 -2
- package/dist/kernel-base.js +2 -2
- package/dist/kernel.d.ts +89 -31
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +430 -73
- package/dist/kernel.test.js +375 -122
- package/dist/lite-kernel.d.ts +55 -0
- package/dist/lite-kernel.d.ts.map +1 -0
- package/dist/lite-kernel.js +112 -0
- package/dist/lite-kernel.test.d.ts +2 -0
- package/dist/lite-kernel.test.d.ts.map +1 -0
- package/dist/lite-kernel.test.js +161 -0
- package/dist/logger.d.ts +2 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +26 -7
- package/dist/plugin-loader.d.ts +15 -0
- package/dist/plugin-loader.d.ts.map +1 -1
- package/dist/plugin-loader.js +40 -10
- package/dist/plugin-loader.test.js +9 -0
- package/dist/security/index.d.ts +3 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +4 -0
- package/dist/security/permission-manager.d.ts +96 -0
- package/dist/security/permission-manager.d.ts.map +1 -0
- package/dist/security/permission-manager.js +235 -0
- package/dist/security/permission-manager.test.d.ts +2 -0
- package/dist/security/permission-manager.test.d.ts.map +1 -0
- package/dist/security/permission-manager.test.js +220 -0
- package/dist/security/plugin-permission-enforcer.d.ts +1 -1
- package/dist/security/sandbox-runtime.d.ts +115 -0
- package/dist/security/sandbox-runtime.d.ts.map +1 -0
- package/dist/security/sandbox-runtime.js +310 -0
- package/dist/security/security-scanner.d.ts +92 -0
- package/dist/security/security-scanner.d.ts.map +1 -0
- package/dist/security/security-scanner.js +273 -0
- package/examples/{enhanced-kernel-example.ts → kernel-features-example.ts} +6 -6
- package/examples/phase2-integration.ts +355 -0
- package/package.json +3 -2
- package/src/api-registry-plugin.test.ts +23 -21
- package/src/api-registry.test.ts +2 -2
- package/src/dependency-resolver.test.ts +287 -0
- package/src/dependency-resolver.ts +388 -0
- package/src/health-monitor.test.ts +81 -0
- package/src/health-monitor.ts +316 -0
- package/src/hot-reload.ts +388 -0
- package/src/index.ts +6 -1
- package/src/kernel-base.ts +2 -2
- package/src/kernel.test.ts +471 -134
- package/src/kernel.ts +518 -76
- package/src/lite-kernel.test.ts +200 -0
- package/src/lite-kernel.ts +135 -0
- package/src/logger.ts +28 -7
- package/src/plugin-loader.test.ts +10 -1
- package/src/plugin-loader.ts +49 -13
- package/src/security/index.ts +19 -0
- package/src/security/permission-manager.test.ts +256 -0
- package/src/security/permission-manager.ts +336 -0
- package/src/security/plugin-permission-enforcer.test.ts +1 -1
- package/src/security/plugin-permission-enforcer.ts +1 -1
- package/src/security/sandbox-runtime.ts +432 -0
- package/src/security/security-scanner.ts +365 -0
- package/dist/enhanced-kernel.d.ts +0 -103
- package/dist/enhanced-kernel.d.ts.map +0 -1
- package/dist/enhanced-kernel.js +0 -403
- package/dist/enhanced-kernel.test.d.ts +0 -2
- package/dist/enhanced-kernel.test.d.ts.map +0 -1
- package/dist/enhanced-kernel.test.js +0 -412
- package/src/enhanced-kernel.test.ts +0 -535
- package/src/enhanced-kernel.ts +0 -496
package/README.md
CHANGED
|
@@ -1,364 +1,35 @@
|
|
|
1
1
|
# @objectstack/core
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
This package defines the fundamental runtime mechanics of the ObjectStack architecture:
|
|
8
|
-
1. **Dependency Injection (DI)**: Advanced service registry with factory functions and lifecycle management
|
|
9
|
-
2. **Plugin Lifecycle**: `init` (Registration) -> `start` (Execution) -> `destroy` (Cleanup)
|
|
10
|
-
3. **Event Bus**: Simple hook system (`hook`, `trigger`) for event-driven communication
|
|
11
|
-
4. **Configurable Logging**: Universal logger using [Pino](https://github.com/pinojs/pino) for Node.js and simple console for browsers
|
|
12
|
-
5. **Enhanced Features**: Version compatibility, health checks, timeout control, graceful shutdown, and more
|
|
13
|
-
|
|
14
|
-
It is completely agnostic of "Data", "HTTP", or "Apps". It only knows `Plugin` and `Service`.
|
|
3
|
+
The **Kernel** of the ObjectStack architecture. It provides the fundamental building blocks for a modular, plugin-based system.
|
|
15
4
|
|
|
16
5
|
## Features
|
|
17
6
|
|
|
18
|
-
|
|
19
|
-
- **Plugin
|
|
20
|
-
- **Service
|
|
21
|
-
- **
|
|
22
|
-
- **High-Performance Logging**:
|
|
23
|
-
- Node.js: Powered by [Pino](https://github.com/pinojs/pino) - extremely fast, low-overhead structured logging
|
|
24
|
-
- Browser: Lightweight console-based logger
|
|
25
|
-
- **Environment Detection**: Automatic runtime detection (Node.js/browser)
|
|
26
|
-
- **Dependency Resolution**: Automatic topological sorting of plugin dependencies
|
|
27
|
-
- **Security**: Automatic sensitive data redaction in logs
|
|
28
|
-
|
|
29
|
-
### Enhanced Features (EnhancedObjectKernel)
|
|
30
|
-
- **Async Plugin Loading**: Load plugins asynchronously with validation
|
|
31
|
-
- **Version Compatibility**: Semantic versioning support and validation
|
|
32
|
-
- **Plugin Signatures**: Security verification (extensible)
|
|
33
|
-
- **Configuration Validation**: Zod-based schema validation for plugin configs
|
|
34
|
-
- **Service Factories**: Factory-based service instantiation with lifecycle control
|
|
35
|
-
- **Service Lifecycles**: Singleton, Transient, and Scoped service management
|
|
36
|
-
- **Circular Dependency Detection**: Automatic detection and reporting
|
|
37
|
-
- **Lazy Loading**: Services created on-demand
|
|
38
|
-
- **Timeout Control**: Configurable timeouts for plugin initialization
|
|
39
|
-
- **Failure Rollback**: Automatic rollback on startup failures
|
|
40
|
-
- **Health Checks**: Monitor plugin health at runtime
|
|
41
|
-
- **Performance Metrics**: Track plugin startup times
|
|
42
|
-
- **Graceful Shutdown**: Proper cleanup with timeout control
|
|
7
|
+
- **ObjectKernel**: A robust Dependency Injection (DI) container and plugin manager.
|
|
8
|
+
- **Plugin Architecture**: A standard interface (`Plugin`) with lifecycle hooks (`init`, `start`, `stop`).
|
|
9
|
+
- **Service Management**: Register and resolve services with type safety.
|
|
10
|
+
- **Logging**: Structured logging interface with swappable backends.
|
|
43
11
|
|
|
44
12
|
## Installation
|
|
45
13
|
|
|
46
14
|
```bash
|
|
47
|
-
npm install @objectstack/core
|
|
48
|
-
# or
|
|
49
15
|
pnpm add @objectstack/core
|
|
50
16
|
```
|
|
51
17
|
|
|
52
|
-
##
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
import { ObjectKernel, Plugin, PluginContext } from '@objectstack/core';
|
|
56
|
-
|
|
57
|
-
// 1. Define a Plugin
|
|
58
|
-
const myPlugin: Plugin = {
|
|
59
|
-
name: 'my-plugin',
|
|
60
|
-
|
|
61
|
-
async init(ctx: PluginContext) {
|
|
62
|
-
ctx.logger.info('Initializing plugin');
|
|
63
|
-
ctx.registerService('my-service', { hello: 'world' });
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// 2. Boot Kernel with logging config
|
|
68
|
-
const kernel = new ObjectKernel({
|
|
69
|
-
logger: {
|
|
70
|
-
level: 'info',
|
|
71
|
-
format: 'pretty'
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
kernel.use(myPlugin);
|
|
76
|
-
await kernel.bootstrap();
|
|
77
|
-
|
|
78
|
-
// 3. Use Service
|
|
79
|
-
const service = kernel.getService('my-service');
|
|
80
|
-
|
|
81
|
-
// 4. Cleanup
|
|
82
|
-
await kernel.shutdown();
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## 🤖 AI Quick Reference
|
|
86
|
-
|
|
87
|
-
**For AI Agents:** This package implements a microkernel architecture. Key concepts:
|
|
88
|
-
|
|
89
|
-
1. **Plugin Lifecycle**: `init()` → `start()` → `destroy()`
|
|
90
|
-
2. **Service Registry**: Share functionality via `ctx.registerService(name, service)` and `ctx.getService(name)`
|
|
91
|
-
3. **Dependencies**: Declare plugin dependencies for automatic load ordering
|
|
92
|
-
4. **Hooks/Events**: Decouple plugins with `ctx.hook(event, handler)` and `ctx.trigger(event, ...args)`
|
|
93
|
-
5. **Logger**: Always use `ctx.logger` for consistent, structured logging
|
|
94
|
-
|
|
95
|
-
**Common Plugin Pattern:**
|
|
96
|
-
```typescript
|
|
97
|
-
const plugin: Plugin = {
|
|
98
|
-
name: 'my-plugin',
|
|
99
|
-
dependencies: ['other-plugin'], // Load after these plugins
|
|
100
|
-
|
|
101
|
-
async init(ctx: PluginContext) {
|
|
102
|
-
// Register services and hooks
|
|
103
|
-
const otherService = ctx.getService('other-service');
|
|
104
|
-
ctx.registerService('my-service', new MyService(otherService));
|
|
105
|
-
ctx.hook('data:created', async (data) => { /* ... */ });
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
async start(ctx: PluginContext) {
|
|
109
|
-
// Execute business logic
|
|
110
|
-
const service = ctx.getService('my-service');
|
|
111
|
-
await service.initialize();
|
|
112
|
-
},
|
|
113
|
-
|
|
114
|
-
async destroy() {
|
|
115
|
-
// Cleanup resources
|
|
116
|
-
await service.close();
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## Configurable Logger
|
|
122
|
-
|
|
123
|
-
The logger uses **[Pino](https://github.com/pinojs/pino)** for Node.js environments (high-performance, low-overhead) and a simple console-based logger for browsers. It automatically detects the runtime environment.
|
|
124
|
-
|
|
125
|
-
### Why Pino?
|
|
126
|
-
|
|
127
|
-
- **Fast**: One of the fastest Node.js loggers available
|
|
128
|
-
- **Low Overhead**: Minimal performance impact on your application
|
|
129
|
-
- **Structured Logging**: Native JSON output for log aggregation tools
|
|
130
|
-
- **Production Ready**: Battle-tested in production environments
|
|
131
|
-
- **Feature Rich**: Automatic log rotation, transports, child loggers, and more
|
|
132
|
-
|
|
133
|
-
### Logger Configuration
|
|
18
|
+
## Basic Usage
|
|
134
19
|
|
|
135
20
|
```typescript
|
|
136
|
-
|
|
137
|
-
logger: {
|
|
138
|
-
level: 'debug', // 'debug' | 'info' | 'warn' | 'error' | 'fatal'
|
|
139
|
-
format: 'pretty', // 'json' | 'text' | 'pretty'
|
|
140
|
-
sourceLocation: true, // Include file/line numbers
|
|
141
|
-
redact: ['password', 'token', 'apiKey'], // Keys to redact
|
|
142
|
-
file: './logs/app.log', // Node.js only
|
|
143
|
-
rotation: { // File rotation (Node.js only)
|
|
144
|
-
maxSize: '10m',
|
|
145
|
-
maxFiles: 5
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
```
|
|
21
|
+
import { ObjectKernel } from '@objectstack/core';
|
|
150
22
|
|
|
151
|
-
|
|
23
|
+
const kernel = new ObjectKernel();
|
|
152
24
|
|
|
153
|
-
|
|
154
|
-
|
|
25
|
+
// Register a simple plugin
|
|
26
|
+
kernel.use({
|
|
155
27
|
name: 'my-plugin',
|
|
156
|
-
|
|
157
|
-
init: async (ctx: PluginContext) => {
|
|
158
|
-
// Basic logging
|
|
159
|
-
ctx.logger.info('Plugin initialized');
|
|
160
|
-
ctx.logger.debug('Debug info', { details: 'data' });
|
|
161
|
-
ctx.logger.warn('Warning message');
|
|
162
|
-
ctx.logger.error('Error occurred', new Error('Oops'));
|
|
163
|
-
|
|
164
|
-
// Sensitive data is automatically redacted
|
|
165
|
-
ctx.logger.info('User login', {
|
|
166
|
-
username: 'john',
|
|
167
|
-
password: 'secret123' // Logged as '***REDACTED***'
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Standalone Logger
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
import { createLogger } from '@objectstack/core';
|
|
177
|
-
|
|
178
|
-
const logger = createLogger({
|
|
179
|
-
level: 'info',
|
|
180
|
-
format: 'json'
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
logger.info('Application started');
|
|
184
|
-
|
|
185
|
-
// Child logger with context
|
|
186
|
-
const requestLogger = logger.child({
|
|
187
|
-
requestId: '123',
|
|
188
|
-
userId: 'user-456'
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
requestLogger.info('Processing request');
|
|
192
|
-
|
|
193
|
-
// Distributed tracing
|
|
194
|
-
const tracedLogger = logger.withTrace('trace-id-123', 'span-id-456');
|
|
195
|
-
|
|
196
|
-
// Cleanup
|
|
197
|
-
await logger.destroy();
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
## Log Formats
|
|
201
|
-
|
|
202
|
-
### JSON (default for Node.js)
|
|
203
|
-
```json
|
|
204
|
-
{"timestamp":"2026-01-29T22:47:36.441Z","level":"info","message":"User action","context":{"userId":"123"}}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### Text
|
|
208
|
-
```
|
|
209
|
-
2026-01-29T22:47:36.441Z | INFO | User action | {"userId":"123"}
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### Pretty (default for browser)
|
|
213
|
-
```
|
|
214
|
-
[INFO] User action { userId: '123' }
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
## Plugin Development
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
import { Plugin, PluginContext } from '@objectstack/core';
|
|
221
|
-
|
|
222
|
-
const databasePlugin: Plugin = {
|
|
223
|
-
name: 'database',
|
|
224
28
|
version: '1.0.0',
|
|
225
|
-
|
|
226
|
-
init: async (ctx: PluginContext) => {
|
|
227
|
-
const db = await connectToDatabase();
|
|
228
|
-
ctx.registerService('db', db);
|
|
229
|
-
ctx.logger.info('Database connected');
|
|
230
|
-
},
|
|
231
|
-
|
|
232
|
-
start: async (ctx: PluginContext) => {
|
|
233
|
-
ctx.logger.info('Database ready');
|
|
234
|
-
},
|
|
235
|
-
|
|
236
|
-
destroy: async () => {
|
|
237
|
-
await db.close();
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
const apiPlugin: Plugin = {
|
|
242
|
-
name: 'api',
|
|
243
|
-
dependencies: ['database'], // Load after database
|
|
244
|
-
|
|
245
|
-
init: async (ctx: PluginContext) => {
|
|
246
|
-
const db = ctx.getService('db');
|
|
247
|
-
const server = createServer(db);
|
|
248
|
-
ctx.registerService('api', server);
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
kernel.use(databasePlugin);
|
|
253
|
-
kernel.use(apiPlugin);
|
|
254
|
-
await kernel.bootstrap();
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
## Enhanced Kernel Usage
|
|
258
|
-
|
|
259
|
-
For production applications, use `EnhancedObjectKernel` for advanced features:
|
|
260
|
-
|
|
261
|
-
```typescript
|
|
262
|
-
import { EnhancedObjectKernel, PluginMetadata, ServiceLifecycle } from '@objectstack/core';
|
|
263
|
-
|
|
264
|
-
// Create enhanced kernel
|
|
265
|
-
const kernel = new EnhancedObjectKernel({
|
|
266
|
-
logger: { level: 'info', format: 'pretty' },
|
|
267
|
-
defaultStartupTimeout: 30000, // 30 seconds
|
|
268
|
-
gracefulShutdown: true,
|
|
269
|
-
shutdownTimeout: 60000, // 60 seconds
|
|
270
|
-
rollbackOnFailure: true, // Rollback on failures
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// Plugin with version and health check
|
|
274
|
-
const plugin: PluginMetadata = {
|
|
275
|
-
name: 'my-plugin',
|
|
276
|
-
version: '1.2.3',
|
|
277
|
-
startupTimeout: 10000,
|
|
278
|
-
|
|
279
29
|
async init(ctx) {
|
|
280
|
-
ctx.
|
|
281
|
-
},
|
|
282
|
-
|
|
283
|
-
async healthCheck() {
|
|
284
|
-
return {
|
|
285
|
-
healthy: true,
|
|
286
|
-
message: 'Service is operational'
|
|
287
|
-
};
|
|
30
|
+
ctx.logger.info('Plugin initializing...');
|
|
288
31
|
}
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
// Register service factory with lifecycle
|
|
292
|
-
kernel.registerServiceFactory(
|
|
293
|
-
'database',
|
|
294
|
-
async (ctx) => await connectToDatabase(),
|
|
295
|
-
ServiceLifecycle.SINGLETON
|
|
296
|
-
);
|
|
32
|
+
});
|
|
297
33
|
|
|
298
|
-
await kernel.use(plugin);
|
|
299
34
|
await kernel.bootstrap();
|
|
300
|
-
|
|
301
|
-
// Check health
|
|
302
|
-
const health = await kernel.checkPluginHealth('my-plugin');
|
|
303
|
-
console.log(health);
|
|
304
|
-
|
|
305
|
-
// Get metrics
|
|
306
|
-
const metrics = kernel.getPluginMetrics();
|
|
307
|
-
console.log(metrics);
|
|
308
|
-
|
|
309
|
-
// Graceful shutdown
|
|
310
|
-
await kernel.shutdown();
|
|
311
35
|
```
|
|
312
|
-
|
|
313
|
-
See [ENHANCED_FEATURES.md](./ENHANCED_FEATURES.md) for comprehensive documentation.
|
|
314
|
-
See [examples/enhanced-kernel-example.ts](./examples/enhanced-kernel-example.ts) for a complete example.
|
|
315
|
-
|
|
316
|
-
## Environment Support
|
|
317
|
-
|
|
318
|
-
### Node.js Features (via Pino)
|
|
319
|
-
- High-performance structured logging
|
|
320
|
-
- Automatic file logging with rotation
|
|
321
|
-
- JSON format for log aggregation tools (Elasticsearch, Splunk, etc.)
|
|
322
|
-
- Pretty printing for development (via pino-pretty)
|
|
323
|
-
- Child loggers with inherited context
|
|
324
|
-
- Minimal performance overhead
|
|
325
|
-
|
|
326
|
-
### Browser Features
|
|
327
|
-
- Pretty console output with colors
|
|
328
|
-
- DevTools integration
|
|
329
|
-
- Lightweight implementation
|
|
330
|
-
- No external dependencies
|
|
331
|
-
|
|
332
|
-
## Security
|
|
333
|
-
|
|
334
|
-
Automatic sensitive data redaction:
|
|
335
|
-
- Default keys: `password`, `token`, `secret`, `key`
|
|
336
|
-
- Configurable via `redact` option
|
|
337
|
-
- Recursive through nested objects
|
|
338
|
-
|
|
339
|
-
## API Reference
|
|
340
|
-
|
|
341
|
-
### ObjectKernel (Basic)
|
|
342
|
-
- `ObjectKernel` - Basic microkernel class
|
|
343
|
-
- `createLogger(config)` - Create standalone logger
|
|
344
|
-
- `Plugin` - Plugin interface
|
|
345
|
-
- `PluginContext` - Runtime context for plugins
|
|
346
|
-
- `Logger` - Logger interface
|
|
347
|
-
|
|
348
|
-
### EnhancedObjectKernel (Advanced)
|
|
349
|
-
- `EnhancedObjectKernel` - Enhanced microkernel with production features
|
|
350
|
-
- `PluginLoader` - Plugin loading and validation
|
|
351
|
-
- `ServiceLifecycle` - Service lifecycle management (SINGLETON, TRANSIENT, SCOPED)
|
|
352
|
-
- `PluginMetadata` - Extended plugin interface with metadata
|
|
353
|
-
- `PluginHealthStatus` - Health check result interface
|
|
354
|
-
|
|
355
|
-
See [TypeScript definitions](./src/types.ts) for complete API.
|
|
356
|
-
|
|
357
|
-
## Documentation
|
|
358
|
-
|
|
359
|
-
- [ENHANCED_FEATURES.md](./ENHANCED_FEATURES.md) - Comprehensive guide to enhanced features
|
|
360
|
-
- [examples/enhanced-kernel-example.ts](./examples/enhanced-kernel-example.ts) - Complete working example
|
|
361
|
-
|
|
362
|
-
## License
|
|
363
|
-
|
|
364
|
-
Apache-2.0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Kernel Refactoring Summary
|
|
2
|
+
|
|
3
|
+
This document summarizes the critical architectural improvements made to the `packages/core` module to address stability, performance, and correctness issues.
|
|
4
|
+
|
|
5
|
+
## 1. Dependency Injection Context Fix
|
|
6
|
+
**Problem:** Service factories were receiving an empty object `{}` instead of the real `PluginContext`.
|
|
7
|
+
**Fix:**
|
|
8
|
+
- Updated `PluginLoader.createServiceInstance` to ensure `this.context` is passed to factories.
|
|
9
|
+
- Added `PluginLoader.setContext` method to inject the context from the Kernel.
|
|
10
|
+
- Kernel now properly injects itself and the loader into the context before initialization.
|
|
11
|
+
|
|
12
|
+
## 2. Sync/Async Gap (Service Availability)
|
|
13
|
+
**Problem:** Services created asynchronously (via `awaitFactory`) were not accessible synchronously immediately after initialization, breaking code that expected `getService` to return an instance if the plugin was loaded.
|
|
14
|
+
**Fix:**
|
|
15
|
+
- Implemented L2 caching via `PluginLoader.getServiceInstance<T>(name: string)`.
|
|
16
|
+
- Kernel's `getService` now checks this synchronous cache first before falling back to the async path.
|
|
17
|
+
- Ensured singleton instances are stored in `serviceInstances` immediately upon creation.
|
|
18
|
+
|
|
19
|
+
## 3. Runtime Circular Dependency Detection
|
|
20
|
+
**Problem:** Complex service graphs could deadlock or crash the stack if factories recursively requested each other. Static analysis was insufficient for dynamic factories.
|
|
21
|
+
**Fix:**
|
|
22
|
+
- Added a `creating` Set to `PluginLoader`.
|
|
23
|
+
- `createServiceInstance` now tracks which services are currently being built.
|
|
24
|
+
- Throws a descriptive error if a loop is detected (e.g., `Circular dependency detected: serviceA -> serviceB -> serviceA`).
|
|
25
|
+
|
|
26
|
+
## 4. Enhanced Error Handling
|
|
27
|
+
**Problem:** The Kernel swallowed errors from service factories (like database connection failures) and threw a generic "Service not found" error, making debugging impossible.
|
|
28
|
+
**Fix:**
|
|
29
|
+
- Refined `Kernel.getService` to distinguish between "service registration missing" and "factory execution failed".
|
|
30
|
+
- Factory errors are now re-thrown with their original stack trace and message.
|
|
31
|
+
|
|
32
|
+
## 5. Configuration Validation
|
|
33
|
+
**Problem:** Configuration validation was a scaffold without implementation.
|
|
34
|
+
**Fix:**
|
|
35
|
+
- Integrated `PluginConfigValidator` (Zod-based) into `PluginLoader`.
|
|
36
|
+
- `validatePluginConfig` now performs actual schema validation against `plugin.configSchema`.
|
|
37
|
+
|
|
38
|
+
## Verification
|
|
39
|
+
- **Build:** Clean build of `dist` artifacts.
|
|
40
|
+
- **Tests:** 100% Pass rate (380/380 tests) across 22 test suites.
|
|
@@ -5,11 +5,13 @@ import { ApiRegistry } from './api-registry';
|
|
|
5
5
|
describe('API Registry Plugin', () => {
|
|
6
6
|
let kernel;
|
|
7
7
|
beforeEach(() => {
|
|
8
|
-
kernel = new ObjectKernel(
|
|
8
|
+
kernel = new ObjectKernel({
|
|
9
|
+
skipSystemValidation: true
|
|
10
|
+
});
|
|
9
11
|
});
|
|
10
12
|
describe('Plugin Registration', () => {
|
|
11
13
|
it('should register API Registry as a service', async () => {
|
|
12
|
-
kernel.use(createApiRegistryPlugin());
|
|
14
|
+
await kernel.use(createApiRegistryPlugin());
|
|
13
15
|
await kernel.bootstrap();
|
|
14
16
|
const registry = kernel.getService('api-registry');
|
|
15
17
|
expect(registry).toBeDefined();
|
|
@@ -17,7 +19,7 @@ describe('API Registry Plugin', () => {
|
|
|
17
19
|
await kernel.shutdown();
|
|
18
20
|
});
|
|
19
21
|
it('should register with custom conflict resolution', async () => {
|
|
20
|
-
kernel.use(createApiRegistryPlugin({
|
|
22
|
+
await kernel.use(createApiRegistryPlugin({
|
|
21
23
|
conflictResolution: 'priority',
|
|
22
24
|
version: '2.0.0',
|
|
23
25
|
}));
|
|
@@ -31,7 +33,7 @@ describe('API Registry Plugin', () => {
|
|
|
31
33
|
});
|
|
32
34
|
describe('Integration with Plugins', () => {
|
|
33
35
|
it('should allow plugins to register APIs', async () => {
|
|
34
|
-
kernel.use(createApiRegistryPlugin());
|
|
36
|
+
await kernel.use(createApiRegistryPlugin());
|
|
35
37
|
const testPlugin = {
|
|
36
38
|
name: 'test-plugin',
|
|
37
39
|
init: async (ctx) => {
|
|
@@ -60,7 +62,7 @@ describe('API Registry Plugin', () => {
|
|
|
60
62
|
registry.registerApi(api);
|
|
61
63
|
},
|
|
62
64
|
};
|
|
63
|
-
kernel.use(testPlugin);
|
|
65
|
+
await kernel.use(testPlugin);
|
|
64
66
|
await kernel.bootstrap();
|
|
65
67
|
const registry = kernel.getService('api-registry');
|
|
66
68
|
const api = registry.getApi('test_api');
|
|
@@ -70,7 +72,7 @@ describe('API Registry Plugin', () => {
|
|
|
70
72
|
await kernel.shutdown();
|
|
71
73
|
});
|
|
72
74
|
it('should allow multiple plugins to register APIs', async () => {
|
|
73
|
-
kernel.use(createApiRegistryPlugin());
|
|
75
|
+
await kernel.use(createApiRegistryPlugin());
|
|
74
76
|
const plugin1 = {
|
|
75
77
|
name: 'plugin-1',
|
|
76
78
|
init: async (ctx) => {
|
|
@@ -112,8 +114,8 @@ describe('API Registry Plugin', () => {
|
|
|
112
114
|
});
|
|
113
115
|
},
|
|
114
116
|
};
|
|
115
|
-
kernel.use(plugin1);
|
|
116
|
-
kernel.use(plugin2);
|
|
117
|
+
await kernel.use(plugin1);
|
|
118
|
+
await kernel.use(plugin2);
|
|
117
119
|
await kernel.bootstrap();
|
|
118
120
|
const registry = kernel.getService('api-registry');
|
|
119
121
|
const stats = registry.getStats();
|
|
@@ -123,7 +125,7 @@ describe('API Registry Plugin', () => {
|
|
|
123
125
|
await kernel.shutdown();
|
|
124
126
|
});
|
|
125
127
|
it('should support API discovery across plugins', async () => {
|
|
126
|
-
kernel.use(createApiRegistryPlugin());
|
|
128
|
+
await kernel.use(createApiRegistryPlugin());
|
|
127
129
|
const dataPlugin = {
|
|
128
130
|
name: 'data-plugin',
|
|
129
131
|
init: async (ctx) => {
|
|
@@ -172,8 +174,8 @@ describe('API Registry Plugin', () => {
|
|
|
172
174
|
});
|
|
173
175
|
},
|
|
174
176
|
};
|
|
175
|
-
kernel.use(dataPlugin);
|
|
176
|
-
kernel.use(analyticsPlugin);
|
|
177
|
+
await kernel.use(dataPlugin);
|
|
178
|
+
await kernel.use(analyticsPlugin);
|
|
177
179
|
await kernel.bootstrap();
|
|
178
180
|
const registry = kernel.getService('api-registry');
|
|
179
181
|
// Find all data APIs
|
|
@@ -189,7 +191,7 @@ describe('API Registry Plugin', () => {
|
|
|
189
191
|
await kernel.shutdown();
|
|
190
192
|
});
|
|
191
193
|
it('should handle route conflicts based on strategy', async () => {
|
|
192
|
-
kernel.use(createApiRegistryPlugin({
|
|
194
|
+
await kernel.use(createApiRegistryPlugin({
|
|
193
195
|
conflictResolution: 'priority',
|
|
194
196
|
}));
|
|
195
197
|
const corePlugin = {
|
|
@@ -238,8 +240,8 @@ describe('API Registry Plugin', () => {
|
|
|
238
240
|
});
|
|
239
241
|
},
|
|
240
242
|
};
|
|
241
|
-
kernel.use(corePlugin);
|
|
242
|
-
kernel.use(pluginOverride);
|
|
243
|
+
await kernel.use(corePlugin);
|
|
244
|
+
await kernel.use(pluginOverride);
|
|
243
245
|
await kernel.bootstrap();
|
|
244
246
|
const registry = kernel.getService('api-registry');
|
|
245
247
|
const result = registry.findEndpointByRoute('GET', '/api/data/:object');
|
|
@@ -249,7 +251,7 @@ describe('API Registry Plugin', () => {
|
|
|
249
251
|
await kernel.shutdown();
|
|
250
252
|
});
|
|
251
253
|
it('should support cleanup on plugin unload', async () => {
|
|
252
|
-
kernel.use(createApiRegistryPlugin());
|
|
254
|
+
await kernel.use(createApiRegistryPlugin());
|
|
253
255
|
const dynamicPlugin = {
|
|
254
256
|
name: 'dynamic-plugin',
|
|
255
257
|
init: async (ctx) => {
|
|
@@ -275,7 +277,7 @@ describe('API Registry Plugin', () => {
|
|
|
275
277
|
// For now, we'll test the registry's unregister capability
|
|
276
278
|
},
|
|
277
279
|
};
|
|
278
|
-
kernel.use(dynamicPlugin);
|
|
280
|
+
await kernel.use(dynamicPlugin);
|
|
279
281
|
await kernel.bootstrap();
|
|
280
282
|
const registry = kernel.getService('api-registry');
|
|
281
283
|
expect(registry.getApi('dynamic_api')).toBeDefined();
|
|
@@ -287,7 +289,7 @@ describe('API Registry Plugin', () => {
|
|
|
287
289
|
});
|
|
288
290
|
describe('API Registry Lifecycle', () => {
|
|
289
291
|
it('should be available during plugin start phase', async () => {
|
|
290
|
-
kernel.use(createApiRegistryPlugin());
|
|
292
|
+
await kernel.use(createApiRegistryPlugin());
|
|
291
293
|
let registryAvailable = false;
|
|
292
294
|
const testPlugin = {
|
|
293
295
|
name: 'test-plugin',
|
|
@@ -300,13 +302,13 @@ describe('API Registry Plugin', () => {
|
|
|
300
302
|
registryAvailable = registry !== undefined;
|
|
301
303
|
},
|
|
302
304
|
};
|
|
303
|
-
kernel.use(testPlugin);
|
|
305
|
+
await kernel.use(testPlugin);
|
|
304
306
|
await kernel.bootstrap();
|
|
305
307
|
expect(registryAvailable).toBe(true);
|
|
306
308
|
await kernel.shutdown();
|
|
307
309
|
});
|
|
308
310
|
it('should provide consistent registry across all plugins', async () => {
|
|
309
|
-
kernel.use(createApiRegistryPlugin());
|
|
311
|
+
await kernel.use(createApiRegistryPlugin());
|
|
310
312
|
let registry1;
|
|
311
313
|
let registry2;
|
|
312
314
|
const plugin1 = {
|
|
@@ -321,8 +323,8 @@ describe('API Registry Plugin', () => {
|
|
|
321
323
|
registry2 = ctx.getService('api-registry');
|
|
322
324
|
},
|
|
323
325
|
};
|
|
324
|
-
kernel.use(plugin1);
|
|
325
|
-
kernel.use(plugin2);
|
|
326
|
+
await kernel.use(plugin1);
|
|
327
|
+
await kernel.use(plugin2);
|
|
326
328
|
await kernel.bootstrap();
|
|
327
329
|
// Same registry instance should be shared
|
|
328
330
|
expect(registry1).toBe(registry2);
|
|
@@ -580,8 +580,8 @@ describe('ApiRegistry', () => {
|
|
|
580
580
|
endpoints: [],
|
|
581
581
|
});
|
|
582
582
|
const snapshot = registry.getRegistry();
|
|
583
|
-
expect(snapshot.byType?.rest
|
|
584
|
-
expect(snapshot.byType?.graphql
|
|
583
|
+
expect(snapshot.byType?.rest?.length).toBe(2);
|
|
584
|
+
expect(snapshot.byType?.graphql?.length).toBe(1);
|
|
585
585
|
});
|
|
586
586
|
});
|
|
587
587
|
describe('clear', () => {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { SemanticVersion, VersionConstraint, CompatibilityLevel, DependencyConflict } from '@objectstack/spec/kernel';
|
|
2
|
+
import type { ObjectLogger } from './logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Semantic Version Parser and Comparator
|
|
5
|
+
*
|
|
6
|
+
* Implements semantic versioning comparison and constraint matching
|
|
7
|
+
*/
|
|
8
|
+
export declare class SemanticVersionManager {
|
|
9
|
+
/**
|
|
10
|
+
* Parse a version string into semantic version components
|
|
11
|
+
*/
|
|
12
|
+
static parse(versionStr: string): SemanticVersion;
|
|
13
|
+
/**
|
|
14
|
+
* Convert semantic version back to string
|
|
15
|
+
*/
|
|
16
|
+
static toString(version: SemanticVersion): string;
|
|
17
|
+
/**
|
|
18
|
+
* Compare two semantic versions
|
|
19
|
+
* Returns: -1 if a < b, 0 if a === b, 1 if a > b
|
|
20
|
+
*/
|
|
21
|
+
static compare(a: SemanticVersion, b: SemanticVersion): number;
|
|
22
|
+
/**
|
|
23
|
+
* Check if version satisfies constraint
|
|
24
|
+
*/
|
|
25
|
+
static satisfies(version: SemanticVersion, constraint: VersionConstraint): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Determine compatibility level between two versions
|
|
28
|
+
*/
|
|
29
|
+
static getCompatibilityLevel(from: SemanticVersion, to: SemanticVersion): CompatibilityLevel;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Plugin Dependency Resolver
|
|
33
|
+
*
|
|
34
|
+
* Resolves plugin dependencies using topological sorting and conflict detection
|
|
35
|
+
*/
|
|
36
|
+
export declare class DependencyResolver {
|
|
37
|
+
private logger;
|
|
38
|
+
constructor(logger: ObjectLogger);
|
|
39
|
+
/**
|
|
40
|
+
* Resolve dependencies using topological sort
|
|
41
|
+
*/
|
|
42
|
+
resolve(plugins: Map<string, {
|
|
43
|
+
version?: string;
|
|
44
|
+
dependencies?: string[];
|
|
45
|
+
}>): string[];
|
|
46
|
+
/**
|
|
47
|
+
* Detect dependency conflicts
|
|
48
|
+
*/
|
|
49
|
+
detectConflicts(plugins: Map<string, {
|
|
50
|
+
version: string;
|
|
51
|
+
dependencies?: Record<string, VersionConstraint>;
|
|
52
|
+
}>): DependencyConflict[];
|
|
53
|
+
/**
|
|
54
|
+
* Find best version that satisfies all constraints
|
|
55
|
+
*/
|
|
56
|
+
findBestVersion(availableVersions: string[], constraints: VersionConstraint[]): string | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Check if dependencies form a valid DAG (no cycles)
|
|
59
|
+
*/
|
|
60
|
+
isAcyclic(dependencies: Map<string, string[]>): boolean;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=dependency-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-resolver.d.ts","sourceRoot":"","sources":["../src/dependency-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;GAIG;AACH,qBAAa,sBAAsB;IACjC;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe;IAsBjD;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM;IAWjD;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,eAAe,GAAG,MAAM;IAkB9D;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,iBAAiB,GAAG,OAAO;IAoElF;;OAEG;IACH,MAAM,CAAC,qBAAqB,CAAC,IAAI,EAAE,eAAe,EAAE,EAAE,EAAE,eAAe,GAAG,kBAAkB;CA0B7F;AAED;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAe;gBAEjB,MAAM,EAAE,YAAY;IAIhC;;OAEG;IACH,OAAO,CACL,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,GAClE,MAAM,EAAE;IAkEX;;OAEG;IACH,eAAe,CACb,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;KAAE,CAAC,GAC1F,kBAAkB,EAAE;IA+EvB;;OAEG;IACH,eAAe,CACb,iBAAiB,EAAE,MAAM,EAAE,EAC3B,WAAW,EAAE,iBAAiB,EAAE,GAC/B,MAAM,GAAG,SAAS;IAoBrB;;OAEG;IACH,SAAS,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO;CAcxD"}
|