import { isPrimitive, isRecord } from '@rsdk/common'; import type { RawValues, SourceType } from '../../types'; import { ConfigSourceException } from '../exceptions'; export abstract class ConfigSource { /** * Wrapped .loadRaw() method which should be implemented in * derived class and enforces basic validation and error transforming. * * NOTE: Guarantees that only ConfigSourceException can't be thrown * @returns Promise */ async load(): Promise { try { /** * Need to handle error on reload because * otherwise application will go down (just as * on reading step) * * TODO: Add negative health check when last * 3 configuration updates fail */ const raw = await this.loadRaw(); return this.flattenEndValidate(raw); } catch (error) { if (error instanceof ConfigSourceException) { throw error; } throw new ConfigSourceException( 'Failed to load configuration from file', error as Error, ); } } /** * Flattens raw configuration and validates that each property is * either primitive or array of primitives. * * @param obj Raw configuration object obtained from source * @param prefix recursive calls chain path in for of string * @returns Vars */ private flattenEndValidate(obj: unknown, prefix = ''): RawValues { if (!isRecord(obj)) { throw new ConfigSourceException( prefix ? `Value at key: ${prefix} should be an object, primitive or array of primitives` : `Value at root sould be an object`, ); } return Object.entries(obj).reduce((acc: RawValues, [key, value]) => { if (isPrimitive(value)) { acc.set(key, value); return acc; } const pre = (prefix.length > 0 ? `${prefix}.` : '') + key; if (Array.isArray(value)) { if (!value.every(isPrimitive)) { throw new ConfigSourceException(`Non primitive array at key: ${pre}`); } acc.set(key, value); return acc; } /** * Value is not primitive and is not array which means * it is object. So: do recursion */ for (const [key, v] of this.flattenEndValidate(value, pre).entries()) { acc.set(key, v); } return acc; }, new Map()); } abstract name(): string; abstract type(): SourceType; abstract loadRaw(): Promise; }