import { lstat, readFile, readdir } from "node:fs/promises"; import { basename, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { normalizeDataProductDefinition } from "./data-product-policy.mjs"; import { persistDataProductDefinition } from "./data-product-delivery.mjs"; const bundledDefinitionsDir = fileURLToPath(new URL("../definitions/", import.meta.url)); export async function loadDataProductDefinitions(directory = bundledDefinitionsDir) { const root = resolve(directory); const entries = (await readdir(root, { withFileTypes: true })) .filter((entry) => entry.name.endsWith(".json")) .sort((left, right) => left.name.localeCompare(right.name)); const definitions = []; for (const entry of entries) { if (!entry.isFile() || entry.isSymbolicLink()) throw new Error("data_product_definition_file_invalid"); const path = join(root, entry.name); const metadata = await lstat(path); if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 2 || metadata.size > 256 * 1024) { throw new Error("data_product_definition_file_invalid"); } let value; try { value = JSON.parse(await readFile(path, "utf8")); } catch { throw new Error("data_product_definition_json_invalid"); } const definition = normalizeDataProductDefinition(value); if (`${definition.id}.json` !== basename(path)) throw new Error("data_product_definition_filename_mismatch"); definitions.push(definition); } if (new Set(definitions.map((definition) => definition.id)).size !== definitions.length) { throw new Error("data_product_definition_duplicate"); } return Object.freeze(definitions); } export async function reconcileDataProductDefinitions(db, directory = bundledDefinitionsDir) { const definitions = await loadDataProductDefinitions(directory); for (const definition of definitions) await persistDataProductDefinition(db, definition); return definitions; }