import { readFile } from 'fs/promises'; import { join } from 'path'; import { ConfigLoader } from '../types'; import { ConfigLoaderError } from '../errors'; export class FileLoader implements ConfigLoader { readonly priority = 50; // Medium priority constructor( private configPath: string, private environment: string ) {} async load(): Promise> { try { const configs: Record[] = []; // Load default config const defaultConfig = await this.loadFile('default.json'); if (defaultConfig) { configs.push(defaultConfig); } // Load environment-specific config const envConfig = await this.loadFile(`${this.environment}.json`); if (envConfig) { configs.push(envConfig); } // Merge configs (later configs override earlier ones) return this.deepMerge(...configs); } catch (error) { throw new ConfigLoaderError( `Failed to load configuration files: ${error}`, 'FileLoader' ); } } private async loadFile(filename: string): Promise | null> { const filepath = join(this.configPath, filename); try { const content = await readFile(filepath, 'utf-8'); return JSON.parse(content); } catch (error: unknown) { // File not found is not an error (configs are optional) if (error.code === 'ENOENT') { return null; } throw error; } } private deepMerge(...objects: Record[]): Record { const result: Record = {}; for (const obj of objects) { for (const [key, value] of Object.entries(obj)) { if (value === null || value === undefined) { result[key] = value; } else if (typeof value === 'object' && !Array.isArray(value)) { result[key] = this.deepMerge(result[key] || {}, value); } else { result[key] = value; } } } return result; } }