@objectstack/runtime 0.9.0 → 0.9.1
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 +10 -0
- package/README.md +296 -0
- package/dist/index.d.ts +1 -0
- package/package.json +4 -4
- package/src/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @objectstack/runtime
|
|
2
2
|
|
|
3
|
+
## 0.9.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Patch release for maintenance and stability improvements. All packages updated with unified versioning.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @objectstack/spec@0.9.1
|
|
10
|
+
- @objectstack/core@0.9.1
|
|
11
|
+
- @objectstack/types@0.9.1
|
|
12
|
+
|
|
3
13
|
## 0.8.2
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -268,6 +268,302 @@ See the `examples/` directory for complete examples:
|
|
|
268
268
|
4. **Use hooks**: Decouple plugins with event system
|
|
269
269
|
5. **Handle errors**: Implement proper error handling in lifecycle methods
|
|
270
270
|
|
|
271
|
+
## Common Plugin Patterns
|
|
272
|
+
|
|
273
|
+
### Service Provider Pattern
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
277
|
+
|
|
278
|
+
export class DatabasePlugin implements Plugin {
|
|
279
|
+
name = 'database';
|
|
280
|
+
private connection: any;
|
|
281
|
+
|
|
282
|
+
async init(ctx: PluginContext) {
|
|
283
|
+
// Initialize connection
|
|
284
|
+
this.connection = await createConnection({
|
|
285
|
+
host: 'localhost',
|
|
286
|
+
database: 'myapp'
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Register as service
|
|
290
|
+
ctx.registerService('database', this.connection);
|
|
291
|
+
|
|
292
|
+
ctx.logger.info('Database connected');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async destroy() {
|
|
296
|
+
// Cleanup
|
|
297
|
+
await this.connection.close();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Service Consumer Pattern
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
306
|
+
|
|
307
|
+
export class RepositoryPlugin implements Plugin {
|
|
308
|
+
name = 'repository';
|
|
309
|
+
dependencies = ['database']; // Ensure database loads first
|
|
310
|
+
|
|
311
|
+
async init(ctx: PluginContext) {
|
|
312
|
+
// Get dependency
|
|
313
|
+
const db = ctx.getService('database');
|
|
314
|
+
|
|
315
|
+
// Create and register repository
|
|
316
|
+
const repo = new UserRepository(db);
|
|
317
|
+
ctx.registerService('user-repository', repo);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Event-Driven Pattern
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
326
|
+
|
|
327
|
+
// Publisher
|
|
328
|
+
export class DataPlugin implements Plugin {
|
|
329
|
+
name = 'data';
|
|
330
|
+
|
|
331
|
+
async init(ctx: PluginContext) {
|
|
332
|
+
const service = {
|
|
333
|
+
async create(entity: string, data: any) {
|
|
334
|
+
const result = await db.insert(entity, data);
|
|
335
|
+
|
|
336
|
+
// Trigger event
|
|
337
|
+
await ctx.trigger('data:created', { entity, data: result });
|
|
338
|
+
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
ctx.registerService('data', service);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Subscriber
|
|
348
|
+
export class AuditPlugin implements Plugin {
|
|
349
|
+
name = 'audit';
|
|
350
|
+
dependencies = ['data'];
|
|
351
|
+
|
|
352
|
+
async init(ctx: PluginContext) {
|
|
353
|
+
// Listen to events
|
|
354
|
+
ctx.hook('data:created', async ({ entity, data }) => {
|
|
355
|
+
ctx.logger.info(`Audit: ${entity} created`, { id: data.id });
|
|
356
|
+
|
|
357
|
+
await auditLog.write({
|
|
358
|
+
action: 'create',
|
|
359
|
+
entity,
|
|
360
|
+
entityId: data.id,
|
|
361
|
+
timestamp: new Date()
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Configuration Pattern
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
372
|
+
import { z } from 'zod';
|
|
373
|
+
|
|
374
|
+
const ConfigSchema = z.object({
|
|
375
|
+
apiKey: z.string(),
|
|
376
|
+
endpoint: z.string().url(),
|
|
377
|
+
timeout: z.number().default(5000)
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
export class ApiPlugin implements Plugin {
|
|
381
|
+
name = 'api-client';
|
|
382
|
+
|
|
383
|
+
constructor(private config: z.infer<typeof ConfigSchema>) {
|
|
384
|
+
// Validate config
|
|
385
|
+
ConfigSchema.parse(config);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async init(ctx: PluginContext) {
|
|
389
|
+
const client = new ApiClient({
|
|
390
|
+
apiKey: this.config.apiKey,
|
|
391
|
+
endpoint: this.config.endpoint,
|
|
392
|
+
timeout: this.config.timeout
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
ctx.registerService('api-client', client);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Usage
|
|
400
|
+
kernel.use(new ApiPlugin({
|
|
401
|
+
apiKey: process.env.API_KEY,
|
|
402
|
+
endpoint: 'https://api.example.com',
|
|
403
|
+
timeout: 10000
|
|
404
|
+
}));
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Factory Pattern
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
411
|
+
|
|
412
|
+
export class ConnectionPoolPlugin implements Plugin {
|
|
413
|
+
name = 'connection-pool';
|
|
414
|
+
private pool: any;
|
|
415
|
+
|
|
416
|
+
async init(ctx: PluginContext) {
|
|
417
|
+
this.pool = {
|
|
418
|
+
connections: new Map(),
|
|
419
|
+
|
|
420
|
+
getConnection(name: string) {
|
|
421
|
+
if (!this.connections.has(name)) {
|
|
422
|
+
this.connections.set(name, createConnection(name));
|
|
423
|
+
}
|
|
424
|
+
return this.connections.get(name);
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
closeAll() {
|
|
428
|
+
for (const conn of this.connections.values()) {
|
|
429
|
+
conn.close();
|
|
430
|
+
}
|
|
431
|
+
this.connections.clear();
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
ctx.registerService('connection-pool', this.pool);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async destroy() {
|
|
439
|
+
// Use stored reference from init phase
|
|
440
|
+
if (this.pool) {
|
|
441
|
+
this.pool.closeAll();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Middleware Pattern
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
451
|
+
|
|
452
|
+
export class LoggingMiddleware implements Plugin {
|
|
453
|
+
name = 'logging-middleware';
|
|
454
|
+
dependencies = ['http-server'];
|
|
455
|
+
|
|
456
|
+
async start(ctx: PluginContext) {
|
|
457
|
+
const server = ctx.getService('http-server');
|
|
458
|
+
|
|
459
|
+
// Register middleware
|
|
460
|
+
server.use(async (req, res, next) => {
|
|
461
|
+
const start = Date.now();
|
|
462
|
+
|
|
463
|
+
ctx.logger.info('Request', {
|
|
464
|
+
method: req.method,
|
|
465
|
+
path: req.path
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
await next();
|
|
469
|
+
|
|
470
|
+
const duration = Date.now() - start;
|
|
471
|
+
ctx.logger.info('Response', {
|
|
472
|
+
method: req.method,
|
|
473
|
+
path: req.path,
|
|
474
|
+
status: res.statusCode,
|
|
475
|
+
duration
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Lazy Loading Pattern
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
486
|
+
|
|
487
|
+
export class HeavyServicePlugin implements Plugin {
|
|
488
|
+
name = 'heavy-service';
|
|
489
|
+
private instance: any = null;
|
|
490
|
+
|
|
491
|
+
async init(ctx: PluginContext) {
|
|
492
|
+
// Register factory instead of instance
|
|
493
|
+
const factory = {
|
|
494
|
+
async getInstance() {
|
|
495
|
+
if (!this.instance) {
|
|
496
|
+
ctx.logger.info('Lazy loading heavy service...');
|
|
497
|
+
this.instance = await loadHeavyService();
|
|
498
|
+
}
|
|
499
|
+
return this.instance;
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
ctx.registerService('heavy-service', factory);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Usage
|
|
508
|
+
const factory = kernel.getService('heavy-service');
|
|
509
|
+
const service = await factory.getInstance(); // Loaded only when needed
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Health Check Pattern
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
import { Plugin, PluginContext } from '@objectstack/core';
|
|
516
|
+
|
|
517
|
+
export class HealthCheckPlugin implements Plugin {
|
|
518
|
+
name = 'health-check';
|
|
519
|
+
dependencies = ['http-server', 'database', 'cache'];
|
|
520
|
+
|
|
521
|
+
async start(ctx: PluginContext) {
|
|
522
|
+
const server = ctx.getService('http-server');
|
|
523
|
+
|
|
524
|
+
server.get('/health', async (req, res) => {
|
|
525
|
+
const checks = await Promise.all([
|
|
526
|
+
this.checkDatabase(ctx),
|
|
527
|
+
this.checkCache(ctx),
|
|
528
|
+
this.checkDiskSpace()
|
|
529
|
+
]);
|
|
530
|
+
|
|
531
|
+
const healthy = checks.every(c => c.healthy);
|
|
532
|
+
|
|
533
|
+
res.status(healthy ? 200 : 503).json({
|
|
534
|
+
status: healthy ? 'healthy' : 'unhealthy',
|
|
535
|
+
checks
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private async checkDatabase(ctx: PluginContext) {
|
|
541
|
+
try {
|
|
542
|
+
const db = ctx.getService('database');
|
|
543
|
+
await db.ping();
|
|
544
|
+
return { name: 'database', healthy: true };
|
|
545
|
+
} catch (error) {
|
|
546
|
+
return { name: 'database', healthy: false, error: error.message };
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private async checkCache(ctx: PluginContext) {
|
|
551
|
+
try {
|
|
552
|
+
const cache = ctx.getService('cache');
|
|
553
|
+
await cache.ping();
|
|
554
|
+
return { name: 'cache', healthy: true };
|
|
555
|
+
} catch (error) {
|
|
556
|
+
return { name: 'cache', healthy: false, error: error.message };
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
private async checkDiskSpace() {
|
|
561
|
+
// Implementation
|
|
562
|
+
return { name: 'disk', healthy: true };
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
271
567
|
## Documentation
|
|
272
568
|
|
|
273
569
|
- [MiniKernel Guide](../../MINI_KERNEL_GUIDE.md) - Complete API documentation and patterns
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,6 @@ export { AppPlugin } from './app-plugin.js';
|
|
|
4
4
|
export { HttpServer } from './http-server.js';
|
|
5
5
|
export { RestServer } from './rest-server.js';
|
|
6
6
|
export { RouteManager, RouteGroupBuilder } from './route-manager.js';
|
|
7
|
+
export type { RouteEntry } from './route-manager.js';
|
|
7
8
|
export { MiddlewareManager } from './middleware.js';
|
|
8
9
|
export * from '@objectstack/core';
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/runtime",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "ObjectStack Core Runtime & Query Engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@objectstack/core": "0.9.
|
|
10
|
-
"@objectstack/types": "0.9.
|
|
11
|
-
"@objectstack/spec": "0.9.
|
|
9
|
+
"@objectstack/core": "0.9.1",
|
|
10
|
+
"@objectstack/types": "0.9.1",
|
|
11
|
+
"@objectstack/spec": "0.9.1"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"typescript": "^5.0.0"
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { AppPlugin } from './app-plugin.js';
|
|
|
9
9
|
export { HttpServer } from './http-server.js';
|
|
10
10
|
export { RestServer } from './rest-server.js';
|
|
11
11
|
export { RouteManager, RouteGroupBuilder } from './route-manager.js';
|
|
12
|
+
export type { RouteEntry } from './route-manager.js';
|
|
12
13
|
export { MiddlewareManager } from './middleware.js';
|
|
13
14
|
|
|
14
15
|
// Export Types
|