Skip to content

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 disk

Snapshots & 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 emitted

Events

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 = 5000

Security 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.json

Validation

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

Released under the MIT License.