Files
SubMiner/src/config/service.ts

117 lines
3.4 KiB
TypeScript

import * as fs from 'fs';
import * as path from 'path';
import { ConfigValidationWarning, RawConfig, ResolvedConfig } from '../types';
import { DEFAULT_CONFIG, deepCloneConfig, deepMergeRawConfig } from './definitions';
import { ConfigPaths, loadRawConfig, loadRawConfigStrict } from './load';
import { resolveConfig } from './resolve';
export type ReloadConfigStrictResult =
| {
ok: true;
config: ResolvedConfig;
warnings: ConfigValidationWarning[];
path: string;
}
| {
ok: false;
error: string;
path: string;
};
export class ConfigStartupParseError extends Error {
readonly path: string;
readonly parseError: string;
constructor(configPath: string, parseError: string) {
super(
`Failed to parse startup config at ${configPath}: ${parseError}. Fix the config file and restart SubMiner.`,
);
this.name = 'ConfigStartupParseError';
this.path = configPath;
this.parseError = parseError;
}
}
export class ConfigService {
private readonly configPaths: ConfigPaths;
private rawConfig: RawConfig = {};
private resolvedConfig: ResolvedConfig = deepCloneConfig(DEFAULT_CONFIG);
private warnings: ConfigValidationWarning[] = [];
private configPathInUse!: string;
constructor(configDir: string) {
this.configPaths = {
configDir,
configFileJsonc: path.join(configDir, 'config.jsonc'),
configFileJson: path.join(configDir, 'config.json'),
};
const loadResult = loadRawConfigStrict(this.configPaths);
if (!loadResult.ok) {
throw new ConfigStartupParseError(loadResult.path, loadResult.error);
}
this.applyResolvedConfig(loadResult.config, loadResult.path);
}
getConfigPath(): string {
return this.configPathInUse;
}
getConfig(): ResolvedConfig {
return deepCloneConfig(this.resolvedConfig);
}
getRawConfig(): RawConfig {
return JSON.parse(JSON.stringify(this.rawConfig)) as RawConfig;
}
getWarnings(): ConfigValidationWarning[] {
return [...this.warnings];
}
reloadConfig(): ResolvedConfig {
const { config, path: configPath } = loadRawConfig(this.configPaths);
return this.applyResolvedConfig(config, configPath);
}
reloadConfigStrict(): ReloadConfigStrictResult {
const loadResult = loadRawConfigStrict(this.configPaths);
if (!loadResult.ok) {
return loadResult;
}
const { config, path: configPath } = loadResult;
const resolvedConfig = this.applyResolvedConfig(config, configPath);
return {
ok: true,
config: resolvedConfig,
warnings: this.getWarnings(),
path: configPath,
};
}
saveRawConfig(config: RawConfig): void {
if (!fs.existsSync(this.configPaths.configDir)) {
fs.mkdirSync(this.configPaths.configDir, { recursive: true });
}
const targetPath = this.configPathInUse.endsWith('.json')
? this.configPathInUse
: this.configPaths.configFileJsonc;
fs.writeFileSync(targetPath, JSON.stringify(config, null, 2));
this.applyResolvedConfig(config, targetPath);
}
patchRawConfig(patch: RawConfig): void {
const merged = deepMergeRawConfig(this.getRawConfig(), patch);
this.saveRawConfig(merged);
}
private applyResolvedConfig(config: RawConfig, configPath: string): ResolvedConfig {
this.rawConfig = config;
this.configPathInUse = configPath;
const { resolved, warnings } = resolveConfig(config);
this.resolvedConfig = resolved;
this.warnings = warnings;
return this.getConfig();
}
}