/** * earth-now.co * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: packages/registry/src/load.ts * Purpose: Load and validate the metric YAML files and JSON fixtures from disk (server-side only) */ import { readFileSync, readdirSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { parse as parseYaml } from "yaml"; import { type MetricEntry, type MetricFixture, fixturesFileSchema, metricsFileSchema, } from "./schema.js"; // load.js lives in /dist (built) or /src (tests) — the package root is one level up. const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), ".."); export interface Registry { metrics: MetricEntry[]; byId: Map; } /** Load every metrics/*.yaml file, validate, and check cross-references. */ export function loadRegistry(rootDir: string = PACKAGE_ROOT): Registry { const metricsDir = join(rootDir, "metrics"); const files = readdirSync(metricsDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml")); const metrics: MetricEntry[] = []; for (const file of files.sort()) { // merge: true enables YAML 1.1 merge keys (<<: *anchor) used by grouped // metric families like the top-10 country populations. const doc: unknown = parseYaml(readFileSync(join(metricsDir, file), "utf8"), { merge: true }); const parsed = metricsFileSchema.safeParse(doc); if (!parsed.success) { throw new Error(`Registry file ${file} is invalid:\n${parsed.error.message}`); } metrics.push(...parsed.data.metrics); } const byId = new Map(); for (const m of metrics) { if (byId.has(m.id)) throw new Error(`Duplicate metric id in registry: ${m.id}`); byId.set(m.id, m); } // Derived metrics must reference declared metrics. for (const m of metrics) { if (m.derived) { for (const input of m.derived.inputs) { if (!byId.has(input.id)) { throw new Error(`Metric ${m.id} derives from undeclared metric ${input.id}`); } } } } return { metrics, byId }; } /** Load every fixtures/*.json file (keyed by metric id) and validate. */ export function loadFixtures(rootDir: string = PACKAGE_ROOT): Map { const fixturesDir = join(rootDir, "fixtures"); const files = readdirSync(fixturesDir).filter((f) => f.endsWith(".json")); const out = new Map(); for (const file of files.sort()) { const doc: unknown = JSON.parse(readFileSync(join(fixturesDir, file), "utf8")); const parsed = fixturesFileSchema.safeParse(doc); if (!parsed.success) { throw new Error(`Fixture file ${file} is invalid:\n${parsed.error.message}`); } for (const [metricId, fixture] of Object.entries(parsed.data)) { if (out.has(metricId)) throw new Error(`Duplicate fixture for metric ${metricId} in ${file}`); out.set(metricId, fixture); } } return out; }