Config Hot-Reload
The Config module provides live configuration management with file watching, validation, snapshots, and automatic rollback.
Overview
Update configuration without restarts:
- File Watching - Detect changes automatically
- Debouncing - Prevent reload storms
- Validation - Zod schema validation before applying
- Snapshots - Keep configuration history
- Rollback - Revert to previous versions
Installation
typescript
import {
ConfigManager,
createConfigManager,
FileWatcher,
} from '@aiversum/aiv-agents/config';Quick Start
typescript
import { z } from 'zod';
// Define your config schema
const AppConfigSchema = z.object({
port: z.number().int().min(1).max(65535),
debug: z.boolean(),
api: z.object({
timeout: z.number(),
retries: z.number(),
}),
});
type AppConfig = z.infer<typeof AppConfigSchema>;
// Create manager
const config = createConfigManager<AppConfig>(
{
configPath: './config.json',
hotReload: { enabled: true, debounceMs: 300 },
},
AppConfigSchema
);
// Load and start watching
await config.load();
await config.startWatching();
// React to changes
config.on('config:change', ({ changes, config }) => {
console.log('Config changed:', changes);
// Apply changes to your app
if (changes.some(c => c.path === 'port')) {
restartServer(config.port);
}
});Configuration Options
typescript
interface ConfigManagerConfig {
// File location
configPath?: string; // Path to config file
format?: 'json' | 'yaml'; // File format
// Hot reload
hotReload: {
enabled: boolean; // Enable watching
debounceMs: number; // Debounce delay (default: 300)
validateBeforeApply: boolean; // Validate before applying
keepSnapshots: boolean; // Keep history
maxSnapshots: number; // Max snapshots to keep
autoRollback: boolean; // Rollback on error
watchPaths: string[]; // Additional paths to watch
};
// Environment
envPrefix?: string; // Env var prefix for overrides
// Logging
logLevel?: 'debug' | 'info' | 'warn' | 'error';
}Reading Configuration
typescript
// Get entire config
const cfg = config.get();
console.log(cfg.port);
// Get value at path
const timeout = config.getValue<number>('api.timeout');
// Check if loaded
if (!config.isLoaded()) {
await config.load();
}Updating Configuration
In-Memory Updates
typescript
// Set a value (in-memory only)
config.setValue('debug', true);
config.setValue('api.timeout', 5000);
// Validation happens automatically if schema is set
try {
config.setValue('port', -1); // Will throw
} catch (error) {
console.error('Invalid value:', error.message);
}Persisting Changes
typescript
// Save to file
await config.save();
// This triggers validation and writes to diskSnapshots & Rollback
Taking Snapshots
typescript
// Manual snapshot
const snapshot = config.takeSnapshot('Before migration');
console.log(snapshot.id); // UUID
// Snapshots are also taken automatically on load
// (if keepSnapshots is enabled)Listing Snapshots
typescript
const snapshots = config.getSnapshots();
// [
// { id: '...', createdAt: Date, description: '...' },
// ...
// ]Rolling Back
typescript
// Rollback to latest snapshot
await config.rollback();
// Rollback to specific snapshot
await config.rollback('snapshot-id-here');Auto-Rollback
When autoRollback is enabled, invalid configuration changes are automatically reverted:
typescript
const config = createConfigManager({
configPath: './config.json',
hotReload: {
enabled: true,
autoRollback: true,
},
}, ConfigSchema);
// If file is edited with invalid values:
// 1. Change detected
// 2. Validation fails
// 3. Previous snapshot restored automatically
// 4. 'config:rollback' event emittedEvents
typescript
config.on('config:load', ({ config, source }) => {
console.log('Config loaded from', source);
});
config.on('config:change', ({ changes, config }) => {
for (const change of changes) {
console.log(`${change.path}: ${change.oldValue} → ${change.newValue}`);
}
});
config.on('config:reload', ({ config, source, duration }) => {
console.log(`Config reloaded in ${duration}ms`);
});
config.on('config:invalid', ({ errors, source }) => {
console.error('Invalid config:', errors);
});
config.on('config:rollback', ({ fromSnapshot, reason }) => {
console.warn(`Rolled back to ${fromSnapshot.id}: ${reason}`);
});
config.on('watcher:change', ({ path, event }) => {
console.log(`File ${event}: ${path}`);
});
config.on('watcher:error', ({ path, error }) => {
console.error(`Watcher error for ${path}:`, error);
});File Watcher
Use the file watcher directly for custom scenarios:
typescript
import { createFileWatcher } from '@aiversum/aiv-agents/config';
const watcher = createFileWatcher({
debounceMs: 500,
recursive: false,
persistOnError: true,
});
// Watch multiple files
await watcher.watch([
'./config.json',
'./settings.yaml',
'./.env',
]);
watcher.on('change', ({ path }) => {
console.log(`File changed: ${path}`);
});
watcher.on('rename', ({ path }) => {
console.log(`File renamed/deleted: ${path}`);
});
// Stop watching
watcher.stop();Change Detection
Changes are detected using deep comparison:
typescript
config.on('config:change', ({ changes }) => {
for (const change of changes) {
console.log({
operation: change.operation, // 'add' | 'update' | 'remove'
path: change.path, // 'api.timeout'
oldValue: change.oldValue, // 3000
newValue: change.newValue, // 5000
timestamp: change.timestamp,
});
}
});YAML Support
typescript
const config = createConfigManager({
configPath: './config.yaml',
format: 'yaml',
hotReload: { enabled: true },
}, ConfigSchema);Environment Variable Overrides
typescript
const config = createConfigManager({
configPath: './config.json',
envPrefix: 'APP_',
});
// Environment variables like APP_PORT=3000 will override config
// APP_API_TIMEOUT=5000 → api.timeout = 5000Security Considerations
Path Validation
The module validates all paths:
- No path traversal (
..) - No null bytes
- Maximum path length (500 chars)
- Maximum paths count (20)
File Permissions
Configuration files with secrets should have restricted permissions:
bash
chmod 600 ~/.aiv/.env
chmod 600 ./config.jsonValidation
Always use Zod schemas for validation:
typescript
const SecureConfigSchema = z.object({
apiKey: z.string()
.min(10)
.refine(key => !key.includes(' '), 'No spaces allowed'),
endpoint: z.string().url(),
});API Reference
See the ConfigManager API Reference for complete documentation.
Testing
bash
# Run config module tests
npm test -- --grep "config"
# Test count: 58 tests