SPB Git

spb/earth-now Public License

earth-now.co — real-time planetary dashboard: live world metrics modeled, not streamed.

TypeScript 93% Shell 2.3% SQL 1.4% JavaScript 1.3% Dockerfile 1.2% CSS 0.8%
2.9 KB · 80 lines typescript
Raw Blame History
1/**2 * earth-now.co3 * Author:  Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File:    packages/registry/src/load.ts6 * Purpose: Load and validate the metric YAML files and JSON fixtures from disk (server-side only)7 */89import { readFileSync, readdirSync } from "node:fs";10import { dirname, join } from "node:path";11import { fileURLToPath } from "node:url";12import { parse as parseYaml } from "yaml";13import {14  type MetricEntry,15  type MetricFixture,16  fixturesFileSchema,17  metricsFileSchema,18} from "./schema.js";1920// load.js lives in <pkg>/dist (built) or <pkg>/src (tests) — the package root is one level up.21const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");2223export interface Registry {24  metrics: MetricEntry[];25  byId: Map<string, MetricEntry>;26}2728/** Load every metrics/*.yaml file, validate, and check cross-references. */29export function loadRegistry(rootDir: string = PACKAGE_ROOT): Registry {30  const metricsDir = join(rootDir, "metrics");31  const files = readdirSync(metricsDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));32  const metrics: MetricEntry[] = [];33  for (const file of files.sort()) {34    // merge: true enables YAML 1.1 merge keys (<<: *anchor) used by grouped35    // metric families like the top-10 country populations.36    const doc: unknown = parseYaml(readFileSync(join(metricsDir, file), "utf8"), { merge: true });37    const parsed = metricsFileSchema.safeParse(doc);38    if (!parsed.success) {39      throw new Error(`Registry file ${file} is invalid:\n${parsed.error.message}`);40    }41    metrics.push(...parsed.data.metrics);42  }4344  const byId = new Map<string, MetricEntry>();45  for (const m of metrics) {46    if (byId.has(m.id)) throw new Error(`Duplicate metric id in registry: ${m.id}`);47    byId.set(m.id, m);48  }49  // Derived metrics must reference declared metrics.50  for (const m of metrics) {51    if (m.derived) {52      for (const input of m.derived.inputs) {53        if (!byId.has(input.id)) {54          throw new Error(`Metric ${m.id} derives from undeclared metric ${input.id}`);55        }56      }57    }58  }59  return { metrics, byId };60}6162/** Load every fixtures/*.json file (keyed by metric id) and validate. */63export function loadFixtures(rootDir: string = PACKAGE_ROOT): Map<string, MetricFixture> {64  const fixturesDir = join(rootDir, "fixtures");65  const files = readdirSync(fixturesDir).filter((f) => f.endsWith(".json"));66  const out = new Map<string, MetricFixture>();67  for (const file of files.sort()) {68    const doc: unknown = JSON.parse(readFileSync(join(fixturesDir, file), "utf8"));69    const parsed = fixturesFileSchema.safeParse(doc);70    if (!parsed.success) {71      throw new Error(`Fixture file ${file} is invalid:\n${parsed.error.message}`);72    }73    for (const [metricId, fixture] of Object.entries(parsed.data)) {74      if (out.has(metricId)) throw new Error(`Duplicate fixture for metric ${metricId} in ${file}`);75      out.set(metricId, fixture);76    }77  }78  return out;79}80