prisma-grapher
Version:
ERD generator for prisma schemas
246 lines • 11.1 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generate = generate;
const wasm_1 = require("@hpcc-js/wasm");
const promises_1 = require("fs/promises");
const identifier = (...parts) => parts
.filter(Boolean)
.map((part) => JSON.stringify(part))
.join(":");
function generate(options) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
const config = options.generator.config;
const output = ((_a = options.generator.output) === null || _a === void 0 ? void 0 : _a.value) || "./prisma/ERD.svg";
const disabled = config.disabled === "true" || process.env.DISABLE_PRISMA_GRAPHER === "true";
const title = (_b = config.title) !== null && _b !== void 0 ? _b : "";
const ignoreEnums = config.ignoreEnums === "true";
const lineColor = (_c = config.lineColor) !== null && _c !== void 0 ? _c : "#004cff";
const headerBackgroundColor = (_d = config.headerBackgroundColor) !== null && _d !== void 0 ? _d : "#bacefc";
const headerForegroundColor = (_e = config.headerForegroundColor) !== null && _e !== void 0 ? _e : "black";
const bodyBackgroundColor = (_f = config.bodyBackgroundColor) !== null && _f !== void 0 ? _f : "white";
const bodyBackgroundColor2 = (_g = config.bodyBackgroundColor2) !== null && _g !== void 0 ? _g : "#e8efff";
const bodyForegroundColor = (_h = config.bodyForegroundColor) !== null && _h !== void 0 ? _h : "black";
const typeForegroundColor = (_j = config.typeForegroundColor) !== null && _j !== void 0 ? _j : "#4f83ff";
if (disabled) {
return console.log("prisma grapher is disabled");
}
const enums = options.dmmf.datamodel.enums;
const models = [...options.dmmf.datamodel.models, ...options.dmmf.datamodel.types];
const enumByName = !ignoreEnums
? enums.reduce((acc, _enum) => ((acc[_enum.name] = _enum), acc), {})
: {};
const modelByName = models.reduce((acc, model, index) => ((acc[model.name] = Object.assign(Object.assign({}, model), { index })), acc), {});
const verticesLeft = [];
const verticesRight = [];
const relations = Object.values(modelByName)
.flatMap((aModel) => aModel.fields
.filter((aField) => enumByName[aField.type] || modelByName[aField.type])
.filter((aField) => aField.type !== aModel.name)
.map((aField) => {
var _a;
const a = [aModel.name, aField.name];
if (enumByName[aField.type]) {
const bModel = enumByName[aField.type];
const b = [bModel.name, ""];
const t = !aField.isList ? "1-m" : "m-m";
return [b, a, t];
}
else {
const bModel = modelByName[aField.type];
const bField = bModel.fields.find((it) => it.type === aModel.name);
const b = [bModel.name, (_a = bField === null || bField === void 0 ? void 0 : bField.name) !== null && _a !== void 0 ? _a : ""];
return !aField.isList && (bField === null || bField === void 0 ? void 0 : bField.isList)
? [b, a, "1-m"]
: aField.isList && !(bField === null || bField === void 0 ? void 0 : bField.isList)
? [a, b, "1-m"]
: aModel.index < bModel.index
? [a, b, !aField.isList ? "1-1" : "m-m"]
: [b, a, !aField.isList ? "1-1" : "m-m"];
}
}))
.map((edge) => JSON.stringify(edge))
.filter((it, i, arr) => arr.indexOf(it) === i)
.map((it) => JSON.parse(it))
.map((edge) => (verticesLeft.push(edge[0]), verticesRight.push(edge[1]), edge));
const relationDotDefinitions = relations.map((edge) => `
edge [
dir=both
arrowtail=${edge[2].startsWith("1-") ? "tee" : "crow"}
arrowhead=${edge[2].endsWith("-1") ? "tee" : "crow"}
]
${identifier(...edge[0])}:e -> ${identifier(...edge[1])}:w
`);
const nodeTableAttributes = `
bgcolor="${bodyBackgroundColor}"
color="${lineColor}"
border="0"
cellborder="0"
cellspacing="0"
cellpadding="0"`;
const headerTdAttributes = `
bgcolor="${headerBackgroundColor}"
color="${lineColor}"
border="1"
cellpadding="8"`;
const fieldsTableAttributes = `
bgcolor="${bodyBackgroundColor}"
color="${lineColor}"
border="0"
cellborder="0"
cellspacing="0"
cellpadding="4"`;
const enumDotDefinitions = !ignoreEnums
? enums.map((_enum) => `
${identifier(_enum.name)} [
shape=plain
label=<<table ${nodeTableAttributes}>
<tr>
<td ${headerTdAttributes}><font color="${headerForegroundColor}">${_enum.name}</font></td>
</tr>
<tr>
<td>
<table ${fieldsTableAttributes}>
${_enum.values
.map((value, i, values) => {
const bgcolor = i % 2 === 0 ? bodyBackgroundColor : bodyBackgroundColor2;
const bottomBorder = i === values.length - 1 ? "B" : "";
return `<tr>
<td
bgcolor="${bgcolor}"
color="${lineColor}"
border="1"
sides="L${bottomBorder}"
></td>
<td
bgcolor="${bgcolor}"
color="${lineColor}"
${bottomBorder ? `border="1" sides="${bottomBorder}"` : ""}
><font color="${bodyForegroundColor}">${value.name}</font></td>
<td
bgcolor="${bgcolor}"
color="${lineColor}"
border="1"
sides="R${bottomBorder}"
></td>
</tr>`;
})
.join("\n")}
</table>
</td>
</tr>
</table>>
]
`)
: [];
const modelDotDefinitions = models.map((model) => `
${identifier(model.name)} [
shape=plain
label=<<table ${nodeTableAttributes}>
<tr>
<td ${headerTdAttributes}><font color="${headerForegroundColor}">${model.name}</font></td>
</tr>
<tr>
<td>
<table ${fieldsTableAttributes}>
${model.fields
.filter((field, i, fields) => !fields.some((otherField) => { var _a; return (_a = otherField.relationFromFields) === null || _a === void 0 ? void 0 : _a.includes(field.name); }))
.map((field, i, fields) => {
const bgcolor = i % 2 === 0 ? bodyBackgroundColor : bodyBackgroundColor2;
const portLeft = field.kind === "enum" ||
verticesRight.find((vertex) => vertex[0] === model.name && vertex[1] === field.name)
? field.name
: "";
const portRight = verticesLeft.find((vertex) => vertex[0] === model.name && vertex[1] === field.name)
? field.name
: "";
const bottomBorder = i === fields.length - 1 ? "B" : "";
return `<tr>
<td
bgcolor="${bgcolor}"
color="${lineColor}"
border="1"
sides="L${bottomBorder}"
port="${portLeft}"
>${field.isId
? `<font color="${bodyForegroundColor}">◆</font>`
: field.relationFromFields !== undefined || field.kind === "enum"
? `<font color="${bodyForegroundColor}">◇</font>`
: ""}</td>
<td
bgcolor="${bgcolor}"
color="${lineColor}"
${bottomBorder ? `border="1" sides="${bottomBorder}"` : ""}
align="left"
><font color="${bodyForegroundColor}">${field.name} </font></td>
<td
bgcolor="${bgcolor}"
color="${lineColor}"
${bottomBorder ? `border="1" sides="${bottomBorder}"` : ""}
align="left"
><font color="${typeForegroundColor}">${[
field.type,
field.isList ? " [ ]" : "",
!field.isRequired ? " ?" : "",
].join("")} </font></td>
<td
bgcolor="${bgcolor}"
color="${lineColor}"
border="1"
sides="R${bottomBorder}"
align="left"
port="${portRight}"
></td>
</tr>`;
})
.join("\n")}
</table>
</td>
</tr>
</table>>
]
`);
const dotSource = `
digraph ERD {
graph [
${title ? `label=${identifier(title)}` : ""}
pad=0.4
labelloc="t"
fontname="Arial,sans-serif"
rankdir="LR"
]
node [
fontname="Arial,sans-serif"
shape=record
style=filled
color="${lineColor}"
fillcolor="${headerBackgroundColor}"
fontcolor="${headerForegroundColor}"
]
edge [
fontname="Arial,sans-serif"
color="${lineColor}"
minlen=5
arrowsize=1.2
]
${relationDotDefinitions.join("\n")}
${enumDotDefinitions.join("\n")}
${modelDotDefinitions.join("\n")}
}
`;
const graphviz = yield wasm_1.Graphviz.load();
const svg = graphviz.dot(dotSource, "svg");
yield (0, promises_1.writeFile)(output, svg);
return console.log(`prisma grapher generated erd at "${output}"`);
});
}
//# sourceMappingURL=generate.js.map