Skip to content

Commit 582dc0e

Browse files
authored
Merge pull request #406 from objectstack-ai/copilot/enhance-plugin-loader-features
2 parents 3c910c1 + 5b7e854 commit 582dc0e

File tree

11 files changed

+2668
-5
lines changed

11 files changed

+2668
-5
lines changed

packages/core/ENHANCED_FEATURES.md

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
# Enhanced ObjectKernel - Advanced Plugin Features
2+
3+
This document describes the enhanced features added to ObjectKernel for production-grade plugin management.
4+
5+
## Overview
6+
7+
The `EnhancedObjectKernel` extends the basic `ObjectKernel` with enterprise-grade features for plugin lifecycle management, dependency injection, and operational resilience.
8+
9+
## Features
10+
11+
### 1. Enhanced Plugin Loading
12+
13+
Async plugin loading with comprehensive validation:
14+
15+
```typescript
16+
import { EnhancedObjectKernel, PluginMetadata } from '@objectstack/core';
17+
18+
const kernel = new EnhancedObjectKernel({
19+
logger: { level: 'info' },
20+
defaultStartupTimeout: 30000, // 30 seconds
21+
gracefulShutdown: true,
22+
shutdownTimeout: 60000, // 60 seconds
23+
rollbackOnFailure: true, // Rollback on startup failure
24+
});
25+
26+
// Plugin with version and timeout
27+
const myPlugin: PluginMetadata = {
28+
name: 'my-plugin',
29+
version: '1.2.3', // Semantic version required
30+
startupTimeout: 10000, // Override default timeout
31+
32+
async init(ctx) {
33+
// Register services
34+
ctx.registerService('my-service', serviceInstance);
35+
},
36+
37+
async start(ctx) {
38+
// Start business logic
39+
},
40+
41+
async destroy() {
42+
// Cleanup resources
43+
},
44+
45+
// Optional: Health check
46+
async healthCheck() {
47+
return {
48+
healthy: true,
49+
message: 'Service is running',
50+
details: { connections: 10 }
51+
};
52+
}
53+
};
54+
55+
await kernel.use(myPlugin);
56+
await kernel.bootstrap();
57+
```
58+
59+
### 2. Advanced Dependency Injection
60+
61+
Factory-based service registration with lifecycle management:
62+
63+
```typescript
64+
import { ServiceLifecycle } from '@objectstack/core';
65+
66+
// Singleton: Created once, shared across all requests
67+
kernel.registerServiceFactory(
68+
'database',
69+
async (ctx) => {
70+
const db = await connectToDatabase();
71+
return db;
72+
},
73+
ServiceLifecycle.SINGLETON
74+
);
75+
76+
// Transient: New instance on every request
77+
kernel.registerServiceFactory(
78+
'request-id',
79+
() => generateUUID(),
80+
ServiceLifecycle.TRANSIENT
81+
);
82+
83+
// Scoped: One instance per scope (e.g., per HTTP request)
84+
kernel.registerServiceFactory(
85+
'user-session',
86+
async (ctx) => {
87+
return new UserSession();
88+
},
89+
ServiceLifecycle.SCOPED
90+
);
91+
92+
// Get service (async)
93+
const db = await kernel.getServiceAsync('database');
94+
95+
// Get scoped service
96+
const session = await kernel.getServiceAsync('user-session', 'request-123');
97+
```
98+
99+
### 3. Service Dependencies
100+
101+
Declare service dependencies for proper initialization order:
102+
103+
```typescript
104+
kernel.registerServiceFactory(
105+
'api-client',
106+
async (ctx) => {
107+
const auth = await ctx.getService('auth-service');
108+
return new ApiClient(auth);
109+
},
110+
ServiceLifecycle.SINGLETON,
111+
['auth-service'] // Dependencies
112+
);
113+
114+
// Detect circular dependencies
115+
const cycles = kernel['pluginLoader'].detectCircularDependencies();
116+
if (cycles.length > 0) {
117+
console.error('Circular dependencies detected:', cycles);
118+
}
119+
```
120+
121+
### 4. Plugin Timeout Control
122+
123+
Prevent plugins from hanging during startup:
124+
125+
```typescript
126+
const plugin: PluginMetadata = {
127+
name: 'slow-plugin',
128+
version: '1.0.0',
129+
startupTimeout: 5000, // 5 second timeout
130+
131+
async init(ctx) {
132+
// If this takes longer than 5s, it will timeout
133+
await slowInitialization();
134+
}
135+
};
136+
137+
await kernel.use(plugin);
138+
139+
try {
140+
await kernel.bootstrap();
141+
} catch (error) {
142+
// Error: Plugin slow-plugin init timeout after 5000ms
143+
}
144+
```
145+
146+
### 5. Startup Failure Rollback
147+
148+
Automatically rollback started plugins if any plugin fails:
149+
150+
```typescript
151+
const plugin1: Plugin = {
152+
name: 'plugin-1',
153+
version: '1.0.0',
154+
async init() {},
155+
async start() {
156+
// Starts successfully
157+
},
158+
async destroy() {
159+
console.log('Rolling back plugin-1');
160+
}
161+
};
162+
163+
const plugin2: Plugin = {
164+
name: 'plugin-2',
165+
version: '1.0.0',
166+
async init() {},
167+
async start() {
168+
throw new Error('Startup failed!');
169+
}
170+
};
171+
172+
await kernel.use(plugin1);
173+
await kernel.use(plugin2);
174+
175+
try {
176+
await kernel.bootstrap();
177+
} catch (error) {
178+
// plugin-1 will be automatically destroyed (rolled back)
179+
// Error: Plugin plugin-2 failed to start - rollback complete
180+
}
181+
```
182+
183+
### 6. Plugin Health Checks
184+
185+
Monitor plugin health at runtime:
186+
187+
```typescript
188+
const plugin: PluginMetadata = {
189+
name: 'database-plugin',
190+
version: '1.0.0',
191+
192+
async init(ctx) {
193+
// Initialize database connection
194+
},
195+
196+
async healthCheck() {
197+
const isConnected = await checkDatabaseConnection();
198+
return {
199+
healthy: isConnected,
200+
message: isConnected ? 'Connected' : 'Disconnected',
201+
details: {
202+
connections: 10,
203+
responseTime: 50
204+
}
205+
};
206+
}
207+
};
208+
209+
await kernel.use(plugin);
210+
await kernel.bootstrap();
211+
212+
// Check individual plugin health
213+
const health = await kernel.checkPluginHealth('database-plugin');
214+
console.log(health);
215+
// { healthy: true, message: 'Connected', details: {...}, lastCheck: Date }
216+
217+
// Check all plugins health
218+
const allHealth = await kernel.checkAllPluginsHealth();
219+
for (const [pluginName, health] of allHealth) {
220+
console.log(`${pluginName}: ${health.healthy ? '' : ''}`);
221+
}
222+
```
223+
224+
### 7. Performance Metrics
225+
226+
Track plugin startup times:
227+
228+
```typescript
229+
await kernel.bootstrap();
230+
231+
const metrics = kernel.getPluginMetrics();
232+
for (const [pluginName, startTime] of metrics) {
233+
console.log(`${pluginName}: ${startTime}ms`);
234+
}
235+
// plugin-1: 150ms
236+
// plugin-2: 320ms
237+
// plugin-3: 45ms
238+
```
239+
240+
### 8. Graceful Shutdown
241+
242+
Properly cleanup resources on shutdown:
243+
244+
```typescript
245+
const kernel = new EnhancedObjectKernel({
246+
gracefulShutdown: true,
247+
shutdownTimeout: 60000 // 60 second timeout
248+
});
249+
250+
// Register custom shutdown handler
251+
kernel.onShutdown(async () => {
252+
console.log('Closing database connections...');
253+
await db.close();
254+
});
255+
256+
// Graceful shutdown
257+
process.on('SIGTERM', async () => {
258+
await kernel.shutdown();
259+
process.exit(0);
260+
});
261+
262+
// Manual shutdown
263+
await kernel.shutdown();
264+
// Triggers:
265+
// 1. kernel:shutdown hook
266+
// 2. Plugin destroy() in reverse order
267+
// 3. Custom shutdown handlers
268+
// 4. Logger cleanup
269+
```
270+
271+
### 9. Version Compatibility
272+
273+
Plugins must use semantic versioning:
274+
275+
```typescript
276+
// Valid versions
277+
'1.0.0'
278+
'2.3.4'
279+
'1.0.0-alpha.1'
280+
'1.0.0+20230101'
281+
282+
// Invalid versions (will be rejected)
283+
'1.0'
284+
'v1.0.0'
285+
'latest'
286+
```
287+
288+
### 10. Plugin Configuration Validation
289+
290+
Use Zod schemas to validate plugin configuration:
291+
292+
```typescript
293+
import { z } from 'zod';
294+
295+
const MyPluginConfigSchema = z.object({
296+
apiKey: z.string(),
297+
timeout: z.number().min(1000).max(30000),
298+
retries: z.number().int().min(0).default(3)
299+
});
300+
301+
const plugin: PluginMetadata = {
302+
name: 'my-plugin',
303+
version: '1.0.0',
304+
configSchema: MyPluginConfigSchema,
305+
306+
async init(ctx) {
307+
// Config is validated before init is called
308+
}
309+
};
310+
```
311+
312+
## Migration from ObjectKernel
313+
314+
To migrate from `ObjectKernel` to `EnhancedObjectKernel`:
315+
316+
```typescript
317+
// Before
318+
import { ObjectKernel } from '@objectstack/core';
319+
const kernel = new ObjectKernel();
320+
321+
// After
322+
import { EnhancedObjectKernel } from '@objectstack/core';
323+
const kernel = new EnhancedObjectKernel({
324+
logger: { level: 'info' },
325+
gracefulShutdown: true,
326+
rollbackOnFailure: true
327+
});
328+
```
329+
330+
Both kernels are compatible - `EnhancedObjectKernel` is a superset of `ObjectKernel`.
331+
332+
## Best Practices
333+
334+
1. **Always set timeouts**: Configure `startupTimeout` to prevent hanging plugins
335+
2. **Implement health checks**: Monitor plugin health at runtime
336+
3. **Use semantic versioning**: Ensures compatibility and proper dependency resolution
337+
4. **Enable rollback**: Set `rollbackOnFailure: true` to prevent partial startup states
338+
5. **Handle shutdown**: Implement `destroy()` to cleanup resources properly
339+
6. **Monitor metrics**: Track startup times to identify slow plugins
340+
7. **Use service factories**: Prefer factories over static instances for better control
341+
8. **Declare dependencies**: Use the dependencies array for proper initialization order
342+
343+
## API Reference
344+
345+
### EnhancedObjectKernel
346+
347+
- `constructor(config: EnhancedKernelConfig)`
348+
- `async use(plugin: Plugin): Promise<this>`
349+
- `registerServiceFactory<T>(name, factory, lifecycle, dependencies?): this`
350+
- `async bootstrap(): Promise<void>`
351+
- `async shutdown(): Promise<void>`
352+
- `async checkPluginHealth(pluginName: string): Promise<PluginHealthStatus>`
353+
- `async checkAllPluginsHealth(): Promise<Map<string, PluginHealthStatus>>`
354+
- `getPluginMetrics(): Map<string, number>`
355+
- `async getServiceAsync<T>(name: string, scopeId?: string): Promise<T>`
356+
- `onShutdown(handler: () => Promise<void>): void`
357+
- `getState(): string`
358+
- `isRunning(): boolean`
359+
360+
### ServiceLifecycle
361+
362+
- `SINGLETON`: Single instance shared across all requests
363+
- `TRANSIENT`: New instance created for each request
364+
- `SCOPED`: New instance per scope (e.g., per HTTP request)
365+
366+
### PluginMetadata
367+
368+
Extended `Plugin` interface with:
369+
- `version: string` - Semantic version
370+
- `configSchema?: z.ZodSchema` - Configuration schema
371+
- `signature?: string` - Plugin signature for verification
372+
- `healthCheck?(): Promise<PluginHealthStatus>` - Health check function
373+
- `startupTimeout?: number` - Startup timeout in milliseconds
374+
- `hotReloadable?: boolean` - Whether plugin supports hot reload
375+
376+
## Examples
377+
378+
See the test files for comprehensive examples:
379+
- `packages/core/src/enhanced-kernel.test.ts`
380+
- `packages/core/src/plugin-loader.test.ts`

0 commit comments

Comments
 (0)