@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/ENHANCED_FEATURES.md +380 -0
  3. package/README.md +299 -12
  4. package/dist/contracts/data-engine.d.ts +39 -22
  5. package/dist/contracts/data-engine.d.ts.map +1 -1
  6. package/dist/contracts/logger.d.ts +63 -0
  7. package/dist/contracts/logger.d.ts.map +1 -0
  8. package/dist/contracts/logger.js +1 -0
  9. package/dist/enhanced-kernel.d.ts +103 -0
  10. package/dist/enhanced-kernel.d.ts.map +1 -0
  11. package/dist/enhanced-kernel.js +403 -0
  12. package/dist/enhanced-kernel.test.d.ts +2 -0
  13. package/dist/enhanced-kernel.test.d.ts.map +1 -0
  14. package/dist/enhanced-kernel.test.js +412 -0
  15. package/dist/index.d.ts +11 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +10 -2
  18. package/dist/kernel-base.d.ts +84 -0
  19. package/dist/kernel-base.d.ts.map +1 -0
  20. package/dist/kernel-base.js +219 -0
  21. package/dist/kernel.d.ts +11 -18
  22. package/dist/kernel.d.ts.map +1 -1
  23. package/dist/kernel.js +43 -114
  24. package/dist/kernel.test.d.ts +2 -0
  25. package/dist/kernel.test.d.ts.map +1 -0
  26. package/dist/kernel.test.js +161 -0
  27. package/dist/logger.d.ts +70 -0
  28. package/dist/logger.d.ts.map +1 -0
  29. package/dist/logger.js +268 -0
  30. package/dist/logger.test.d.ts +2 -0
  31. package/dist/logger.test.d.ts.map +1 -0
  32. package/dist/logger.test.js +92 -0
  33. package/dist/plugin-loader.d.ts +148 -0
  34. package/dist/plugin-loader.d.ts.map +1 -0
  35. package/dist/plugin-loader.js +287 -0
  36. package/dist/plugin-loader.test.d.ts +2 -0
  37. package/dist/plugin-loader.test.d.ts.map +1 -0
  38. package/dist/plugin-loader.test.js +339 -0
  39. package/dist/types.d.ts +2 -1
  40. package/dist/types.d.ts.map +1 -1
  41. package/examples/enhanced-kernel-example.ts +309 -0
  42. package/package.json +19 -4
  43. package/src/contracts/data-engine.ts +46 -24
  44. package/src/contracts/logger.ts +70 -0
  45. package/src/enhanced-kernel.test.ts +535 -0
  46. package/src/enhanced-kernel.ts +496 -0
  47. package/src/index.ts +23 -2
  48. package/src/kernel-base.ts +256 -0
  49. package/src/kernel.test.ts +200 -0
  50. package/src/kernel.ts +55 -129
  51. package/src/logger.test.ts +116 -0
  52. package/src/logger.ts +306 -0
  53. package/src/plugin-loader.test.ts +412 -0
  54. package/src/plugin-loader.ts +435 -0
  55. package/src/types.ts +2 -1
  56. package/vitest.config.ts +8 -0
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Enhanced ObjectKernel Example
3
+ *
4
+ * Demonstrates advanced plugin features:
5
+ * - Version compatibility
6
+ * - Service factories with lifecycle management
7
+ * - Plugin timeout control
8
+ * - Startup failure rollback
9
+ * - Health checks
10
+ * - Performance metrics
11
+ * - Graceful shutdown
12
+ */
13
+
14
+ import {
15
+ EnhancedObjectKernel,
16
+ PluginMetadata,
17
+ ServiceLifecycle,
18
+ PluginContext
19
+ } from '../index.js';
20
+
21
+ // ============================================================================
22
+ // Example 1: Database Plugin with Health Checks
23
+ // ============================================================================
24
+
25
+ const databasePlugin: PluginMetadata = {
26
+ name: 'database',
27
+ version: '1.0.0',
28
+ startupTimeout: 10000, // 10 second timeout
29
+
30
+ async init(ctx: PluginContext) {
31
+ ctx.logger.info('Initializing database plugin');
32
+
33
+ // Register database service using factory
34
+ // This creates a singleton that's initialized once
35
+ const db = {
36
+ connected: false,
37
+ async connect() {
38
+ ctx.logger.info('Connecting to database...');
39
+ await new Promise(resolve => setTimeout(resolve, 100)); // Simulate connection
40
+ this.connected = true;
41
+ ctx.logger.info('Database connected');
42
+ },
43
+ async disconnect() {
44
+ ctx.logger.info('Disconnecting from database...');
45
+ this.connected = false;
46
+ },
47
+ async query(sql: string) {
48
+ if (!this.connected) {
49
+ throw new Error('Database not connected');
50
+ }
51
+ return { rows: [] };
52
+ }
53
+ };
54
+
55
+ ctx.registerService('db', db);
56
+ },
57
+
58
+ async start(ctx: PluginContext) {
59
+ const db = ctx.getService<any>('db');
60
+ await db.connect();
61
+ },
62
+
63
+ async destroy() {
64
+ // Cleanup on shutdown
65
+ console.log('Cleaning up database connection');
66
+ },
67
+
68
+ async healthCheck() {
69
+ // Health check returns status
70
+ return {
71
+ healthy: true,
72
+ message: 'Database is operational',
73
+ details: {
74
+ connections: 5,
75
+ responseTime: 45
76
+ }
77
+ };
78
+ }
79
+ };
80
+
81
+ // ============================================================================
82
+ // Example 2: API Plugin with Dependencies
83
+ // ============================================================================
84
+
85
+ const apiPlugin: PluginMetadata = {
86
+ name: 'api',
87
+ version: '2.1.0',
88
+ dependencies: ['database'], // Requires database plugin to be loaded first
89
+
90
+ async init(ctx: PluginContext) {
91
+ ctx.logger.info('Initializing API plugin');
92
+
93
+ // Access database service (guaranteed to exist due to dependencies)
94
+ const db = ctx.getService<any>('db');
95
+
96
+ const api = {
97
+ async getUsers() {
98
+ return await db.query('SELECT * FROM users');
99
+ },
100
+ async createUser(data: any) {
101
+ return await db.query('INSERT INTO users VALUES ...', data);
102
+ }
103
+ };
104
+
105
+ ctx.registerService('api', api);
106
+ },
107
+
108
+ async healthCheck() {
109
+ return {
110
+ healthy: true,
111
+ message: 'API is ready',
112
+ details: {
113
+ routes: 15,
114
+ activeRequests: 3
115
+ }
116
+ };
117
+ }
118
+ };
119
+
120
+ // ============================================================================
121
+ // Example 3: Cache Plugin with Scoped Services
122
+ // ============================================================================
123
+
124
+ const cachePlugin: PluginMetadata = {
125
+ name: 'cache',
126
+ version: '1.2.3',
127
+
128
+ async init(ctx: PluginContext) {
129
+ ctx.logger.info('Initializing cache plugin');
130
+
131
+ // Simple in-memory cache
132
+ const cache = new Map<string, any>();
133
+
134
+ ctx.registerService('cache', {
135
+ get(key: string) {
136
+ return cache.get(key);
137
+ },
138
+ set(key: string, value: any) {
139
+ cache.set(key, value);
140
+ },
141
+ clear() {
142
+ cache.clear();
143
+ }
144
+ });
145
+ },
146
+
147
+ async healthCheck() {
148
+ return {
149
+ healthy: true,
150
+ message: 'Cache is operational'
151
+ };
152
+ }
153
+ };
154
+
155
+ // ============================================================================
156
+ // Example 4: Using Service Factories
157
+ // ============================================================================
158
+
159
+ async function setupServiceFactories(kernel: EnhancedObjectKernel) {
160
+ // Singleton: Created once, shared across all requests
161
+ kernel.registerServiceFactory(
162
+ 'logger-service',
163
+ (ctx) => {
164
+ return {
165
+ log: (message: string) => {
166
+ ctx.logger.info(message);
167
+ }
168
+ };
169
+ },
170
+ ServiceLifecycle.SINGLETON
171
+ );
172
+
173
+ // Transient: New instance on every request
174
+ kernel.registerServiceFactory(
175
+ 'request-id',
176
+ () => {
177
+ return `req-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
178
+ },
179
+ ServiceLifecycle.TRANSIENT
180
+ );
181
+
182
+ // Scoped: One instance per scope (e.g., per HTTP request)
183
+ kernel.registerServiceFactory(
184
+ 'user-session',
185
+ () => {
186
+ return {
187
+ id: Math.random().toString(36),
188
+ data: new Map<string, any>()
189
+ };
190
+ },
191
+ ServiceLifecycle.SCOPED
192
+ );
193
+ }
194
+
195
+ // ============================================================================
196
+ // Main Application
197
+ // ============================================================================
198
+
199
+ async function main() {
200
+ console.log('🚀 Starting Enhanced ObjectKernel Example\n');
201
+
202
+ // Create enhanced kernel with configuration
203
+ const kernel = new EnhancedObjectKernel({
204
+ logger: {
205
+ level: 'info',
206
+ format: 'pretty'
207
+ },
208
+ defaultStartupTimeout: 30000, // 30 seconds
209
+ gracefulShutdown: true,
210
+ shutdownTimeout: 60000, // 60 seconds
211
+ rollbackOnFailure: true, // Rollback on failure
212
+ });
213
+
214
+ // Setup service factories
215
+ await setupServiceFactories(kernel);
216
+
217
+ // Register plugins
218
+ await kernel.use(databasePlugin);
219
+ await kernel.use(cachePlugin);
220
+ await kernel.use(apiPlugin);
221
+
222
+ console.log('📦 Plugins registered\n');
223
+
224
+ // Bootstrap kernel
225
+ console.log('⚡ Bootstrapping kernel...\n');
226
+ await kernel.bootstrap();
227
+
228
+ console.log('\n✅ Kernel started successfully!\n');
229
+
230
+ // Show plugin metrics
231
+ console.log('📊 Plugin Startup Metrics:');
232
+ const metrics = kernel.getPluginMetrics();
233
+ for (const [name, time] of metrics) {
234
+ console.log(` ${name}: ${time}ms`);
235
+ }
236
+ console.log('');
237
+
238
+ // Check plugin health
239
+ console.log('🏥 Plugin Health Status:');
240
+ const healthStatuses = await kernel.checkAllPluginsHealth();
241
+ for (const [name, health] of healthStatuses) {
242
+ const status = health.healthy ? '✅' : '❌';
243
+ console.log(` ${status} ${name}: ${health.message}`);
244
+ if (health.details) {
245
+ console.log(` Details:`, health.details);
246
+ }
247
+ }
248
+ console.log('');
249
+
250
+ // Use services
251
+ console.log('🔧 Using services:');
252
+ const db = kernel.getService<any>('db');
253
+ console.log(' Database connected:', db.connected);
254
+
255
+ const cache = kernel.getService<any>('cache');
256
+ cache.set('user:1', { name: 'John Doe' });
257
+ console.log(' Cached user:', cache.get('user:1'));
258
+
259
+ const api = kernel.getService<any>('api');
260
+ console.log(' API ready:', !!api);
261
+ console.log('');
262
+
263
+ // Test service factories
264
+ console.log('🏭 Testing Service Factories:');
265
+
266
+ // Singleton - same instance
267
+ const logger1 = await kernel.getServiceAsync('logger-service');
268
+ const logger2 = await kernel.getServiceAsync('logger-service');
269
+ console.log(' Singleton test:', logger1 === logger2 ? 'PASS ✅' : 'FAIL ❌');
270
+
271
+ // Transient - different instances
272
+ const id1 = await kernel.getServiceAsync('request-id');
273
+ const id2 = await kernel.getServiceAsync('request-id');
274
+ console.log(' Transient test:', id1 !== id2 ? 'PASS ✅' : 'FAIL ❌');
275
+ console.log(' Generated IDs:', id1, 'and', id2);
276
+
277
+ // Scoped - same within scope, different across scopes
278
+ const session1a = await kernel.getServiceAsync('user-session', 'request-1');
279
+ const session1b = await kernel.getServiceAsync('user-session', 'request-1');
280
+ const session2 = await kernel.getServiceAsync('user-session', 'request-2');
281
+ console.log(' Scoped (same scope):', session1a === session1b ? 'PASS ✅' : 'FAIL ❌');
282
+ console.log(' Scoped (diff scope):', session1a !== session2 ? 'PASS ✅' : 'FAIL ❌');
283
+ console.log('');
284
+
285
+ // Register custom shutdown handler
286
+ kernel.onShutdown(async () => {
287
+ console.log('🧹 Running custom cleanup...');
288
+ });
289
+
290
+ // Simulate running for a bit
291
+ console.log('⏳ Running for 2 seconds...\n');
292
+ await new Promise(resolve => setTimeout(resolve, 2000));
293
+
294
+ // Graceful shutdown
295
+ console.log('🛑 Initiating graceful shutdown...\n');
296
+ await kernel.shutdown();
297
+
298
+ console.log('\n✅ Shutdown complete!\n');
299
+ }
300
+
301
+ // Run the example
302
+ if (import.meta.url === `file://${process.argv[1]}`) {
303
+ main().catch(error => {
304
+ console.error('❌ Error:', error);
305
+ process.exit(1);
306
+ });
307
+ }
308
+
309
+ export { main };
package/package.json CHANGED
@@ -1,17 +1,32 @@
1
1
  {
2
2
  "name": "@objectstack/core",
3
- "version": "0.6.1",
3
+ "version": "0.7.2",
4
4
  "description": "Microkernel Core for ObjectStack",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "devDependencies": {
9
- "typescript": "^5.0.0"
9
+ "typescript": "^5.0.0",
10
+ "vitest": "^1.0.0",
11
+ "@types/node": "^20.0.0"
10
12
  },
11
13
  "dependencies": {
12
- "@objectstack/spec": "0.6.1"
14
+ "pino": "^8.17.0",
15
+ "pino-pretty": "^10.3.0",
16
+ "zod": "^3.22.0",
17
+ "@objectstack/spec": "0.7.2"
18
+ },
19
+ "peerDependencies": {
20
+ "pino": "^8.0.0"
21
+ },
22
+ "peerDependenciesMeta": {
23
+ "pino": {
24
+ "optional": true
25
+ }
13
26
  },
14
27
  "scripts": {
15
- "build": "tsc"
28
+ "build": "tsc",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest"
16
31
  }
17
32
  }
@@ -1,5 +1,14 @@
1
- import { QueryAST } from '@objectstack/spec/data';
2
- import { DriverOptions } from '@objectstack/spec/system';
1
+ import {
2
+ DataEngineQueryOptions,
3
+ DataEngineInsertOptions,
4
+ DataEngineUpdateOptions,
5
+ DataEngineDeleteOptions,
6
+ DataEngineAggregateOptions,
7
+ DataEngineCountOptions,
8
+ DataEngineRequest, // Added Request type for batch
9
+ QueryAST,
10
+ DriverOptions
11
+ } from '@objectstack/spec/data';
3
12
 
4
13
  /**
5
14
  * IDataEngine - Standard Data Engine Interface
@@ -7,32 +16,33 @@ import { DriverOptions } from '@objectstack/spec/system';
7
16
  * Abstract interface for data persistence capabilities.
8
17
  * Following the Dependency Inversion Principle - plugins depend on this interface,
9
18
  * not on concrete database implementations.
19
+ *
20
+ * Aligned with 'src/data/data-engine.zod.ts' in @objectstack/spec.
10
21
  */
11
22
 
12
- export interface DataEngineFilter {
13
- [key: string]: any;
14
- }
15
-
16
- export interface DataEngineQueryOptions {
17
- /** Filter conditions */
18
- filter?: DataEngineFilter;
19
- /** Fields to select */
20
- select?: string[];
21
- /** Sort order */
22
- sort?: Record<string, 1 | -1 | 'asc' | 'desc'>;
23
- /** Limit number of results */
24
- limit?: number;
25
- /** Skip number of results */
26
- skip?: number;
27
- /** Maximum number of results */
28
- top?: number;
29
- }
30
-
31
23
  export interface IDataEngine {
32
- insert(objectName: string, data: any): Promise<any>;
33
24
  find(objectName: string, query?: DataEngineQueryOptions): Promise<any[]>;
34
- update(objectName: string, id: any, data: any): Promise<any>;
35
- delete(objectName: string, id: any): Promise<boolean>;
25
+ findOne(objectName: string, query?: DataEngineQueryOptions): Promise<any>;
26
+ insert(objectName: string, data: any | any[], options?: DataEngineInsertOptions): Promise<any>;
27
+ update(objectName: string, data: any, options?: DataEngineUpdateOptions): Promise<any>;
28
+ delete(objectName: string, options?: DataEngineDeleteOptions): Promise<any>;
29
+ count(objectName: string, query?: DataEngineCountOptions): Promise<number>;
30
+ aggregate(objectName: string, query: DataEngineAggregateOptions): Promise<any[]>;
31
+
32
+ /**
33
+ * Vector Search (AI/RAG)
34
+ */
35
+ vectorFind?(objectName: string, vector: number[], options?: { filter?: any, limit?: number, select?: string[], threshold?: number }): Promise<any[]>;
36
+
37
+ /**
38
+ * Batch Operations (Transactional)
39
+ */
40
+ batch?(requests: DataEngineRequest[], options?: { transaction?: boolean }): Promise<any[]>;
41
+
42
+ /**
43
+ * Execute raw command (Escape hatch)
44
+ */
45
+ execute?(command: any, options?: Record<string, any>): Promise<any>;
36
46
  }
37
47
 
38
48
  export interface DriverInterface {
@@ -47,6 +57,18 @@ export interface DriverInterface {
47
57
  update(object: string, id: any, data: any, options?: DriverOptions): Promise<any>;
48
58
  delete(object: string, id: any, options?: DriverOptions): Promise<any>;
49
59
 
60
+ /**
61
+ * Bulk & Batch Operations
62
+ */
63
+ bulkCreate?(object: string, data: any[], options?: DriverOptions): Promise<any>;
64
+ updateMany?(object: string, query: QueryAST, data: any, options?: DriverOptions): Promise<any>;
65
+ deleteMany?(object: string, query: QueryAST, options?: DriverOptions): Promise<any>;
66
+
50
67
  count?(object: string, query: QueryAST, options?: DriverOptions): Promise<number>;
68
+
69
+ /**
70
+ * Raw Execution
71
+ */
72
+ execute?(command: any, params?: any, options?: DriverOptions): Promise<any>;
51
73
  }
52
74
 
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Logger Contract
3
+ *
4
+ * Defines the interface for logging in ObjectStack.
5
+ * Compatible with both browser console and structured logging systems.
6
+ */
7
+ export interface Logger {
8
+ /**
9
+ * Log a debug message
10
+ * @param message - The message to log
11
+ * @param meta - Optional metadata to include
12
+ */
13
+ debug(message: string, meta?: Record<string, any>): void;
14
+
15
+ /**
16
+ * Log an informational message
17
+ * @param message - The message to log
18
+ * @param meta - Optional metadata to include
19
+ */
20
+ info(message: string, meta?: Record<string, any>): void;
21
+
22
+ /**
23
+ * Log a warning message
24
+ * @param message - The message to log
25
+ * @param meta - Optional metadata to include
26
+ */
27
+ warn(message: string, meta?: Record<string, any>): void;
28
+
29
+ /**
30
+ * Log an error message
31
+ * @param message - The message to log
32
+ * @param error - Optional error object
33
+ * @param meta - Optional metadata to include
34
+ */
35
+ error(message: string, error?: Error, meta?: Record<string, any>): void;
36
+
37
+ /**
38
+ * Log a fatal error message
39
+ * @param message - The message to log
40
+ * @param error - Optional error object
41
+ * @param meta - Optional metadata to include
42
+ */
43
+ fatal?(message: string, error?: Error, meta?: Record<string, any>): void;
44
+
45
+ /**
46
+ * Create a child logger with additional context
47
+ * @param context - Context to add to all logs from this child
48
+ */
49
+ child?(context: Record<string, any>): Logger;
50
+
51
+ /**
52
+ * Set trace context for distributed tracing
53
+ * @param traceId - Trace identifier
54
+ * @param spanId - Span identifier
55
+ */
56
+ withTrace?(traceId: string, spanId?: string): Logger;
57
+
58
+ /**
59
+ * Compatibility method for console.log usage
60
+ * @param message - The message to log
61
+ * @param args - Additional arguments
62
+ */
63
+ log?(message: string, ...args: any[]): void;
64
+
65
+ /**
66
+ * Cleanup resources (close file streams, etc.)
67
+ * Should be called when the logger is no longer needed
68
+ */
69
+ destroy?(): Promise<void>;
70
+ }