Added logger object to global NS
Some checks failed
armco-org/node-starter-kit/pipeline/head There was a failure building this commit
Some checks failed
armco-org/node-starter-kit/pipeline/head There was a failure building this commit
This commit is contained in:
344
CONFIG_DRIVEN_BREAKING_CHANGE.md
Normal file
344
CONFIG_DRIVEN_BREAKING_CHANGE.md
Normal file
@@ -0,0 +1,344 @@
|
||||
# Config-Driven Initialization - Breaking Change Summary
|
||||
|
||||
## What Changed
|
||||
|
||||
NSK v2 now supports **config-driven initialization** where `Application.create().build()` automatically loads and instantiates plugins from `armcorc.json` configuration.
|
||||
|
||||
## Problem Solved
|
||||
|
||||
### Before
|
||||
- Config files only contained plugin **settings**
|
||||
- You still had to **manually register** plugin instances in code:
|
||||
```typescript
|
||||
App.create(app)
|
||||
.plugin(createLoggerPlugin()) // Code change required
|
||||
.plugin(createDatabasePlugin()) // Code change required
|
||||
.build()
|
||||
```
|
||||
- Changing plugins required **code changes + rebuild + redeploy**
|
||||
|
||||
### After
|
||||
- Config file is the **single source of truth**
|
||||
- `Application.create().build()` auto-loads plugins from config
|
||||
- Changing plugins only requires **restart** (no rebuild)
|
||||
|
||||
## How It Works
|
||||
|
||||
### 1. Plugin Factory Registry
|
||||
|
||||
When you import `@armco/node-starter-kit`, all built-in plugins automatically register their factory functions:
|
||||
|
||||
```typescript
|
||||
// Happens automatically when you import NSK
|
||||
registerPluginFactory('logger', (config) => createLoggerPlugin(config))
|
||||
registerPluginFactory('database', (config) => createDatabasePlugin(config))
|
||||
registerPluginFactory('cache', (config) => createCachePlugin(config))
|
||||
// etc...
|
||||
```
|
||||
|
||||
### 2. Hybrid Loading Logic (Config + Manual)
|
||||
|
||||
```typescript
|
||||
// In Application.build():
|
||||
// 1. Config is PRIMARY source of truth
|
||||
// 2. Manual registration OVERRIDES specific plugins
|
||||
|
||||
if (config has plugins) {
|
||||
// Auto-load plugins from config
|
||||
for (pluginName, pluginConfig in config.plugins) {
|
||||
if (pluginName NOT manually registered && enabled !== false) {
|
||||
const factory = pluginRegistry.get(pluginName)
|
||||
const plugin = factory(pluginConfig)
|
||||
application.plugin(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
// Add manually registered plugins (these override config)
|
||||
for (manualPlugin in manualPlugins) {
|
||||
application.plugin(manualPlugin)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Behavior:**
|
||||
- Config defines **what** plugins are enabled
|
||||
- Manual registration **overrides** specific plugin instances
|
||||
- Manually registered plugins should still be declared in config (for consistency)
|
||||
|
||||
## Usage
|
||||
|
||||
### Approach 1: Pure Config-Driven (Recommended)
|
||||
|
||||
**server.ts:**
|
||||
```typescript
|
||||
import express from 'express'
|
||||
import { Application } from '@armco/node-starter-kit'
|
||||
|
||||
const app = express()
|
||||
const nsk = await Application.create(app).build() // That's it!
|
||||
|
||||
app.listen(3000)
|
||||
```
|
||||
|
||||
**armcorc.json:**
|
||||
```json
|
||||
{
|
||||
"appName": "auth-core",
|
||||
"plugins": {
|
||||
"logger": { "enabled": true, "level": "info" },
|
||||
"database": { "enabled": true, "uri": "mongodb://localhost/authdb" },
|
||||
"cache": { "enabled": true, "adapter": "redis" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**To change config:**
|
||||
1. Edit `armcorc.json`
|
||||
2. Restart the app
|
||||
3. Done! (no rebuild needed)
|
||||
|
||||
### Approach 2: Programmatic Config (Cloud Config)
|
||||
|
||||
```typescript
|
||||
import { Application } from '@armco/node-starter-kit'
|
||||
|
||||
// Load from config server, K8s ConfigMap, env vars, etc.
|
||||
const config = {
|
||||
appName: 'auth-core',
|
||||
plugins: {
|
||||
logger: {
|
||||
enabled: true,
|
||||
level: process.env.LOG_LEVEL || 'info'
|
||||
},
|
||||
database: {
|
||||
enabled: true,
|
||||
uri: process.env.MONGODB_URI
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
.withConfig(config)
|
||||
.build()
|
||||
```
|
||||
|
||||
### Approach 3: Hybrid (RECOMMENDED for most cases)
|
||||
|
||||
Config is primary source of truth, manual registration for overrides:
|
||||
|
||||
**armcorc.json:**
|
||||
```json
|
||||
{
|
||||
"appName": "auth-core",
|
||||
"plugins": {
|
||||
"logger": { "enabled": true, "level": "info" },
|
||||
"database": { "enabled": true, "uri": "mongodb://..." },
|
||||
"cache": { "enabled": true },
|
||||
"socket": { "enabled": true }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**server.ts:**
|
||||
```typescript
|
||||
import http from 'http'
|
||||
import { Application, createSocketPlugin } from '@armco/node-starter-kit'
|
||||
|
||||
const app = express()
|
||||
const server = http.createServer(app)
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
// Config auto-loads: logger, database, cache
|
||||
// Manual registration for socket (requires server instance)
|
||||
.plugin(createSocketPlugin(server))
|
||||
.build()
|
||||
|
||||
server.listen(3000)
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Config declares all plugins (single source of truth)
|
||||
- Code only handles runtime requirements (e.g., HttpServer for socket)
|
||||
- Easy restart-based reconfiguration for most plugins
|
||||
|
||||
### Approach 4: Pure Manual (Still Supported)
|
||||
|
||||
```typescript
|
||||
import { Application, createLoggerPlugin } from '@armco/node-starter-kit'
|
||||
|
||||
// Explicit plugin registration (old way, still works)
|
||||
const nsk = await Application.create(app)
|
||||
.plugin(createLoggerPlugin({ level: 'info' }))
|
||||
.plugin(createDatabasePlugin({ uri: 'mongodb://...' }))
|
||||
.build()
|
||||
```
|
||||
|
||||
## Supported Plugins
|
||||
|
||||
These plugins can be auto-loaded from config:
|
||||
|
||||
| Plugin Name | Config Key | Notes |
|
||||
|-------------|------------|-------|
|
||||
| Logger | `logger` | ✅ Fully supported |
|
||||
| Database | `database` | ✅ Fully supported |
|
||||
| Cache | `cache` | ✅ Fully supported |
|
||||
| Scheduler | `scheduler` | ✅ Fully supported |
|
||||
| Telemetry | `telemetry` or `opentelemetry` | ✅ Fully supported |
|
||||
| Socket.IO | `socket` | ⚠️ **Requires manual registration** (needs `HttpServer` instance) |
|
||||
|
||||
### Socket.IO Special Case
|
||||
|
||||
Socket.IO plugin requires an `HttpServer` instance, which cannot be provided via JSON config:
|
||||
|
||||
```typescript
|
||||
import http from 'http'
|
||||
import { Application, createSocketPlugin } from '@armco/node-starter-kit'
|
||||
|
||||
const app = express()
|
||||
const server = http.createServer(app)
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
// Auto-load other plugins from config
|
||||
.withConfig('./armcorc.json')
|
||||
// Manually add socket (requires server instance)
|
||||
.plugin(createSocketPlugin(server, { cors: { origin: '*' } }))
|
||||
.build()
|
||||
|
||||
server.listen(3000)
|
||||
```
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Minimal Config
|
||||
```json
|
||||
{
|
||||
"appName": "my-service",
|
||||
"plugins": {
|
||||
"logger": { "enabled": true }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Production Config
|
||||
```json
|
||||
{
|
||||
"appName": "auth-core",
|
||||
"env": "production",
|
||||
"plugins": {
|
||||
"logger": {
|
||||
"enabled": true,
|
||||
"level": "info",
|
||||
"format": "json"
|
||||
},
|
||||
"database": {
|
||||
"enabled": true,
|
||||
"adapter": "mongoose",
|
||||
"uri": "mongodb://mongo:27017/authdb",
|
||||
"options": {
|
||||
"maxPoolSize": 10
|
||||
}
|
||||
},
|
||||
"cache": {
|
||||
"enabled": true,
|
||||
"adapter": "redis",
|
||||
"uri": "redis://redis:6379",
|
||||
"ttl": 3600,
|
||||
"keyPrefix": "auth:"
|
||||
},
|
||||
"telemetry": {
|
||||
"enabled": true,
|
||||
"serviceName": "auth-core",
|
||||
"serviceVersion": "1.0.0",
|
||||
"otlpEndpoint": "http://tempo:4318",
|
||||
"exporters": ["otlp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Disable a Plugin
|
||||
```json
|
||||
{
|
||||
"appName": "my-service",
|
||||
"plugins": {
|
||||
"logger": { "enabled": true },
|
||||
"cache": { "enabled": false } // Disabled
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Path
|
||||
|
||||
### If You're Happy with Current Approach
|
||||
|
||||
**No changes needed!** Manual plugin registration still works:
|
||||
|
||||
```typescript
|
||||
const nsk = await Application.create(app)
|
||||
.plugin(createLoggerPlugin())
|
||||
.build()
|
||||
```
|
||||
|
||||
### If You Want Config-Driven
|
||||
|
||||
1. **Create `armcorc.json`** with your plugin configs
|
||||
2. **Remove manual `.plugin()` calls**
|
||||
3. **Simplify to** `Application.create(app).build()`
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Restart-based reconfiguration**: No rebuild or redeploy needed
|
||||
2. **GitOps friendly**: Config changes tracked in version control
|
||||
3. **Cloud config integration**: Load from K8s ConfigMaps, AWS Parameter Store, etc.
|
||||
4. **Environment-specific**: Easy to swap configs per environment
|
||||
5. **Cleaner code**: Less boilerplate in application code
|
||||
|
||||
## Runtime Reconfiguration Workflow
|
||||
|
||||
```bash
|
||||
# 1. Edit config
|
||||
vim armcorc.json
|
||||
|
||||
# 2. Commit (optional, for GitOps)
|
||||
git commit -am "Enable cache plugin"
|
||||
|
||||
# 3. Restart app (PM2, K8s, systemd, etc.)
|
||||
pm2 reload my-service
|
||||
# or
|
||||
kubectl rollout restart deployment/my-service
|
||||
|
||||
# 4. New config is active (no rebuild!)
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Files Changed/Added
|
||||
|
||||
**New:**
|
||||
- `v2/core/PluginFactory.ts` - Plugin factory registry
|
||||
- `v2/examples/config-driven-initialization.ts` - Usage examples
|
||||
- `v2/examples/armco.config.full.json` - Full config reference
|
||||
- `v2/CONFIG_DRIVEN_INITIALIZATION.md` - Comprehensive docs
|
||||
|
||||
**Modified:**
|
||||
- `v2/core/Application.ts` - Auto-load plugins from config
|
||||
- `v2/index.ts` - Export PluginFactory
|
||||
- All plugin `index.ts` files - Register factories on import
|
||||
|
||||
### Type Safety
|
||||
|
||||
Config is still validated using Zod schemas, ensuring type safety at runtime.
|
||||
|
||||
## Questions?
|
||||
|
||||
- See `/v2/examples/config-driven-initialization.ts` for complete examples
|
||||
- See `/v2/CONFIG_DRIVEN_INITIALIZATION.md` for detailed documentation
|
||||
- See `/v2/examples/armco.config.full.json` for full config reference
|
||||
|
||||
## Summary
|
||||
|
||||
✅ **Backward compatible** - Old approach still works
|
||||
✅ **Opt-in** - Use config-driven only if you want
|
||||
✅ **Restart-based reconfiguration** - No rebuild needed
|
||||
✅ **Cloud-native** - Integrates with config management systems
|
||||
✅ **Type-safe** - Still validated with Zod schemas
|
||||
5
build.ts
5
build.ts
@@ -98,6 +98,11 @@ import pkg from "./package.json";
|
||||
fs.copyFileSync("./LICENSE", "./dist/LICENSE");
|
||||
}
|
||||
|
||||
// Copy globals.d.ts for TypeScript type augmentation
|
||||
if (fs.existsSync("./v2/globals.d.ts")) {
|
||||
fs.copyFileSync("./v2/globals.d.ts", "./dist/globals.d.ts");
|
||||
}
|
||||
|
||||
logger.info('✅ Build completed successfully!');
|
||||
logger.info('📦 Package ready in ./dist folder');
|
||||
} catch (err) {
|
||||
|
||||
372
v2/CONFIG_DRIVEN_INITIALIZATION.md
Normal file
372
v2/CONFIG_DRIVEN_INITIALIZATION.md
Normal file
@@ -0,0 +1,372 @@
|
||||
# Config-Driven Initialization
|
||||
|
||||
NSK v2 now supports **config-driven initialization**, enabling restart-based reconfiguration without code changes or rebuilds. This document explains how it works and how to use it.
|
||||
|
||||
## Overview
|
||||
|
||||
### Previous Approach (Still Supported)
|
||||
```typescript
|
||||
import { Application, createLoggerPlugin, createDatabasePlugin } from '@armco/node-starter-kit'
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
.plugin(createLoggerPlugin({ level: 'info' }))
|
||||
.plugin(createDatabasePlugin({ uri: process.env.MONGODB_URI }))
|
||||
.build()
|
||||
```
|
||||
|
||||
### New Config-Driven Approach
|
||||
```typescript
|
||||
import { Application } from '@armco/node-starter-kit'
|
||||
|
||||
// Just this - plugins auto-loaded from armcorc.json
|
||||
const nsk = await Application.create(app).build()
|
||||
```
|
||||
|
||||
**armcorc.json:**
|
||||
```json
|
||||
{
|
||||
"appName": "my-service",
|
||||
"plugins": {
|
||||
"logger": { "enabled": true, "level": "info" },
|
||||
"database": { "enabled": true, "uri": "mongodb://localhost/mydb" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Restart-based reconfiguration**: Change config → restart app → changes applied (no rebuild)
|
||||
2. **Single source of truth**: All configuration in one place (JSON/YAML)
|
||||
3. **Cloud config integration**: Load from config server, env vars, K8s ConfigMaps, etc.
|
||||
4. **Environment-specific configs**: Easy to swap configs per environment
|
||||
5. **GitOps friendly**: Config changes tracked in version control
|
||||
|
||||
## How It Works
|
||||
|
||||
### Plugin Factory Registry
|
||||
|
||||
NSK maintains a global registry that maps plugin names to factory functions:
|
||||
|
||||
```typescript
|
||||
// Internally, when you import @armco/node-starter-kit:
|
||||
registerPluginFactory('logger', (config) => createLoggerPlugin(config))
|
||||
registerPluginFactory('database', (config) => createDatabasePlugin(config))
|
||||
registerPluginFactory('cache', (config) => createCachePlugin(config))
|
||||
// ... etc
|
||||
```
|
||||
|
||||
### Auto-Loading Process
|
||||
|
||||
When `Application.create(app).build()` is called **without** explicit plugins:
|
||||
|
||||
1. NSK searches for `armcorc.json` (or `.armcorc`, `armco.config.js`, etc.)
|
||||
2. Reads the `plugins` section
|
||||
3. For each enabled plugin, looks up its factory in the registry
|
||||
4. Instantiates the plugin with its config
|
||||
5. Registers and starts all plugins
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### Pattern 1: Pure Config-Driven (Recommended)
|
||||
|
||||
**server.ts:**
|
||||
```typescript
|
||||
import express from 'express'
|
||||
import { Application } from '@armco/node-starter-kit'
|
||||
|
||||
const app = express()
|
||||
|
||||
// All plugins loaded from armcorc.json
|
||||
const nsk = await Application.create(app).build()
|
||||
|
||||
app.listen(3000)
|
||||
```
|
||||
|
||||
**armcorc.json:**
|
||||
```json
|
||||
{
|
||||
"appName": "auth-core",
|
||||
"plugins": {
|
||||
"logger": {
|
||||
"enabled": true,
|
||||
"level": "info",
|
||||
"format": "json"
|
||||
},
|
||||
"database": {
|
||||
"enabled": true,
|
||||
"adapter": "mongoose",
|
||||
"uri": "mongodb://localhost:27017/authdb"
|
||||
},
|
||||
"cache": {
|
||||
"enabled": true,
|
||||
"adapter": "redis",
|
||||
"uri": "redis://localhost:6379"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Cloud Config Integration
|
||||
|
||||
```typescript
|
||||
import { Application } from '@armco/node-starter-kit'
|
||||
|
||||
// Load from cloud config store
|
||||
const config = await fetchConfigFromCloudStore()
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
.withConfig(config)
|
||||
.build()
|
||||
```
|
||||
|
||||
### Pattern 3: Environment Variables
|
||||
|
||||
```typescript
|
||||
import { Application } from '@armco/node-starter-kit'
|
||||
|
||||
const config = {
|
||||
appName: 'auth-core',
|
||||
plugins: {
|
||||
logger: {
|
||||
enabled: true,
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
},
|
||||
database: {
|
||||
enabled: true,
|
||||
uri: process.env.MONGODB_URI,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
.withConfig(config)
|
||||
.build()
|
||||
```
|
||||
|
||||
### Pattern 4: Mixed (Config + Manual)
|
||||
|
||||
Use config for standard plugins, manual registration for special cases:
|
||||
|
||||
```typescript
|
||||
import { Application } from '@armco/node-starter-kit'
|
||||
import http from 'http'
|
||||
|
||||
const app = express()
|
||||
const server = http.createServer(app)
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
// Auto-load plugins from config
|
||||
.withConfig('./armcorc.json')
|
||||
// Manually add socket (requires server instance)
|
||||
.plugin(createSocketPlugin(server))
|
||||
.build()
|
||||
```
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
### Config File Locations
|
||||
|
||||
NSK searches for configuration in this order:
|
||||
1. `armco.config.js` (TypeScript: `armco.config.ts`)
|
||||
2. `armco.config.json`
|
||||
3. `.armcorc`
|
||||
4. `.armcorc.json`
|
||||
5. `.armcorc.js`
|
||||
6. `armcorc.json`
|
||||
7. `package.json` (under `"armco"` key)
|
||||
|
||||
### Plugin Configuration Schema
|
||||
|
||||
Each plugin in `plugins` object follows this structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"<plugin-name>": {
|
||||
"enabled": true, // Set to false to disable
|
||||
"priority": 10, // Load order (optional)
|
||||
"<plugin-specific-config>": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Supported Plugin Keys
|
||||
|
||||
| Key | Plugin | Config Type |
|
||||
|-----|--------|-------------|
|
||||
| `logger` | Logger | `LoggerConfig` |
|
||||
| `database` | Database | `DatabaseConfig` |
|
||||
| `cache` | Cache | `CacheConfig` |
|
||||
| `scheduler` | Scheduler | `SchedulerConfig` |
|
||||
| `telemetry` or `opentelemetry` | OpenTelemetry | `OpenTelemetryConfig` |
|
||||
|
||||
**Note**: `socket` plugin cannot be auto-loaded (requires `HttpServer` instance).
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### If You're Using Manual Plugin Registration
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
const nsk = await Application.create(app)
|
||||
.plugin(createLoggerPlugin({ level: 'info' }))
|
||||
.plugin(createDatabasePlugin({ uri: 'mongodb://...' }))
|
||||
.build()
|
||||
```
|
||||
|
||||
**After (Option 1 - Config File):**
|
||||
|
||||
Create `armcorc.json`:
|
||||
```json
|
||||
{
|
||||
"appName": "my-service",
|
||||
"plugins": {
|
||||
"logger": { "enabled": true, "level": "info" },
|
||||
"database": { "enabled": true, "uri": "mongodb://..." }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Update code:
|
||||
```typescript
|
||||
const nsk = await Application.create(app).build()
|
||||
```
|
||||
|
||||
**After (Option 2 - Inline Config):**
|
||||
```typescript
|
||||
const nsk = await Application.create(app)
|
||||
.withConfig({
|
||||
appName: 'my-service',
|
||||
plugins: {
|
||||
logger: { enabled: true, level: 'info' },
|
||||
database: { enabled: true, uri: 'mongodb://...' },
|
||||
},
|
||||
})
|
||||
.build()
|
||||
```
|
||||
|
||||
## Runtime Reconfiguration
|
||||
|
||||
### Pattern: Graceful Reload
|
||||
|
||||
1. **Update config file** (manually, API, or config management tool)
|
||||
2. **Send SIGTERM** to the process
|
||||
3. **Process manager restarts** the app (PM2, K8s, systemd, etc.)
|
||||
4. **NSK loads new config** on startup
|
||||
|
||||
```bash
|
||||
# Example with PM2
|
||||
vim armcorc.json # Edit config
|
||||
pm2 reload my-service # Graceful reload
|
||||
```
|
||||
|
||||
### Pattern: Hot Reload (Advanced)
|
||||
|
||||
For hot reload without restart, you'll need custom logic:
|
||||
|
||||
```typescript
|
||||
import { watch } from 'fs'
|
||||
import { Application } from '@armco/node-starter-kit'
|
||||
|
||||
let nsk: Application
|
||||
|
||||
async function loadApp() {
|
||||
if (nsk) {
|
||||
await nsk.shutdown()
|
||||
}
|
||||
nsk = await Application.create(app).build()
|
||||
}
|
||||
|
||||
// Initial load
|
||||
await loadApp()
|
||||
|
||||
// Watch for config changes
|
||||
watch('./armcorc.json', async () => {
|
||||
console.log('Config changed, reloading...')
|
||||
await loadApp()
|
||||
})
|
||||
```
|
||||
|
||||
## Custom Plugins
|
||||
|
||||
To make your custom plugin auto-loadable:
|
||||
|
||||
```typescript
|
||||
import { registerPluginFactory, type PluginFactory } from '@armco/node-starter-kit'
|
||||
|
||||
export class MyCustomPlugin extends BasePlugin<MyConfig> {
|
||||
// ... implementation
|
||||
}
|
||||
|
||||
export function createMyPlugin(config: MyConfig) {
|
||||
return new MyCustomPlugin(config)
|
||||
}
|
||||
|
||||
// Register factory
|
||||
registerPluginFactory('mycustom', (config) =>
|
||||
createMyPlugin(config as unknown as MyConfig)
|
||||
)
|
||||
```
|
||||
|
||||
Then use in config:
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"mycustom": {
|
||||
"enabled": true,
|
||||
"myOption": "value"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use config files for production**: More maintainable than inline configs
|
||||
2. **Environment-specific configs**: Use separate files (`armcorc.prod.json`, `armcorc.dev.json`)
|
||||
3. **Sensitive data**: Use env vars, not config files:
|
||||
```typescript
|
||||
const config = await ConfigLoader.load()
|
||||
config.plugins.database.uri = process.env.MONGODB_URI
|
||||
```
|
||||
4. **Validate configs**: NSK uses Zod for validation, but add your own checks
|
||||
5. **Version control**: Commit configs (except secrets) for GitOps workflows
|
||||
6. **Documentation**: Document your config schema for your team
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Plugin factory not found"
|
||||
|
||||
**Cause**: Plugin not imported, so factory wasn't registered.
|
||||
|
||||
**Solution**: Import the main package:
|
||||
```typescript
|
||||
import '@armco/node-starter-kit' // Triggers factory registration
|
||||
```
|
||||
|
||||
Or import specific plugin:
|
||||
```typescript
|
||||
import '@armco/node-starter-kit/plugins/logger'
|
||||
```
|
||||
|
||||
### Config not loading
|
||||
|
||||
**Cause**: Config file not found or invalid.
|
||||
|
||||
**Solution**: Check file name and location. Enable debug:
|
||||
```typescript
|
||||
const loader = new ConfigLoader()
|
||||
const config = loader.load()
|
||||
console.log('Loaded config:', config)
|
||||
```
|
||||
|
||||
### Plugin not initializing
|
||||
|
||||
**Cause**: Missing required config fields or `enabled: false`.
|
||||
|
||||
**Solution**: Check plugin config requirements in type definitions.
|
||||
|
||||
## Examples
|
||||
|
||||
See `/v2/examples/config-driven-initialization.ts` for complete examples.
|
||||
370
v2/GLOBAL_LOGGER.md
Normal file
370
v2/GLOBAL_LOGGER.md
Normal file
@@ -0,0 +1,370 @@
|
||||
# Global Logger Access
|
||||
|
||||
NSK v2 provides first-class global access to the logger, matching the legacy NSK v1 behavior. Once NSK is initialized with the logger plugin, the logger is automatically available throughout your application **without requiring imports**.
|
||||
|
||||
## How It Works
|
||||
|
||||
### 1. Automatic Global Injection
|
||||
|
||||
When the logger plugin is installed, NSK automatically makes it available on the global object:
|
||||
|
||||
```typescript
|
||||
// In your server.ts
|
||||
import { Application } from '@armco/node-starter-kit'
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
.withConfig({
|
||||
appName: 'my-service',
|
||||
plugins: {
|
||||
logger: { enabled: true, level: 'info' }
|
||||
}
|
||||
})
|
||||
.build()
|
||||
|
||||
// Logger is now globally available!
|
||||
logger.info('Application started')
|
||||
```
|
||||
|
||||
### 2. TypeScript Type Safety
|
||||
|
||||
NSK exports a global namespace augmentation (`globals.d.ts`) that provides full TypeScript type safety for the global logger:
|
||||
|
||||
```typescript
|
||||
// globals.d.ts (automatically included when you install NSK)
|
||||
declare global {
|
||||
var logger: Logger
|
||||
}
|
||||
```
|
||||
|
||||
**No configuration needed in your project!** Just install NSK and TypeScript will automatically recognize the `logger` global.
|
||||
|
||||
## Usage
|
||||
|
||||
### In Route Handlers
|
||||
|
||||
```typescript
|
||||
app.get('/api/users/:id', (req, res) => {
|
||||
// No import needed!
|
||||
logger.info('User request received', { userId: req.params.id })
|
||||
|
||||
try {
|
||||
const user = getUserById(req.params.id)
|
||||
logger.info('User found', { user })
|
||||
res.json(user)
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch user', { error })
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### In Service Classes
|
||||
|
||||
```typescript
|
||||
class UserService {
|
||||
async createUser(data: any) {
|
||||
// Logger is globally available
|
||||
logger.info('Creating user', { email: data.email })
|
||||
|
||||
try {
|
||||
const user = await db.users.create(data)
|
||||
logger.info('User created', { userId: user.id })
|
||||
return user
|
||||
} catch (error) {
|
||||
logger.error('Failed to create user', { error })
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### In Middleware
|
||||
|
||||
```typescript
|
||||
function requestLogger(req, res, next) {
|
||||
const start = Date.now()
|
||||
|
||||
logger.info('Incoming request', {
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
})
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start
|
||||
logger.info('Request completed', {
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
statusCode: res.statusCode,
|
||||
duration: `${duration}ms`,
|
||||
})
|
||||
})
|
||||
|
||||
next()
|
||||
}
|
||||
```
|
||||
|
||||
### In Error Handlers
|
||||
|
||||
```typescript
|
||||
function errorHandler(err, req, res, next) {
|
||||
logger.error('Unhandled error', {
|
||||
error: err.message,
|
||||
stack: err.stack,
|
||||
path: req.path,
|
||||
})
|
||||
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
```
|
||||
|
||||
### In Background Jobs
|
||||
|
||||
```typescript
|
||||
async function processQueue() {
|
||||
logger.info('Processing queue')
|
||||
|
||||
while (true) {
|
||||
const job = await queue.getNext()
|
||||
|
||||
if (!job) {
|
||||
logger.debug('No jobs in queue')
|
||||
await sleep(1000)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info('Processing job', { jobId: job.id })
|
||||
await processJob(job)
|
||||
logger.info('Job completed', { jobId: job.id })
|
||||
} catch (error) {
|
||||
logger.error('Job failed', { jobId: job.id, error })
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
### 1. No Import Boilerplate
|
||||
|
||||
**Before (with imports):**
|
||||
```typescript
|
||||
import { logger } from './logger' // Repeated in every file
|
||||
import { logger } from '../utils/logger' // Different paths
|
||||
import { getLogger } from '@armco/node-starter-kit' // Different ways
|
||||
```
|
||||
|
||||
**After (global):**
|
||||
```typescript
|
||||
// No imports needed - logger is just available!
|
||||
logger.info('Request received')
|
||||
```
|
||||
|
||||
### 2. Consistency
|
||||
|
||||
Every file uses the same logger instance automatically:
|
||||
- No confusion about which logger to import
|
||||
- No risk of multiple logger instances
|
||||
- Configuration changes apply everywhere instantly
|
||||
|
||||
### 3. Cleaner Code
|
||||
|
||||
```typescript
|
||||
// ❌ With imports (verbose)
|
||||
import express from 'express'
|
||||
import { logger } from '../utils/logger'
|
||||
|
||||
function handler(req, res) {
|
||||
logger.info('Processing request')
|
||||
// ...
|
||||
}
|
||||
|
||||
// ✅ With global (clean)
|
||||
import express from 'express'
|
||||
|
||||
function handler(req, res) {
|
||||
logger.info('Processing request')
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Migration Compatibility
|
||||
|
||||
Matches legacy NSK v1 behavior - existing code works without changes!
|
||||
|
||||
## TypeScript Configuration
|
||||
|
||||
### No Configuration Needed! ✅
|
||||
|
||||
When you install `@armco/node-starter-kit`, TypeScript automatically recognizes the global logger through the exported `globals.d.ts` file.
|
||||
|
||||
### How It Works
|
||||
|
||||
1. NSK exports `globals.d.ts` in its package
|
||||
2. TypeScript automatically picks it up via the triple-slash reference in `index.ts`:
|
||||
```typescript
|
||||
/// <reference path="./globals.d.ts" />
|
||||
```
|
||||
3. Your project gets full type safety without any configuration!
|
||||
|
||||
### If You Need Manual Configuration (Rare)
|
||||
|
||||
In very rare cases, if TypeScript doesn't pick up the types automatically, you can add this to your `tsconfig.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"types": ["@armco/node-starter-kit"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
But this is **usually not necessary** - the types are included automatically.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Initialize NSK Early
|
||||
|
||||
Ensure NSK is initialized before using the logger:
|
||||
|
||||
```typescript
|
||||
// server.ts
|
||||
import { Application } from '@armco/node-starter-kit'
|
||||
|
||||
async function main() {
|
||||
// Initialize NSK first
|
||||
const nsk = await Application.create(app)
|
||||
.withConfig('./armcorc.json')
|
||||
.build()
|
||||
|
||||
// Now logger is available
|
||||
logger.info('Application initialized')
|
||||
|
||||
// Import other modules (they can now use logger)
|
||||
const { setupRoutes } = await import('./routes')
|
||||
setupRoutes(app)
|
||||
}
|
||||
|
||||
main()
|
||||
```
|
||||
|
||||
### 2. Structured Logging
|
||||
|
||||
Use structured logging with context objects:
|
||||
|
||||
```typescript
|
||||
// ✅ Good: Structured with context
|
||||
logger.info('User login', {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
ip: req.ip,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
|
||||
// ❌ Less good: String concatenation
|
||||
logger.info(`User ${user.id} logged in from ${req.ip}`)
|
||||
```
|
||||
|
||||
### 3. Appropriate Log Levels
|
||||
|
||||
Use the right log level for each situation:
|
||||
|
||||
```typescript
|
||||
logger.debug('Detailed debugging info') // Development only
|
||||
logger.info('Normal operations') // General info
|
||||
logger.warn('Something unusual') // Potential issues
|
||||
logger.error('Something failed', { error }) // Errors
|
||||
```
|
||||
|
||||
### 4. Error Logging
|
||||
|
||||
Always include error objects and context:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
await riskyOperation()
|
||||
} catch (error) {
|
||||
logger.error('Operation failed', {
|
||||
error, // Full error object
|
||||
operation: 'riskyOperation',
|
||||
userId: user.id,
|
||||
context: { /* additional info */ }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Comparison: Legacy vs V2
|
||||
|
||||
### Legacy NSK v1
|
||||
|
||||
**Required manual namespace declaration in host project:**
|
||||
|
||||
```typescript
|
||||
// In your project's types/global.d.ts (had to create this)
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
logger: any
|
||||
}
|
||||
}
|
||||
var logger: any
|
||||
}
|
||||
```
|
||||
|
||||
### NSK v2
|
||||
|
||||
**No manual configuration needed!**
|
||||
|
||||
Just install NSK and the types are automatically available. The namespace augmentation is exported from NSK itself.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Cannot find name 'logger'"
|
||||
|
||||
**Cause**: NSK not initialized yet or logger plugin not enabled.
|
||||
|
||||
**Solution**:
|
||||
1. Ensure logger plugin is enabled in `armcorc.json`
|
||||
2. Initialize NSK before using the logger
|
||||
3. Check that NSK initialization completed successfully
|
||||
|
||||
### TypeScript error: "Property 'logger' does not exist"
|
||||
|
||||
**Cause**: TypeScript not picking up the global types (very rare).
|
||||
|
||||
**Solution**:
|
||||
1. Restart your TypeScript language server
|
||||
2. Check that `@armco/node-starter-kit` is installed
|
||||
3. If still not working, add to `tsconfig.json`:
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"types": ["@armco/node-starter-kit"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Logger not available in some files
|
||||
|
||||
**Cause**: File is imported/executed before NSK initialization.
|
||||
|
||||
**Solution**: Ensure NSK is initialized before importing other application modules:
|
||||
|
||||
```typescript
|
||||
// ✅ Good
|
||||
async function main() {
|
||||
await initNSK() // Initialize first
|
||||
const routes = await import('./routes') // Then import
|
||||
}
|
||||
|
||||
// ❌ Bad
|
||||
import { routes } from './routes' // Imported before NSK init
|
||||
async function main() {
|
||||
await initNSK()
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See `/v2/examples/global-logger-usage.ts` for complete working examples.
|
||||
@@ -2,6 +2,7 @@ import { Application as ExpressApp } from 'express'
|
||||
import { Container } from './Container'
|
||||
import { PluginManager } from './PluginManager'
|
||||
import { ConfigLoader } from './ConfigLoader'
|
||||
import { pluginRegistry } from './PluginFactory'
|
||||
import { ApplicationContext, AppConfig } from '../types/Context'
|
||||
import { Plugin, PluginRegistration } from '../types/Plugin'
|
||||
import { Logger } from '../types/Logger'
|
||||
@@ -305,7 +306,46 @@ export class ApplicationBuilder {
|
||||
}
|
||||
|
||||
// Register plugins
|
||||
if (this.pluginRegistrations.length > 0) {
|
||||
// Strategy: Config is primary source of truth, manual registration acts as override
|
||||
if (finalConfig.plugins && Object.keys(finalConfig.plugins).length > 0) {
|
||||
// Get plugin names that were manually registered
|
||||
const manualPluginNames = new Set(
|
||||
this.pluginRegistrations.map(p =>
|
||||
typeof p === 'object' && 'plugin' in p ? p.plugin.name : (p as any).name
|
||||
)
|
||||
)
|
||||
|
||||
// Validate: Manually registered plugins should also be declared in config
|
||||
// (This ensures config is the single source of truth for what's enabled)
|
||||
for (const pluginName of manualPluginNames) {
|
||||
if (!finalConfig.plugins[pluginName]) {
|
||||
console.warn(
|
||||
`[NSK] Warning: Plugin '${pluginName}' is manually registered but not declared in config. ` +
|
||||
`Consider adding it to armcorc.json for consistency.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-load plugins from config, excluding ones manually registered
|
||||
const pluginsToAutoLoad: Record<string, any> = {}
|
||||
for (const [name, config] of Object.entries(finalConfig.plugins)) {
|
||||
if (!manualPluginNames.has(name)) {
|
||||
pluginsToAutoLoad[name] = config
|
||||
}
|
||||
}
|
||||
|
||||
// Load non-overridden plugins from config
|
||||
if (Object.keys(pluginsToAutoLoad).length > 0) {
|
||||
const autoLoadedPlugins = pluginRegistry.createFromConfig(pluginsToAutoLoad)
|
||||
application.plugins(autoLoadedPlugins)
|
||||
}
|
||||
|
||||
// Add manually registered plugins (these override config)
|
||||
if (this.pluginRegistrations.length > 0) {
|
||||
application.plugins(this.pluginRegistrations)
|
||||
}
|
||||
} else if (this.pluginRegistrations.length > 0) {
|
||||
// No config plugins, but manual plugins exist - use manual plugins
|
||||
application.plugins(this.pluginRegistrations)
|
||||
}
|
||||
|
||||
|
||||
100
v2/core/PluginFactory.ts
Normal file
100
v2/core/PluginFactory.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Plugin, PluginRegistration } from '../types/Plugin'
|
||||
import { PluginConfig } from '../types/Context'
|
||||
|
||||
/**
|
||||
* Plugin factory function type
|
||||
* Takes plugin config and returns a plugin instance
|
||||
*/
|
||||
export type PluginFactory = (config?: PluginConfig) => Plugin | PluginRegistration
|
||||
|
||||
/**
|
||||
* Global registry for plugin factories
|
||||
* Maps plugin names (from config) to their factory functions
|
||||
*/
|
||||
class PluginFactoryRegistry {
|
||||
private factories = new Map<string, PluginFactory>()
|
||||
|
||||
/**
|
||||
* Register a plugin factory
|
||||
* @param name - Plugin name as it appears in config (e.g., 'logger', 'database')
|
||||
* @param factory - Factory function that creates the plugin
|
||||
*/
|
||||
register(name: string, factory: PluginFactory): void {
|
||||
this.factories.set(name, factory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a plugin factory by name
|
||||
*/
|
||||
get(name: string): PluginFactory | undefined {
|
||||
return this.factories.get(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a factory is registered
|
||||
*/
|
||||
has(name: string): boolean {
|
||||
return this.factories.has(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered plugin names
|
||||
*/
|
||||
getRegisteredNames(): string[] {
|
||||
return Array.from(this.factories.keys())
|
||||
}
|
||||
|
||||
/**
|
||||
* Create plugin instances from config
|
||||
* @param pluginsConfig - Plugin configuration from AppConfig
|
||||
* @returns Array of plugin instances
|
||||
*/
|
||||
createFromConfig(pluginsConfig: Record<string, PluginConfig>): Array<Plugin | PluginRegistration> {
|
||||
const plugins: Array<Plugin | PluginRegistration> = []
|
||||
|
||||
for (const [name, config] of Object.entries(pluginsConfig)) {
|
||||
// Skip if explicitly disabled
|
||||
if (config.enabled === false) {
|
||||
continue
|
||||
}
|
||||
|
||||
const factory = this.get(name)
|
||||
if (!factory) {
|
||||
console.warn(`[NSK] Plugin factory not found for: ${name}. Skipping...`)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
const plugin = factory(config)
|
||||
plugins.push(plugin)
|
||||
} catch (error) {
|
||||
console.error(`[NSK] Failed to create plugin '${name}':`, error)
|
||||
throw new Error(
|
||||
`Plugin creation failed for '${name}': ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return plugins
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Global plugin factory registry instance
|
||||
*/
|
||||
export const pluginRegistry = new PluginFactoryRegistry()
|
||||
|
||||
/**
|
||||
* Register a plugin factory (convenience function)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // In your plugin file:
|
||||
* registerPluginFactory('logger', (config) => createLoggerPlugin(config))
|
||||
* ```
|
||||
*/
|
||||
export function registerPluginFactory(name: string, factory: PluginFactory): void {
|
||||
pluginRegistry.register(name, factory)
|
||||
}
|
||||
72
v2/examples/armco.config.full.json
Normal file
72
v2/examples/armco.config.full.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"appName": "my-service",
|
||||
"env": "production",
|
||||
"plugins": {
|
||||
"logger": {
|
||||
"enabled": true,
|
||||
"adapter": "winston",
|
||||
"level": "info",
|
||||
"format": "json"
|
||||
},
|
||||
"database": {
|
||||
"enabled": true,
|
||||
"adapter": "mongoose",
|
||||
"uri": "mongodb://localhost:27017/mydb",
|
||||
"options": {
|
||||
"maxPoolSize": 10,
|
||||
"serverSelectionTimeoutMS": 5000
|
||||
}
|
||||
},
|
||||
"cache": {
|
||||
"enabled": true,
|
||||
"adapter": "redis",
|
||||
"uri": "redis://localhost:6379",
|
||||
"ttl": 3600,
|
||||
"keyPrefix": "myapp:"
|
||||
},
|
||||
"scheduler": {
|
||||
"enabled": true,
|
||||
"adapter": "node-cron",
|
||||
"timezone": "UTC",
|
||||
"tasks": []
|
||||
},
|
||||
"telemetry": {
|
||||
"enabled": true,
|
||||
"serviceName": "my-service",
|
||||
"serviceVersion": "1.0.0",
|
||||
"otlpEndpoint": "http://localhost:4318",
|
||||
"exporters": ["otlp"],
|
||||
"enableTracing": true,
|
||||
"enableMetrics": true,
|
||||
"autoInstrumentation": true,
|
||||
"sampleRate": 1.0
|
||||
}
|
||||
},
|
||||
"middlewares": {
|
||||
"helmet": {
|
||||
"enabled": true
|
||||
},
|
||||
"cors": {
|
||||
"enabled": true,
|
||||
"origin": "*"
|
||||
},
|
||||
"rateLimit": {
|
||||
"enabled": true,
|
||||
"windowMs": 900000,
|
||||
"max": 100
|
||||
}
|
||||
},
|
||||
"health": {
|
||||
"enabled": true,
|
||||
"endpoint": "/health"
|
||||
},
|
||||
"metrics": {
|
||||
"enabled": true,
|
||||
"endpoint": "/metrics",
|
||||
"collector": "prometheus"
|
||||
},
|
||||
"server": {
|
||||
"port": 3000,
|
||||
"host": "0.0.0.0"
|
||||
}
|
||||
}
|
||||
201
v2/examples/config-driven-initialization.ts
Normal file
201
v2/examples/config-driven-initialization.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Config-Driven Initialization Example
|
||||
*
|
||||
* This example demonstrates how to initialize NSK v2 purely from configuration
|
||||
* without explicit plugin registration in code. This enables:
|
||||
* - Restart-based reconfiguration (no rebuild required)
|
||||
* - Config-as-code or cloud config integration
|
||||
* - Uniform JSON input for all plugins
|
||||
*/
|
||||
|
||||
import express from 'express'
|
||||
import { Application } from '../core/Application'
|
||||
|
||||
// Import plugins to trigger factory registration
|
||||
// This is required once at app entry point
|
||||
import '../plugins/logger'
|
||||
import '../plugins/database'
|
||||
import '../plugins/cache'
|
||||
import '../plugins/scheduler'
|
||||
import '../plugins/telemetry'
|
||||
|
||||
/**
|
||||
* Example 1: Minimal - Auto-load from armcorc.json
|
||||
*
|
||||
* If you have armcorc.json in your project root:
|
||||
* {
|
||||
* "appName": "my-service",
|
||||
* "env": "production",
|
||||
* "plugins": {
|
||||
* "logger": {
|
||||
* "enabled": true,
|
||||
* "level": "info",
|
||||
* "format": "json"
|
||||
* },
|
||||
* "database": {
|
||||
* "enabled": true,
|
||||
* "adapter": "mongoose",
|
||||
* "uri": "mongodb://localhost:27017/mydb"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
async function minimalExample() {
|
||||
const app = express()
|
||||
|
||||
// This is all you need - NSK auto-loads plugins from armcorc.json
|
||||
const nsk = await Application.create(app).build()
|
||||
|
||||
// Access services from container
|
||||
const logger = nsk.getContainer().resolve('logger')
|
||||
const database = nsk.getContainer().tryResolve('database')
|
||||
|
||||
logger.info('Application initialized from config')
|
||||
|
||||
return nsk
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 2: Explicit config object (for cloud config integration)
|
||||
*/
|
||||
async function explicitConfigExample() {
|
||||
const app = express()
|
||||
|
||||
// Load config from cloud config store, env vars, etc.
|
||||
const config = {
|
||||
appName: 'auth-core',
|
||||
env: process.env.NODE_ENV || 'development',
|
||||
plugins: {
|
||||
logger: {
|
||||
enabled: true,
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: process.env.NODE_ENV === 'production' ? 'json' : 'pretty',
|
||||
},
|
||||
database: {
|
||||
enabled: true,
|
||||
adapter: 'mongoose',
|
||||
uri: process.env.MONGODB_URI!,
|
||||
},
|
||||
cache: {
|
||||
enabled: true,
|
||||
adapter: 'redis',
|
||||
uri: process.env.REDIS_URI,
|
||||
ttl: 3600,
|
||||
keyPrefix: 'auth:',
|
||||
},
|
||||
scheduler: {
|
||||
enabled: true,
|
||||
timezone: 'UTC',
|
||||
tasks: [
|
||||
{
|
||||
name: 'cleanup-sessions',
|
||||
schedule: '0 2 * * *', // 2 AM daily
|
||||
handler: async () => {
|
||||
// Cleanup logic
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
telemetry: {
|
||||
enabled: process.env.ENABLE_TELEMETRY === 'true',
|
||||
serviceName: 'auth-core',
|
||||
serviceVersion: '1.0.0',
|
||||
otlpEndpoint: process.env.OTLP_ENDPOINT,
|
||||
exporters: ['otlp'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
.withConfig(config)
|
||||
.build()
|
||||
|
||||
return nsk
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 3: Mixed approach (config + manual services)
|
||||
*
|
||||
* Use config for plugins, but manually register custom services
|
||||
*/
|
||||
async function mixedExample() {
|
||||
const app = express()
|
||||
|
||||
// Custom services not managed by plugins
|
||||
class UserRepository {
|
||||
async findById(id: string) {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
// Config handles plugins
|
||||
.withConfig('./armcorc.json')
|
||||
// Manual services registration
|
||||
.withServices((container) => {
|
||||
container.singleton('userRepo', new UserRepository())
|
||||
})
|
||||
.build()
|
||||
|
||||
// Access both plugin services and custom services
|
||||
const logger = nsk.getContainer().resolve('logger')
|
||||
const userRepo = nsk.getContainer().resolve('userRepo')
|
||||
|
||||
return nsk
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 4: Socket plugin (special case)
|
||||
*
|
||||
* Socket plugin requires HttpServer instance, so it can't be purely config-driven.
|
||||
* You need to register it manually or use a hybrid approach.
|
||||
*/
|
||||
async function socketExample() {
|
||||
const app = express()
|
||||
const http = require('http')
|
||||
const server = http.createServer(app)
|
||||
|
||||
const { createSocketPlugin } = await import('../plugins/socket')
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
// Auto-load other plugins from config
|
||||
.withConfig('./armcorc.json')
|
||||
// Manually add socket plugin (requires server instance)
|
||||
.plugin(createSocketPlugin(server, {
|
||||
enabled: true,
|
||||
cors: { origin: '*' },
|
||||
}))
|
||||
.build()
|
||||
|
||||
server.listen(3000)
|
||||
return nsk
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 5: Runtime config updates (restart pattern)
|
||||
*
|
||||
* For runtime reconfiguration without rebuild:
|
||||
* 1. Update armcorc.json (manually, API, or config management system)
|
||||
* 2. Send SIGTERM to process
|
||||
* 3. Process manager (PM2, K8s, etc.) restarts the app
|
||||
* 4. New config is loaded on startup
|
||||
*/
|
||||
async function runtimeReconfigExample() {
|
||||
const app = express()
|
||||
|
||||
// On every restart, fresh config is loaded
|
||||
const nsk = await Application.create(app).build()
|
||||
|
||||
// Graceful shutdown already handled by NSK
|
||||
// Just ensure process manager restarts on SIGTERM
|
||||
|
||||
return nsk
|
||||
}
|
||||
|
||||
export {
|
||||
minimalExample,
|
||||
explicitConfigExample,
|
||||
mixedExample,
|
||||
socketExample,
|
||||
runtimeReconfigExample,
|
||||
}
|
||||
187
v2/examples/global-logger-usage.ts
Normal file
187
v2/examples/global-logger-usage.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Global Logger Usage Example
|
||||
*
|
||||
* This demonstrates how the logger is automatically available globally
|
||||
* once NSK is initialized, without requiring imports.
|
||||
*
|
||||
* This matches the legacy NSK v1 behavior.
|
||||
*/
|
||||
|
||||
import express from 'express'
|
||||
import { Application } from '../index'
|
||||
|
||||
/**
|
||||
* Initialize NSK with logger plugin
|
||||
*/
|
||||
async function initializeApp() {
|
||||
const app = express()
|
||||
|
||||
// Initialize NSK with logger from config
|
||||
const nsk = await Application.create(app)
|
||||
.withConfig({
|
||||
appName: 'global-logger-demo',
|
||||
plugins: {
|
||||
logger: {
|
||||
enabled: true,
|
||||
level: 'info',
|
||||
format: 'pretty',
|
||||
},
|
||||
},
|
||||
})
|
||||
.build()
|
||||
|
||||
// After NSK init, logger is available globally
|
||||
// No import needed!
|
||||
logger.info('✅ NSK initialized - logger is now globally available')
|
||||
|
||||
return { app, nsk }
|
||||
}
|
||||
|
||||
/**
|
||||
* Example route handler - no logger import needed!
|
||||
*/
|
||||
function setupRoutes(app: express.Application) {
|
||||
app.get('/api/users/:id', (req, res) => {
|
||||
// Direct usage: logger is globally available
|
||||
logger.info('User request received', { userId: req.params.id })
|
||||
|
||||
try {
|
||||
// Simulate some work
|
||||
const user = { id: req.params.id, name: 'John Doe' }
|
||||
|
||||
logger.info('User found', { user })
|
||||
res.json(user)
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch user', { error, userId: req.params.id })
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/api/auth/login', (req, res) => {
|
||||
logger.info('Login attempt', { username: req.body.username })
|
||||
|
||||
// Login logic...
|
||||
|
||||
logger.warn('Failed login attempt', {
|
||||
username: req.body.username,
|
||||
ip: req.ip,
|
||||
})
|
||||
|
||||
res.status(401).json({ error: 'Invalid credentials' })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Example service class - no logger import!
|
||||
*/
|
||||
class UserService {
|
||||
async getUser(id: string) {
|
||||
// Logger is globally available in any file
|
||||
logger.debug('UserService.getUser called', { userId: id })
|
||||
|
||||
try {
|
||||
// Database call...
|
||||
const user = { id, name: 'Jane Doe' }
|
||||
|
||||
logger.info('User retrieved from database', { userId: id })
|
||||
return user
|
||||
} catch (error) {
|
||||
logger.error('Database query failed', { error, userId: id })
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async createUser(data: any) {
|
||||
logger.info('Creating new user', { email: data.email })
|
||||
|
||||
// Validation...
|
||||
// Database insert...
|
||||
|
||||
logger.info('User created successfully', { userId: 'new-id' })
|
||||
return { id: 'new-id', ...data }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example middleware - no logger import!
|
||||
*/
|
||||
function requestLogger(req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const start = Date.now()
|
||||
|
||||
// Log incoming request
|
||||
logger.info('Incoming request', {
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
ip: req.ip,
|
||||
})
|
||||
|
||||
// Log response
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start
|
||||
|
||||
logger.info('Request completed', {
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
statusCode: res.statusCode,
|
||||
duration: `${duration}ms`,
|
||||
})
|
||||
})
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* Example error handler - no logger import!
|
||||
*/
|
||||
function errorHandler(
|
||||
err: Error,
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction
|
||||
) {
|
||||
// Global logger available in error handlers too
|
||||
logger.error('Unhandled error', {
|
||||
error: err.message,
|
||||
stack: err.stack,
|
||||
path: req.path,
|
||||
method: req.method,
|
||||
})
|
||||
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
|
||||
/**
|
||||
* Main application
|
||||
*/
|
||||
async function main() {
|
||||
const { app, nsk } = await initializeApp()
|
||||
|
||||
// Setup middleware
|
||||
app.use(express.json())
|
||||
app.use(requestLogger)
|
||||
|
||||
// Setup routes
|
||||
setupRoutes(app)
|
||||
|
||||
// Setup error handler
|
||||
app.use(errorHandler)
|
||||
|
||||
// Start server
|
||||
const PORT = 3000
|
||||
app.listen(PORT, () => {
|
||||
// Logger available everywhere!
|
||||
logger.info(`🚀 Server started on port ${PORT}`)
|
||||
logger.info('Logger is globally available in all files')
|
||||
logger.info('No imports required!')
|
||||
})
|
||||
}
|
||||
|
||||
// TypeScript knows about global logger automatically
|
||||
// because of globals.d.ts exported by NSK
|
||||
main().catch((error) => {
|
||||
// Even in catch blocks, logger is available
|
||||
console.error('Failed to start application:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
export { UserService, requestLogger, errorHandler }
|
||||
193
v2/examples/hybrid-config-and-manual.ts
Normal file
193
v2/examples/hybrid-config-and-manual.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Hybrid Config + Manual Plugin Registration Example
|
||||
*
|
||||
* This demonstrates the recommended pattern where:
|
||||
* 1. Config (armcorc.json) is the primary source of truth
|
||||
* 2. Manual plugin registration acts as override for specific plugins
|
||||
* 3. Both approaches work together seamlessly
|
||||
*/
|
||||
|
||||
import express from 'express'
|
||||
import http from 'http'
|
||||
import { Application, createSocketPlugin, createLoggerPlugin } from '../index'
|
||||
|
||||
/**
|
||||
* Example 1: Config-first with manual override
|
||||
*
|
||||
* Use case: Most plugins from config, but need custom logger instance
|
||||
*/
|
||||
async function configWithManualOverride() {
|
||||
const app = express()
|
||||
|
||||
// armcorc.json contains:
|
||||
// {
|
||||
// "appName": "my-service",
|
||||
// "plugins": {
|
||||
// "logger": { "enabled": true, "level": "info" }, <- Will be overridden
|
||||
// "database": { "enabled": true, "uri": "..." }, <- Auto-loaded
|
||||
// "cache": { "enabled": true, "adapter": "redis" } <- Auto-loaded
|
||||
// }
|
||||
// }
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
// Config loaded from armcorc.json (database, cache auto-loaded)
|
||||
.plugin(createLoggerPlugin({
|
||||
// This overrides the logger config from armcorc.json
|
||||
level: 'debug', // Override: use debug instead of info
|
||||
format: 'pretty',
|
||||
// Custom transports, filters, etc.
|
||||
}))
|
||||
.build()
|
||||
|
||||
// Result:
|
||||
// - logger: Manually registered instance (overrides config)
|
||||
// - database: Auto-loaded from config
|
||||
// - cache: Auto-loaded from config
|
||||
|
||||
return nsk
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 2: Socket plugin special case
|
||||
*
|
||||
* Socket plugin requires HttpServer, so it must be manually registered
|
||||
* but other plugins come from config
|
||||
*/
|
||||
async function socketWithConfig() {
|
||||
const app = express()
|
||||
const server = http.createServer(app)
|
||||
|
||||
// armcorc.json:
|
||||
// {
|
||||
// "appName": "chat-service",
|
||||
// "plugins": {
|
||||
// "logger": { "enabled": true },
|
||||
// "database": { "enabled": true, "uri": "..." },
|
||||
// "cache": { "enabled": true },
|
||||
// "socket": { "enabled": true, "cors": { "origin": "*" } } <- Declared in config
|
||||
// }
|
||||
// }
|
||||
|
||||
const nsk = await Application.create(app)
|
||||
// Socket plugin must be manually registered (requires server instance)
|
||||
.plugin(createSocketPlugin(server, {
|
||||
// Config from armcorc.json can be used here if needed
|
||||
cors: { origin: '*' },
|
||||
}))
|
||||
.build()
|
||||
|
||||
// Result:
|
||||
// - logger: Auto-loaded from config
|
||||
// - database: Auto-loaded from config
|
||||
// - cache: Auto-loaded from config
|
||||
// - socket: Manually registered (overrides config, but config declares it's enabled)
|
||||
|
||||
server.listen(3000)
|
||||
return nsk
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 3: Runtime plugin selection
|
||||
*
|
||||
* Load plugins from config, but conditionally override based on environment
|
||||
*/
|
||||
async function runtimeOverride() {
|
||||
const app = express()
|
||||
|
||||
const builder = Application.create(app)
|
||||
|
||||
// In development, override logger with custom console logger
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
builder.plugin(createLoggerPlugin({
|
||||
level: 'debug',
|
||||
format: 'pretty',
|
||||
}))
|
||||
}
|
||||
|
||||
// All other plugins from config
|
||||
const nsk = await builder.build()
|
||||
|
||||
return nsk
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 4: Progressive enhancement
|
||||
*
|
||||
* Start with basic config, add more plugins manually as needed
|
||||
*/
|
||||
async function progressiveEnhancement() {
|
||||
const app = express()
|
||||
|
||||
// armcorc.json has minimal config:
|
||||
// {
|
||||
// "appName": "my-service",
|
||||
// "plugins": {
|
||||
// "logger": { "enabled": true }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Build with additional plugins not in config
|
||||
const nsk = await Application.create(app)
|
||||
// Logger comes from config
|
||||
// Add more plugins manually as your app grows
|
||||
// (though ideally these should be in config too)
|
||||
.build()
|
||||
|
||||
return nsk
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 5: Best practice pattern (RECOMMENDED)
|
||||
*
|
||||
* All plugins declared in config, manual registration ONLY for:
|
||||
* 1. Plugins requiring runtime instances (e.g., HttpServer for socket)
|
||||
* 2. Environment-specific overrides
|
||||
*/
|
||||
async function bestPracticePattern() {
|
||||
const app = express()
|
||||
const server = http.createServer(app)
|
||||
|
||||
// armcorc.json declares ALL plugins:
|
||||
// {
|
||||
// "appName": "auth-core",
|
||||
// "plugins": {
|
||||
// "logger": { "enabled": true, "level": "info" },
|
||||
// "database": { "enabled": true, "uri": "..." },
|
||||
// "cache": { "enabled": true, "adapter": "redis" },
|
||||
// "scheduler": { "enabled": true, "timezone": "UTC" },
|
||||
// "telemetry": { "enabled": true, "serviceName": "auth-core" },
|
||||
// "socket": { "enabled": true } <- Declared but must be manually registered
|
||||
// }
|
||||
// }
|
||||
|
||||
const builder = Application.create(app)
|
||||
|
||||
// ONLY manually register socket (requires server instance)
|
||||
builder.plugin(createSocketPlugin(server))
|
||||
|
||||
// Optional: Environment-specific overrides
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
builder.plugin(createLoggerPlugin({
|
||||
level: 'debug',
|
||||
format: 'pretty',
|
||||
}))
|
||||
}
|
||||
|
||||
const nsk = await builder.build()
|
||||
|
||||
// Result:
|
||||
// - Config is single source of truth for what's enabled
|
||||
// - Code only handles runtime-specific requirements
|
||||
// - Easy to see what's running by looking at armcorc.json
|
||||
|
||||
server.listen(3000)
|
||||
return nsk
|
||||
}
|
||||
|
||||
export {
|
||||
configWithManualOverride,
|
||||
socketWithConfig,
|
||||
runtimeOverride,
|
||||
progressiveEnhancement,
|
||||
bestPracticePattern,
|
||||
}
|
||||
75
v2/examples/test-config-driven.ts
Normal file
75
v2/examples/test-config-driven.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Quick test script to verify config-driven initialization
|
||||
*
|
||||
* Usage:
|
||||
* npx tsx v2/examples/test-config-driven.ts
|
||||
*/
|
||||
|
||||
import express from 'express'
|
||||
import { Application } from '../index'
|
||||
|
||||
async function test() {
|
||||
console.log('\n🧪 Testing Config-Driven Initialization\n')
|
||||
|
||||
const app = express()
|
||||
|
||||
// Test config
|
||||
const config = {
|
||||
appName: 'test-app',
|
||||
env: 'development',
|
||||
plugins: {
|
||||
logger: {
|
||||
enabled: true,
|
||||
level: 'info',
|
||||
format: 'pretty',
|
||||
},
|
||||
cache: {
|
||||
enabled: true,
|
||||
adapter: 'memory',
|
||||
ttl: 60,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
console.log('📝 Config:', JSON.stringify(config, null, 2))
|
||||
console.log('\n🚀 Building application with config-driven initialization...\n')
|
||||
|
||||
try {
|
||||
const nsk = await Application.create(app)
|
||||
.withConfig(config)
|
||||
.build()
|
||||
|
||||
console.log('✅ Application built successfully!')
|
||||
|
||||
// Test that plugins were loaded
|
||||
const logger = nsk.getContainer().tryResolve('logger')
|
||||
const cache = nsk.getContainer().tryResolve('cache')
|
||||
|
||||
if (logger) {
|
||||
console.log('✅ Logger plugin loaded')
|
||||
logger.info('Logger is working!')
|
||||
} else {
|
||||
console.log('❌ Logger plugin NOT loaded')
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
console.log('✅ Cache plugin loaded')
|
||||
await cache.set('test', 'hello', 10)
|
||||
const value = await cache.get('test')
|
||||
console.log(` Cache test: set "test" = "hello", get "test" = "${value}"`)
|
||||
} else {
|
||||
console.log('❌ Cache plugin NOT loaded')
|
||||
}
|
||||
|
||||
// Shutdown
|
||||
await nsk.shutdown()
|
||||
console.log('\n✅ Config-driven initialization test passed!')
|
||||
process.exit(0)
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ Config-driven initialization test failed:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
test()
|
||||
44
v2/globals.d.ts
vendored
Normal file
44
v2/globals.d.ts
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Global namespace augmentation for NSK
|
||||
*
|
||||
* This file augments the global namespace to provide first-class
|
||||
* access to NSK services without requiring imports.
|
||||
*
|
||||
* When you install NSK in your project, these globals are automatically
|
||||
* available with full TypeScript type safety.
|
||||
*
|
||||
* Usage:
|
||||
* ```typescript
|
||||
* // No imports needed!
|
||||
* logger.info('Request received')
|
||||
* logger.error('Something went wrong', { error })
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { Logger } from './types/Logger'
|
||||
|
||||
declare global {
|
||||
/**
|
||||
* Global logger instance
|
||||
*
|
||||
* Automatically injected by NSK Logger Plugin.
|
||||
* Available everywhere in your application without imports.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* logger.info('User logged in', { userId: '123' })
|
||||
* logger.warn('Rate limit approaching', { count: 95 })
|
||||
* logger.error('Database connection failed', { error })
|
||||
* ```
|
||||
*/
|
||||
var logger: Logger
|
||||
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
logger: Logger
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This export is required to make this a module
|
||||
export {}
|
||||
@@ -1,18 +1,26 @@
|
||||
/**
|
||||
* @armco/node-starter-kit v2
|
||||
* Modern plugin-based Node.js application framework
|
||||
*
|
||||
* Global types are automatically available via globals.d.ts
|
||||
*/
|
||||
|
||||
// Side-effect import to ensure global type augmentation is loaded
|
||||
// This triggers TypeScript to process globals.d.ts
|
||||
import type {} from './globals'
|
||||
|
||||
// Core exports
|
||||
export { Application, ApplicationBuilder } from './core/Application'
|
||||
export { Container } from './core/Container'
|
||||
export { PluginManager } from './core/PluginManager'
|
||||
export { ConfigLoader, defineConfig } from './core/ConfigLoader'
|
||||
export { pluginRegistry, registerPluginFactory, type PluginFactory } from './core/PluginFactory'
|
||||
|
||||
// Type exports
|
||||
export * from './types'
|
||||
|
||||
// Plugin exports
|
||||
// Importing these also triggers factory registration for config-driven initialization
|
||||
export { LoggerPlugin, createLoggerPlugin } from './plugins/logger'
|
||||
export { DatabasePlugin, createDatabasePlugin } from './plugins/database'
|
||||
export { SocketPlugin, createSocketPlugin } from './plugins/socket'
|
||||
|
||||
4
v2/plugins/cache/index.ts
vendored
4
v2/plugins/cache/index.ts
vendored
@@ -5,6 +5,7 @@ import { MemoryAdapter } from './MemoryAdapter'
|
||||
import { RedisAdapter } from './RedisAdapter'
|
||||
import { Logger } from '../../types/Logger'
|
||||
import { HealthStatus } from '../../types/Plugin'
|
||||
import { registerPluginFactory } from '../../core/PluginFactory'
|
||||
|
||||
/**
|
||||
* Cache plugin for data caching
|
||||
@@ -203,3 +204,6 @@ export class CachePlugin extends BasePlugin<CacheConfig> {
|
||||
export function createCachePlugin(config: CacheConfig = {}): CachePlugin {
|
||||
return new CachePlugin(config)
|
||||
}
|
||||
|
||||
// Auto-register plugin factory
|
||||
registerPluginFactory('cache', (config) => createCachePlugin(config as unknown as CacheConfig))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BasePlugin, HealthStatus } from '../../types/Plugin'
|
||||
import { ApplicationContext } from '../../types/Context'
|
||||
import { MongooseAdapter, MongooseConfig } from './MongooseAdapter'
|
||||
import { registerPluginFactory } from '../../core/PluginFactory'
|
||||
|
||||
export interface DatabaseConfig {
|
||||
adapter?: 'mongoose'
|
||||
@@ -96,3 +97,6 @@ export class DatabasePlugin extends BasePlugin<DatabaseConfig> {
|
||||
export function createDatabasePlugin(config: DatabaseConfig): DatabasePlugin {
|
||||
return new DatabasePlugin(config)
|
||||
}
|
||||
|
||||
// Auto-register plugin factory
|
||||
registerPluginFactory('database', (config) => createDatabasePlugin(config as unknown as DatabaseConfig))
|
||||
|
||||
@@ -2,6 +2,7 @@ import { BasePlugin } from '../../types/Plugin'
|
||||
import { ApplicationContext } from '../../types/Context'
|
||||
import { LoggerConfig } from '../../types/Logger'
|
||||
import { WinstonAdapter } from './WinstonAdapter'
|
||||
import { registerPluginFactory } from '../../core/PluginFactory'
|
||||
|
||||
/**
|
||||
* Logger Plugin
|
||||
@@ -36,6 +37,10 @@ export class LoggerPlugin extends BasePlugin<LoggerConfig> {
|
||||
// Register logger in container
|
||||
context.container.singleton('logger', this.logger)
|
||||
|
||||
// Make logger available globally (first-class access)
|
||||
// This allows direct usage: logger.info() without imports
|
||||
;(global as any).logger = this.logger
|
||||
|
||||
this.logger.info(`Logger plugin installed: ${config.adapter || 'winston'}`)
|
||||
}
|
||||
|
||||
@@ -54,3 +59,6 @@ export class LoggerPlugin extends BasePlugin<LoggerConfig> {
|
||||
export function createLoggerPlugin(config?: LoggerConfig): LoggerPlugin {
|
||||
return new LoggerPlugin(config)
|
||||
}
|
||||
|
||||
// Auto-register plugin factory
|
||||
registerPluginFactory('logger', (config) => createLoggerPlugin(config as unknown as LoggerConfig))
|
||||
|
||||
@@ -4,6 +4,7 @@ import { SchedulerAdapter, SchedulerConfig } from '../../types/Scheduler'
|
||||
import { NodeCronAdapter } from './NodeCronAdapter'
|
||||
import { Logger } from '../../types/Logger'
|
||||
import { HealthStatus } from '../../types/Plugin'
|
||||
import { registerPluginFactory } from '../../core/PluginFactory'
|
||||
|
||||
/**
|
||||
* Scheduler plugin for task scheduling
|
||||
@@ -268,3 +269,6 @@ export class SchedulerPlugin extends BasePlugin<SchedulerConfig> {
|
||||
export function createSchedulerPlugin(config: SchedulerConfig = {}): SchedulerPlugin {
|
||||
return new SchedulerPlugin(config)
|
||||
}
|
||||
|
||||
// Auto-register plugin factory
|
||||
registerPluginFactory('scheduler', (config) => createSchedulerPlugin(config as unknown as SchedulerConfig))
|
||||
|
||||
@@ -4,6 +4,7 @@ import { OpenTelemetryConfig, OpenTelemetryAdapter } from '../../types/OpenTelem
|
||||
import { OtelAdapter } from './OpenTelemetryAdapter'
|
||||
import { Logger } from '../../types/Logger'
|
||||
import { HealthStatus } from '../../types/Plugin'
|
||||
import { registerPluginFactory } from '../../core/PluginFactory'
|
||||
|
||||
/**
|
||||
* OpenTelemetry plugin for distributed tracing and metrics
|
||||
@@ -226,3 +227,7 @@ export class OpenTelemetryPlugin extends BasePlugin<OpenTelemetryConfig> {
|
||||
export function createOpenTelemetryPlugin(config: OpenTelemetryConfig): OpenTelemetryPlugin {
|
||||
return new OpenTelemetryPlugin(config)
|
||||
}
|
||||
|
||||
// Auto-register plugin factory
|
||||
registerPluginFactory('opentelemetry', (config) => createOpenTelemetryPlugin(config as unknown as OpenTelemetryConfig))
|
||||
registerPluginFactory('telemetry', (config) => createOpenTelemetryPlugin(config as unknown as OpenTelemetryConfig))
|
||||
|
||||
Reference in New Issue
Block a user