Type Safe Configuration Manager
Hierarchical configuration management with validation and environment support
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();