type MergerFunc = (base: T, layer: T) => T; type Merger = MergerFunc | ConfigMerger type MergerMap = { [K in keyof T]?: Merger } type PreTransformer = (value: Partial) => Partial export const RESET_TOKEN = Symbol(); export type ResetObject = { [RESET_TOKEN]?: any } export class ConfigMerger { constructor(map?: MergerMap, public preTransform?: PreTransformer) { for (let [k, m] of Object.entries(map || {})) { this.defineMergeHandler(k as any, m as any); } } private keyMergers: { [K in keyof T]?: MergerFunc } = {}; mergeConfigs(base: T, ...layers: Partial<(T & ResetObject)>[]) { if (this.preTransform) base = this.preTransform(base) as T; let newConfig = { ...base }; for (let layer of layers) { if (layer == null) continue; if (this.preTransform) layer = this.preTransform(layer as any); if (layer[RESET_TOKEN]) newConfig = {} as any; for (let [k, v] of Object.entries(layer)) { if ((k as any) == RESET_TOKEN) continue; const merger = this.keyMergers[k]; newConfig[k] = merger ? merger(newConfig[k], v) : v; } } return newConfig; } defineMergeHandler(key: K | K[], merger: MergerFunc | ConfigMerger) { const keys = Array.isArray(key) ? key : [key]; for (let k of keys) { let normMerger: MergerFunc; if (merger instanceof ConfigMerger) { normMerger = (base, layer) => merger.mergeConfigs(base, layer); } else { normMerger = merger; } this.keyMergers[k] = normMerger; } } } export function merger(spec?: MergerMap, preTransform?: PreTransformer) { return new ConfigMerger(spec); }