import { readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
interface ModelsFile {
readonly schema_version: string;
readonly models: MentalModelInput[];
}
interface MentalModelInput {
readonly code: string;
readonly name: string;
readonly definition: string;
readonly example: string;
readonly transformation: string;
readonly when_to_use: string;
readonly how_to_apply: string;
}
interface MentalModelRecord {
readonly code: string;
readonly name: string;
readonly definition: string;
readonly example: string;
readonly transformation: string;
readonly whenToUse: string;
readonly howToApply: string;
}
const REQUIRED_SCHEMA_VERSION = "1.1" as const;
const PROJECT_ROOT = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
const MODELS_PATH = path.join(PROJECT_ROOT, "data", "models.json");
const SEED_OUTPUT_PATH = path.join(PROJECT_ROOT, "seed.sql");
const TARGET_TABLE = "mental_models";
const escapeSql = (value: string): string => value.replace(/'/g, "''");
const ensureString = (value: unknown, field: string, code: string): string => {
if (typeof value !== "string" || value.trim().length === 0) {
throw new Error(`Model ${code}: field '${field}' must be a non-empty string.`);
}
return value;
};
const loadModels = (): MentalModelRecord[] => {
const raw = readFileSync(MODELS_PATH, "utf-8");
const parsed = JSON.parse(raw) as ModelsFile;
if (parsed.schema_version !== REQUIRED_SCHEMA_VERSION) {
throw new Error(
`Unsupported schema_version '${parsed.schema_version}'. Expected '${REQUIRED_SCHEMA_VERSION}'.`
);
}
return parsed.models.map((model) => {
const code = ensureString(model.code, "code", model.code ?? "UNKNOWN");
return {
code,
name: ensureString(model.name, "name", code),
definition: ensureString(model.definition, "definition", code),
example: ensureString(model.example, "example", code),
transformation: ensureString(model.transformation, "transformation", code),
whenToUse: ensureString(model.when_to_use, "when_to_use", code),
howToApply: ensureString(model.how_to_apply, "how_to_apply", code),
} satisfies MentalModelRecord;
});
};
const buildSeedStatements = (models: MentalModelRecord[]): string => {
const columns = [
"code",
"name",
"transformation",
"definition",
"example",
"when_to_use",
"how_to_apply",
];
const lines: string[] = [];
lines.push("-- This file is auto-generated by scripts/generate-seed.ts");
lines.push("-- Do not edit manually. Update data/models.json and rerun the generator.");
lines.push("BEGIN TRANSACTION;");
lines.push(`DELETE FROM ${TARGET_TABLE};`);
models.forEach((model) => {
const values = [
model.code,
model.name,
model.transformation,
model.definition,
model.example,
model.whenToUse,
model.howToApply,
].map((value) => `'${escapeSql(value)}'`);
lines.push(`INSERT INTO ${TARGET_TABLE} (${columns.join(", ")}) VALUES (${values.join(", ")});`);
});
lines.push("COMMIT;");
return `${lines.join("\n")}\n`;
};
const main = (): void => {
const models = loadModels();
const sql = buildSeedStatements(models);
writeFileSync(SEED_OUTPUT_PATH, sql, "utf-8");
// eslint-disable-next-line no-console
console.log(`Generated ${models.length} mental model rows in ${path.relative(PROJECT_ROOT, SEED_OUTPUT_PATH)}`);
};
main();