Resolve Config With Explicit Source Precedence

A configuration resolver that merges defaults, file, environment and CLI sources in a declared order and records which source won each key.

TypescriptConfig Management

Code

export interface ConfigSource {
  readonly name: string;
  readonly values: Record<string, unknown>;
}

export interface ResolvedConfig<T> {
  readonly value: T;
  readonly provenance: Record<string, string>;
}

export function resolveConfig<T extends Record<string, unknown>>(
  sources: readonly ConfigSource[],
  options: { required?: readonly string[] } = {},
): ResolvedConfig<T> {
  const merged: Record<string, unknown> = {};
  const provenance: Record<string, string> = {};
  const seenNames = new Set<string>();

  for (const source of sources) {
    if (seenNames.has(source.name)) {
      throw new Error("duplicate config source name: " + source.name);
    }
    seenNames.add(source.name);
    if (!source.values || typeof source.values !== "object") {
      throw new TypeError("source " + source.name + " values must be an object");
    }
    for (const [key, value] of Object.entries(source.values)) {
      if (value === undefined) continue;
      merged[key] = value;
      provenance[key] = source.name;
    }
  }

  const missing = (options.required ?? []).filter((key) => !(key in merged));
  if (missing.length > 0) {
    throw new Error("missing required config keys: " + missing.join(", "));
  }

  return { value: merged as T, provenance };
}