TYPESCRIPT

Type Safe Configuration Manager

Hierarchical configuration management with validation and environment support

TypeScriptConfigurationValidationEnvironment

Code

interface ConfigSchema {
  [key: string]: {
    type: 'string' | 'number' | 'boolean' | 'object' | 'array';
    required?: boolean;
    default?: any;
    validator?: (value: any) => boolean | string;
    envVar?: string;
    transform?: (value: any) => any;
  };
}

type ConfigFromSchema<T extends ConfigSchema> = {
  [K in keyof T]: T[K]['type'] extends 'string'
    ? string
    : T[K]['type'] extends 'number'
    ? number
    : T[K]['type'] extends 'boolean'
    ? boolean
    : T[K]['type'] extends 'object'
    ? Record<string, any>
    : T[K]['type'] extends 'array'
    ? any[]
    : never;
};

class ConfigManager<T extends ConfigSchema> {
  private config: Partial<ConfigFromSchema<T>> = {};
  private schema: T;
  private sources: Array<Record<string, any>> = [];
  
  constructor(schema: T) {
    this.schema = schema;
    this.loadDefaults();
  }
  
  // Load configuration from various sources
  loadFromObject(source: Record<string, any>): this {
    this.sources.push(source);
    this.mergeSource(source);
    return this;
  }
  
  loadFromEnv(prefix = ''): this {
    const envConfig: Record<string, any> = {};
    
    __TOKEN_144__ (const [key, def] of Object.entries(this.schema)) {
      const envKey = def.envVar || `${prefix}${key.toUpperCase()}`;
      const envValue = process.env[envKey];
      
      __TOKEN_150__ (envValue !== undefined) {
        envConfig[key] = this.parseEnvValue(envValue, def.type);
      }
    }
    
    return this.loadFromObject(envConfig);
  }
  
  loadFromJSON(json: string): this {
    try {
      const parsed = JSON.parse(json);
      return this.loadFromObject(parsed);
    } __TOKEN_160__ (error) {
      throw new Error(`Invalid JSON configuration: ${error}`);
    }
  }
  
  loadFromFile(filePath: string): this {
    // This would be implemented based on runtime (Node.js, Deno, etc.)
    // For example in Node.js:
    // const content = fs.readFileSync(filePath, 'utf-8');
    // return this.loadFromJSON(content);
    return this;
  }
  
  private parseEnvValue(value: string, type: ConfigSchema[string]['type']): any {
    switch (type) {
      case 'string':
        return value;
      case 'number':
        const num = Number(value);
        __TOKEN_171__ (isNaN(num)) throw new Error(`Invalid number: ${value}`);
        return num;
      case 'boolean':
        __TOKEN_175__ (value.toLowerCase() === 'true' || value === '1') return true;
        __TOKEN_177__ (value.toLowerCase() === 'false' || value === '0') return false;
        throw new Error(`Invalid boolean: ${value}`);
      case 'object':
        try {
          return JSON.parse(value);
        } catch {
          throw new Error(`Invalid JSON object: ${value}`);
        }
      case 'array':
        try {
          const parsed = JSON.parse(value);
          __TOKEN_188__ (!Array.isArray(parsed)) {
            throw new Error('Not an array');
          }
          return parsed;
        } catch {
          // Try comma-separated values
          return value.split(',').map(v => v.trim());
        }
    }
  }
  
  private loadDefaults(): void {
    __TOKEN_195__ (const [key, def] of Object.entries(this.schema)) {
      __TOKEN_199__ (def.default !== undefined) {
        this.config[key as keyof T] = def.default;
      }
    }
  }
  
  private mergeSource(source: Record<string, any>): void {
    __TOKEN_202__ (const [key, def] of Object.entries(this.schema)) {
      __TOKEN_206__ (source[key] !== undefined) {
        let value = source[key];
        
        // Apply transform if specified
        __TOKEN_208__ (def.transform) {
          value = def.transform(value);
        }
        
        // Validate type
        value = this.validateAndCast(key, value, def);
        
        // Custom validation
        __TOKEN_210__ (def.validator) {
          const validationResult = def.validator(value);
          __TOKEN_212__ (validationResult !== true) {
            throw new Error(`Validation failed for ${key}: ${validationResult}`);
          }
        }
        
        this.config[key as keyof T] = value;
      }
    }
  }
  
  private validateAndCast(key: string, value: any, def: ConfigSchema[string]): any {
    const expectedType = def.type;
    
    switch (expectedType) {
      case 'string':
        __TOKEN_219__ (typeof value !== 'string') {
          throw new Error(`Expected string for ${key}, got ${typeof value}`);
        }
        return value;
        
      case 'number':
        __TOKEN_224__ (typeof value === 'string') {
          const num = Number(value);
          __TOKEN_227__ (isNaN(num)) {
            throw new Error(`Invalid number for ${key}: ${value}`);
          }
          return num;
        }
        __TOKEN_231__ (typeof value !== 'number') {
          throw new Error(`Expected number for ${key}, got ${typeof value}`);
        }
        return value;
        
      case 'boolean':
        __TOKEN_236__ (typeof value === 'string') {
          __TOKEN_238__ (value.toLowerCase() === 'true' || value === '1') return true;
          __TOKEN_240__ (value.toLowerCase() === 'false' || value === '0') return false;
          throw new Error(`Invalid boolean for ${key}: ${value}`);
        }
        __TOKEN_244__ (typeof value !== 'boolean') {
          throw new Error(`Expected boolean for ${key}, got ${typeof value}`);
        }
        return value;
        
      case 'object':
        __TOKEN_249__ (typeof value !== 'object' || value === null || Array.isArray(value)) {
          throw new Error(`Expected object for ${key}, got ${typeof value}`);
        }
        return value;
        
      case 'array':
        __TOKEN_254__ (!Array.isArray(value)) {
          throw new Error(`Expected array for ${key}, got ${typeof value}`);
        }
        return value;
    }
  }
  
  // Get configuration value
  get<K extends keyof ConfigFromSchema<T>>(key: K): ConfigFromSchema<T>[K] {
    const value = this.config[key];
    
    __TOKEN_261__ (value === undefined) {
      const def = this.schema[key as string];
      __TOKEN_264__ (def.required) {
        throw new Error(`Required configuration missing: ${String(key)}`);
      }
      return def.default;
    }
    
    return value;
  }
  
  getAll(): ConfigFromSchema<T> {
    const result: any = {};
    
    __TOKEN_270__ (const key of Object.keys(this.schema)) {
      result[key] = this.get(key as any);
    }
    
    return result as ConfigFromSchema<T>;
  }
  
  // Check if configuration is valid
  validate(): { valid: boolean; errors: string[] } {
    const errors: string[] = [];
    
    __TOKEN_277__ (const [key, def] of Object.entries(this.schema)) {
      const value = this.config[key];
      
      __TOKEN_283__ (def.required && value === undefined) {
        errors.push(`Required field missing: ${key}`);
        continue;
      }
      
      __TOKEN_284__ (value !== undefined && def.validator) {
        const validationResult = def.validator(value);
        __TOKEN_286__ (validationResult !== true) {
          errors.push(`Validation failed for ${key}: ${validationResult}`);
        }
      }
    }
    
    return {
      valid: errors.length === 0,
      errors
    };
  }
  
  // Create nested configuration
  createNested<K extends keyof ConfigFromSchema<T>>(
    key: K,
    nestedSchema: ConfigSchema
  ): ConfigManager<any> {
    const nestedValue = this.get(key);
    
    __TOKEN_291__ (typeof nestedValue !== 'object' || nestedValue === null) {
      throw new Error(`Cannot create nested config from non-object value: ${String(key)}`);
    }
    
    const nestedConfig = new ConfigManager(nestedSchema);
    nestedConfig.loadFromObject(nestedValue as Record<string, any>);
    
    return nestedConfig;
  }
  
  // Override configuration at runtime
  set<K extends keyof ConfigFromSchema<T>>(key: K, value: ConfigFromSchema<T>[K]): void {
    const def = this.schema[key as string];
    __TOKEN_301__ (!def) {
      throw new Error(`Unknown configuration key: ${String(key)}`);
    }
    
    const validated = this.validateAndCast(key as string, value, def);
    
    __TOKEN_306__ (def.validator) {
      const validationResult = def.validator(validated);
      __TOKEN_308__ (validationResult !== true) {
        throw new Error(`Validation failed: ${validationResult}`);
      }
    }
    
    this.config[key] = validated;
  }
}

// Usage examples
const appConfigSchema = {
  server: {
    type: 'object' as const,
    required: true,
    default: {},
    validator: (value) => {
      __TOKEN_315__ (!value.port || typeof value.port !== 'number') {
        return 'Port must be a number';
      }
      __TOKEN_318__ (value.port < 1 || value.port > 65535) {
        return 'Port must be between 1 and 65535';
      }
      return true;
    }
  },
  database: {
    type: 'object' as const,
    required: true,
    envVar: 'DB_CONFIG'
  },
  logging: {
    type: 'object' as const,
    default: { level: 'info', format: 'json' },
    validator: (value) => {
      const validLevels = ['error', 'warn', 'info', 'debug'];
      __TOKEN_326__ (!validLevels.includes(value.level)) {
        return `Log level must be one of: ${validLevels.join(', ')}`;
      }
      return true;
    }
  },
  features: {
    type: 'object' as const,
    default: { analytics: false, caching: true }
  },
  cors: {
    type: 'object' as const,
    default: { enabled: true, origins: ['localhost'] }
  },
  rateLimit: {
    type: 'number' as const,
    default: 100,
    validator: (value) => value > 0 || 'Rate limit must be positive'
  }
};

// Create configuration manager
const config = new ConfigManager(appConfigSchema)
  .loadFromObject({
    server: { port: 3000, host: '0.0.0.0' },
    database: { url: 'postgres:__TOKEN_15__
  })
  .loadFromEnv('APP_');

__TOKEN_16__
const serverConfig = config.get('server');
console.log(`Server port: ${serverConfig.port}`);

__TOKEN_17__
const validation = config.validate();
if (!validation.valid) {
  console.error('Configuration errors:', validation.errors);
}

__TOKEN_18__
const loggingConfig = config.createNested('logging', {
  level: { type: 'string' as const, required: true },
  format: { type: 'string' as const, default: 'json' }
});

console.log('Log level:', loggingConfig.get('level'));

__TOKEN_19__
config.set('rateLimit', 200);

__TOKEN_20__
type AppConfig = ConfigFromSchema<typeof appConfigSchema>;

__TOKEN_21__
class ConfigBuilder<T extends ConfigSchema> {
  private configManager: ConfigManager<T>;
  
  constructor(schema: T) {
    this.configManager = new ConfigManager(schema);
  }
  
  withDefaults(defaults: Partial<ConfigFromSchema<T>>): this {
    this.configManager.loadFromObject(defaults as any);
    return this;
  }
  
  withEnvironment(prefix = ''): this {
    this.configManager.loadFromEnv(prefix);
    return this;
  }
  
  withFile(filePath: string): this {
    this.configManager.loadFromFile(filePath);
    return this;
  }
  
  build(): ConfigManager<T> {
    const validation = this.configManager.validate();
    if (!validation.valid) {
      throw new Error(`Configuration invalid: ${validation.errors.join(', ')}`);
    }
    return this.configManager;
  }
}

__TOKEN_22__
const appConfig = new ConfigBuilder(appConfigSchema)
  .withDefaults({
    server: { port: 3000 },
    logging: { level: 'info' }
  })
  .withEnvironment('APP_')
  .build();