switched to new config and removed old

This commit is contained in:
Boki 2025-06-18 21:03:45 -04:00
parent 6b69bcbcaa
commit 269364fbc8
70 changed files with 889 additions and 2978 deletions

View file

@ -0,0 +1,220 @@
import { join } from 'path';
import { z } from 'zod';
import {
ConfigManagerOptions,
Environment,
ConfigLoader,
DeepPartial,
ConfigSchema
} from './types';
import { ConfigError, ConfigValidationError } from './errors';
import { EnvLoader } from './loaders/env.loader';
import { FileLoader } from './loaders/file.loader';
export class ConfigManager<T = Record<string, unknown>> {
private config: T | null = null;
private loaders: ConfigLoader[];
private environment: Environment;
private schema?: ConfigSchema;
constructor(options: ConfigManagerOptions = {}) {
this.environment = options.environment || this.detectEnvironment();
// Default loaders if none provided
if (options.loaders) {
this.loaders = options.loaders;
} else {
const configPath = options.configPath || join(process.cwd(), 'config');
this.loaders = [
new FileLoader(configPath, this.environment),
new EnvLoader(''), // No prefix for env vars to match our .env file
];
}
}
/**
* Initialize the configuration by loading from all sources
*/
async initialize(schema?: ConfigSchema): Promise<T> {
if (this.config) {
return this.config;
}
this.schema = schema;
// Sort loaders by priority (higher priority last)
const sortedLoaders = [...this.loaders].sort((a, b) => a.priority - b.priority);
// Load configurations from all sources
const configs: Record<string, unknown>[] = [];
for (const loader of sortedLoaders) {
const config = await loader.load();
if (config && Object.keys(config).length > 0) {
configs.push(config);
}
}
// Merge all configurations
const mergedConfig = this.deepMerge(...configs) as T;
// Add environment if not present
if (typeof mergedConfig === 'object' && mergedConfig !== null && !('environment' in mergedConfig)) {
(mergedConfig as any).environment = this.environment;
}
// Validate if schema provided
if (this.schema) {
try {
this.config = this.schema.parse(mergedConfig) as T;
} catch (error) {
if (error instanceof z.ZodError) {
throw new ConfigValidationError(
'Configuration validation failed',
error.errors
);
}
throw error;
}
} else {
this.config = mergedConfig;
}
return this.config;
}
/**
* Get the current configuration
*/
get(): T {
if (!this.config) {
throw new ConfigError('Configuration not initialized. Call initialize() first.');
}
return this.config;
}
/**
* Get a specific configuration value by path
*/
getValue<R = unknown>(path: string): R {
const config = this.get();
const keys = path.split('.');
let value: any = config;
for (const key of keys) {
if (value && typeof value === 'object' && key in value) {
value = value[key];
} else {
throw new ConfigError(`Configuration key not found: ${path}`);
}
}
return value as R;
}
/**
* Check if a configuration path exists
*/
has(path: string): boolean {
try {
this.getValue(path);
return true;
} catch {
return false;
}
}
/**
* Update configuration at runtime (useful for testing)
*/
set(updates: DeepPartial<T>): void {
if (!this.config) {
throw new ConfigError('Configuration not initialized. Call initialize() first.');
}
const updated = this.deepMerge(this.config as any, updates as any) as T;
// Re-validate if schema is present
if (this.schema) {
try {
this.config = this.schema.parse(updated) as T;
} catch (error) {
if (error instanceof z.ZodError) {
throw new ConfigValidationError(
'Configuration validation failed after update',
error.errors
);
}
throw error;
}
} else {
this.config = updated;
}
}
/**
* Get the current environment
*/
getEnvironment(): Environment {
return this.environment;
}
/**
* Reset configuration (useful for testing)
*/
reset(): void {
this.config = null;
}
/**
* Validate configuration against a schema
*/
validate<S extends ConfigSchema>(schema: S): z.infer<S> {
const config = this.get();
return schema.parse(config);
}
/**
* Create a typed configuration getter
*/
createTypedGetter<S extends z.ZodSchema>(schema: S): () => z.infer<S> {
return () => this.validate(schema);
}
private detectEnvironment(): Environment {
const env = process.env.NODE_ENV?.toLowerCase();
switch (env) {
case 'production':
case 'prod':
return 'production';
case 'test':
return 'test';
case 'development':
case 'dev':
default:
return 'development';
}
}
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) &&
!(value instanceof Date) &&
!(value instanceof RegExp)
) {
result[key] = this.deepMerge(result[key] || {}, value);
} else {
result[key] = value;
}
}
}
return result;
}
}