@nonsoo/prisma-mermaid 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/build/index.cjs +383 -0
- package/build/index.d.cts +63 -0
- package/build/index.d.ts +63 -0
- package/build/index.js +345 -0
- package/build/lib/PrismaMermaidGenerators/bin.cjs +1844 -0
- package/build/lib/PrismaMermaidGenerators/bin.d.cts +1 -0
- package/build/lib/PrismaMermaidGenerators/bin.d.ts +1 -0
- package/build/lib/PrismaMermaidGenerators/bin.js +1849 -0
- package/package.json +74 -0
package/build/index.js
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// src/lib/MermaidClass/prismaMermaidClass.ts
|
|
2
|
+
import pkg from "@prisma/internals";
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
// src/constants/mermaid.ts
|
|
7
|
+
var mermaidERDiagramConfig = {
|
|
8
|
+
theme: "neutral",
|
|
9
|
+
themeVariables: {
|
|
10
|
+
fontSize: "20px",
|
|
11
|
+
fontFamily: "Arial",
|
|
12
|
+
padding: "12px",
|
|
13
|
+
lineHeight: "1.4"
|
|
14
|
+
},
|
|
15
|
+
flowchart: {
|
|
16
|
+
nodeSpacing: 80,
|
|
17
|
+
rankSpacing: 120,
|
|
18
|
+
htmlLabels: true
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var mermaidClassDiagramConfig = {
|
|
22
|
+
theme: "neutral",
|
|
23
|
+
themeVariables: {
|
|
24
|
+
fontFamily: "Arial",
|
|
25
|
+
lineHeight: "1.4"
|
|
26
|
+
},
|
|
27
|
+
flowchart: {
|
|
28
|
+
nodeSpacing: 300,
|
|
29
|
+
rankSpacing: 120,
|
|
30
|
+
htmlLabels: true
|
|
31
|
+
},
|
|
32
|
+
class: {
|
|
33
|
+
hideEmptyMembersBox: true
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var DEFAULT_BASE_NODE_SPACING = 100;
|
|
37
|
+
var DEFAULT_BASE_EDGE_SPACING = 150;
|
|
38
|
+
|
|
39
|
+
// src/utils/mermaid.ts
|
|
40
|
+
var generateDiagramSpacing = ({
|
|
41
|
+
baseEdge,
|
|
42
|
+
baseNode,
|
|
43
|
+
models
|
|
44
|
+
}) => {
|
|
45
|
+
const totalFields = models.reduce((sum, m) => sum + m.fields.length, 0);
|
|
46
|
+
const totalRelations = models.reduce(
|
|
47
|
+
(sum, m) => sum + m.fields.filter((f) => f.relationName && f.relationFromFields?.length).length,
|
|
48
|
+
0
|
|
49
|
+
);
|
|
50
|
+
return {
|
|
51
|
+
nodeSpacing: baseNode + models.length * 6 + totalFields * 2,
|
|
52
|
+
edgeSpacing: baseEdge + models.length * 4 + totalRelations * 4
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
var generateMermaidConfig = (config, models) => {
|
|
56
|
+
if (config["themeVariables"]) {
|
|
57
|
+
const { edgeSpacing, nodeSpacing } = generateDiagramSpacing({
|
|
58
|
+
baseEdge: DEFAULT_BASE_EDGE_SPACING,
|
|
59
|
+
baseNode: DEFAULT_BASE_NODE_SPACING,
|
|
60
|
+
models
|
|
61
|
+
});
|
|
62
|
+
config["themeVariables"] = {
|
|
63
|
+
...config["themeVariables"],
|
|
64
|
+
edgeSpacing,
|
|
65
|
+
nodeSpacing
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const json = JSON.stringify(config, null, 2);
|
|
69
|
+
return `%%{init: ${json}}%%
|
|
70
|
+
`;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/lib/MermaidClass/utils.ts
|
|
74
|
+
var generateCardinality = ({
|
|
75
|
+
isList,
|
|
76
|
+
isRequired
|
|
77
|
+
}) => {
|
|
78
|
+
if (isList) return '"*"';
|
|
79
|
+
return isRequired ? '"1"' : '"0..1"';
|
|
80
|
+
};
|
|
81
|
+
var generateRelationships = ({
|
|
82
|
+
relationships
|
|
83
|
+
}) => {
|
|
84
|
+
const lines = [];
|
|
85
|
+
for (const relName in relationships) {
|
|
86
|
+
const sides = relationships[relName];
|
|
87
|
+
if (!sides) continue;
|
|
88
|
+
if (sides.length === 1) {
|
|
89
|
+
const a = sides[0];
|
|
90
|
+
if (!a) continue;
|
|
91
|
+
lines.push(
|
|
92
|
+
`${a.model} ${generateCardinality({
|
|
93
|
+
isList: a.isList,
|
|
94
|
+
isRequired: a.isRequired
|
|
95
|
+
})} --> "1" ${a.fieldType} : ${relName}`
|
|
96
|
+
);
|
|
97
|
+
} else if (sides.length === 2) {
|
|
98
|
+
const a = sides[0];
|
|
99
|
+
const b = sides[1];
|
|
100
|
+
if (!a || !b) continue;
|
|
101
|
+
lines.push(
|
|
102
|
+
`${a.model} ${generateCardinality({
|
|
103
|
+
isList: a.isList,
|
|
104
|
+
isRequired: a.isRequired
|
|
105
|
+
})} --> ${generateCardinality({
|
|
106
|
+
isList: b.isList,
|
|
107
|
+
isRequired: b.isRequired
|
|
108
|
+
})} ${b.model} : ${relName}`
|
|
109
|
+
);
|
|
110
|
+
} else {
|
|
111
|
+
for (let i = 1; i < sides.length; i++) {
|
|
112
|
+
const a = sides[0];
|
|
113
|
+
const b = sides[i];
|
|
114
|
+
if (!a || !b) continue;
|
|
115
|
+
lines.push(
|
|
116
|
+
`${a.model} ${generateCardinality({
|
|
117
|
+
isList: a.isList,
|
|
118
|
+
isRequired: a.isRequired
|
|
119
|
+
})} --> ${generateCardinality({
|
|
120
|
+
isList: b.isList,
|
|
121
|
+
isRequired: b.isRequired
|
|
122
|
+
})} ${b.model} : ${relName}`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return lines;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// src/lib/MermaidClass/prismaMermaidClass.ts
|
|
131
|
+
var { getDMMF } = pkg;
|
|
132
|
+
var generateDiagram = async ({
|
|
133
|
+
outputPath,
|
|
134
|
+
schemaPath,
|
|
135
|
+
generatorPrismaDocument
|
|
136
|
+
}) => {
|
|
137
|
+
const outputDir = outputPath ? path.resolve(outputPath) : path.join(`${process.cwd()}/src/generated/diagrams`);
|
|
138
|
+
try {
|
|
139
|
+
const prismaDocument = generatorPrismaDocument ?? await getDMMF({
|
|
140
|
+
datamodel: readFileSync(schemaPath, "utf-8")
|
|
141
|
+
});
|
|
142
|
+
const models = prismaDocument.datamodel.models;
|
|
143
|
+
const enums = prismaDocument.datamodel.enums;
|
|
144
|
+
const mermaidLines = [
|
|
145
|
+
"%% --------------------------------------------",
|
|
146
|
+
"%% Auto-generated Mermaid Class Diagram. Do Not Edit Directly.",
|
|
147
|
+
"%% --------------------------------------------\n",
|
|
148
|
+
generateMermaidConfig(mermaidClassDiagramConfig, models),
|
|
149
|
+
"classDiagram"
|
|
150
|
+
];
|
|
151
|
+
const relationships = {};
|
|
152
|
+
models.forEach((model) => {
|
|
153
|
+
mermaidLines.push(`class ${model.name} {`);
|
|
154
|
+
model.fields.forEach((field) => {
|
|
155
|
+
mermaidLines.push(` ${field.type} ${field.name}`);
|
|
156
|
+
if (field.relationName) {
|
|
157
|
+
if (!relationships[field.relationName]) {
|
|
158
|
+
relationships[field.relationName] = [];
|
|
159
|
+
}
|
|
160
|
+
relationships[field.relationName].push({
|
|
161
|
+
model: model.name,
|
|
162
|
+
fieldType: field.type,
|
|
163
|
+
isList: field.isList ?? false,
|
|
164
|
+
isRequired: field.isRequired ?? false
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
mermaidLines.push("}");
|
|
169
|
+
});
|
|
170
|
+
enums.forEach((enumDef) => {
|
|
171
|
+
mermaidLines.push(`class ${enumDef.name} {`);
|
|
172
|
+
enumDef.values.forEach((val) => {
|
|
173
|
+
mermaidLines.push(` <<enumeration>> ${val.name}`);
|
|
174
|
+
});
|
|
175
|
+
mermaidLines.push("}");
|
|
176
|
+
});
|
|
177
|
+
const relationLines = generateRelationships({ relationships });
|
|
178
|
+
const output = mermaidLines.concat(relationLines).join("\n");
|
|
179
|
+
mkdirSync(outputDir, { recursive: true });
|
|
180
|
+
const outFile = path.join(outputDir, "mermaidClassDiagram.mmd");
|
|
181
|
+
writeFileSync(outFile, output, "utf-8");
|
|
182
|
+
console.log(`Mermaid Class Diagram written to: ${outFile}`);
|
|
183
|
+
return outFile;
|
|
184
|
+
} catch (e) {
|
|
185
|
+
console.error("Failed to generate Mermaid Class Diagram.", e);
|
|
186
|
+
return "";
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// src/lib/MermaidERD/prismaMermaidErd.ts
|
|
191
|
+
import pkg2 from "@prisma/internals";
|
|
192
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
193
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
194
|
+
import path2 from "path";
|
|
195
|
+
|
|
196
|
+
// src/lib/MermaidERD/utils.ts
|
|
197
|
+
var generateCardinality2 = ({
|
|
198
|
+
isList,
|
|
199
|
+
isRequired
|
|
200
|
+
}) => {
|
|
201
|
+
if (isList) {
|
|
202
|
+
return "}|";
|
|
203
|
+
}
|
|
204
|
+
return isRequired ? "||" : "o|";
|
|
205
|
+
};
|
|
206
|
+
var getKeyConstraints = (isId, fieldName, foreignKeys, nativeTypes) => {
|
|
207
|
+
if (isId) return "PK";
|
|
208
|
+
if (nativeTypes) {
|
|
209
|
+
const allNativeTypes = nativeTypes.flatMap((nativeType) => nativeType);
|
|
210
|
+
if (!isId && allNativeTypes.includes("UniqueIdentifier")) {
|
|
211
|
+
return "FK";
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (!isId && foreignKeys.has(fieldName)) return "FK";
|
|
215
|
+
return "";
|
|
216
|
+
};
|
|
217
|
+
var getOptionalitySymbol = (isRequired) => {
|
|
218
|
+
return isRequired ? "" : `"?"`;
|
|
219
|
+
};
|
|
220
|
+
var generateRelationships2 = ({
|
|
221
|
+
relationships
|
|
222
|
+
}) => {
|
|
223
|
+
const relationLines = [];
|
|
224
|
+
for (const relName in relationships) {
|
|
225
|
+
const sides = relationships[relName];
|
|
226
|
+
if (!sides) continue;
|
|
227
|
+
if (sides.length === 1) {
|
|
228
|
+
const a = sides[0];
|
|
229
|
+
if (!a) continue;
|
|
230
|
+
relationLines.push(
|
|
231
|
+
` ${a.model} ${generateCardinality2({
|
|
232
|
+
isList: a.isList,
|
|
233
|
+
isRequired: a.isRequired
|
|
234
|
+
})}--${generateCardinality2({ isList: false, isRequired: true })} ${a.fieldType} : ${relName}`
|
|
235
|
+
);
|
|
236
|
+
} else if (sides.length === 2) {
|
|
237
|
+
const a = sides[0];
|
|
238
|
+
const b = sides[1];
|
|
239
|
+
if (!a || !b) continue;
|
|
240
|
+
relationLines.push(
|
|
241
|
+
` ${a.model} ${generateCardinality2({
|
|
242
|
+
isList: a.isList,
|
|
243
|
+
isRequired: a.isRequired
|
|
244
|
+
})}--${generateCardinality2({
|
|
245
|
+
isList: b.isList,
|
|
246
|
+
isRequired: b.isRequired
|
|
247
|
+
})} ${b.model} : ${relName}`
|
|
248
|
+
);
|
|
249
|
+
} else {
|
|
250
|
+
for (let i = 1; i < sides.length; i++) {
|
|
251
|
+
const a = sides[0];
|
|
252
|
+
const b = sides[i];
|
|
253
|
+
if (!a || !b) continue;
|
|
254
|
+
relationLines.push(
|
|
255
|
+
` ${a.model} ${generateCardinality2({
|
|
256
|
+
isList: a.isList,
|
|
257
|
+
isRequired: a.isRequired
|
|
258
|
+
})}--${generateCardinality2({
|
|
259
|
+
isList: b.isList,
|
|
260
|
+
isRequired: b.isRequired
|
|
261
|
+
})} ${b.model} : ${relName}`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return relationLines;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// src/lib/MermaidERD/prismaMermaidErd.ts
|
|
270
|
+
var { getDMMF: getDMMF2 } = pkg2;
|
|
271
|
+
var generateDiagram2 = async ({
|
|
272
|
+
outputPath,
|
|
273
|
+
schemaPath,
|
|
274
|
+
generatorPrismaDocument
|
|
275
|
+
}) => {
|
|
276
|
+
const outputDir = outputPath ? path2.resolve(outputPath) : path2.join(`${process.cwd()}/src/generated/diagrams`);
|
|
277
|
+
try {
|
|
278
|
+
const prismaDocument = generatorPrismaDocument ?? await getDMMF2({
|
|
279
|
+
datamodel: readFileSync2(schemaPath, "utf-8")
|
|
280
|
+
});
|
|
281
|
+
const schemaModels = prismaDocument.datamodel.models;
|
|
282
|
+
const schemaEnums = prismaDocument.datamodel.enums;
|
|
283
|
+
console.dir(schemaEnums, { depth: null });
|
|
284
|
+
const mermaidLines = [
|
|
285
|
+
"%% --------------------------------------------",
|
|
286
|
+
"%% Auto-generated Mermaid ER Diagram. Do Not Edit Directly.",
|
|
287
|
+
"%% --------------------------------------------\n",
|
|
288
|
+
generateMermaidConfig(mermaidERDiagramConfig, schemaModels),
|
|
289
|
+
"erDiagram"
|
|
290
|
+
];
|
|
291
|
+
const relationships = {};
|
|
292
|
+
schemaModels.forEach((model) => {
|
|
293
|
+
mermaidLines.push(` ${model.name} {`);
|
|
294
|
+
const foreignKeys = /* @__PURE__ */ new Set();
|
|
295
|
+
model.fields.forEach((field) => {
|
|
296
|
+
if (field.relationFromFields && field.relationFromFields.length > 0) {
|
|
297
|
+
field.relationFromFields.forEach((fk) => {
|
|
298
|
+
foreignKeys.add(fk);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
mermaidLines.push(
|
|
302
|
+
` ${field.type} ${field.name} ${getKeyConstraints(
|
|
303
|
+
field.isId,
|
|
304
|
+
field.name,
|
|
305
|
+
foreignKeys,
|
|
306
|
+
field.nativeType
|
|
307
|
+
)} ${getOptionalitySymbol(field.isRequired)}`
|
|
308
|
+
);
|
|
309
|
+
if (field.relationName) {
|
|
310
|
+
if (!relationships[field.relationName]) {
|
|
311
|
+
relationships[field.relationName] = [];
|
|
312
|
+
}
|
|
313
|
+
relationships[field.relationName].push({
|
|
314
|
+
model: model.name,
|
|
315
|
+
fieldType: field.type,
|
|
316
|
+
isList: field.isList ?? false,
|
|
317
|
+
isRequired: field.isRequired ?? false
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
mermaidLines.push(` }`);
|
|
322
|
+
});
|
|
323
|
+
schemaEnums.forEach((enumDef) => {
|
|
324
|
+
mermaidLines.push(` ${enumDef.name} {`);
|
|
325
|
+
enumDef.values.forEach((enumValue) => {
|
|
326
|
+
mermaidLines.push(` ${enumValue.name}`);
|
|
327
|
+
});
|
|
328
|
+
mermaidLines.push(` }`);
|
|
329
|
+
});
|
|
330
|
+
const relationLines = generateRelationships2({ relationships });
|
|
331
|
+
const output = mermaidLines.concat(relationLines);
|
|
332
|
+
mkdirSync2(outputDir, { recursive: true });
|
|
333
|
+
const outFile = path2.join(outputDir, "mermaidErdDiagram.mmd");
|
|
334
|
+
writeFileSync2(outFile, output.join("\n"));
|
|
335
|
+
console.log(`Mermaid ERD generated at: ${outFile}`);
|
|
336
|
+
return outFile;
|
|
337
|
+
} catch {
|
|
338
|
+
console.error("Failed to generate Mermaid ER Diagram.");
|
|
339
|
+
return "";
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
export {
|
|
343
|
+
generateDiagram as generateMermaidClass,
|
|
344
|
+
generateDiagram2 as generateMermaidERD
|
|
345
|
+
};
|