stock-bot/libs/config/src/loaders/file.loader.ts
2025-06-20 09:10:57 -04:00

72 lines
No EOL
2 KiB
TypeScript

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<Record<string, unknown>> {
try {
const configs: Record<string, unknown>[] = [];
// 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<Record<string, unknown> | 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<string, any>[]): Record<string, any> {
const result: Record<string, any> = {};
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;
}
}