mikro-orm-markdown 0.1.1 → 0.1.2
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/CHANGELOG.md +14 -0
- package/dist/cli.cjs +326 -197
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +5 -2
- package/dist/cli.d.ts +5 -2
- package/dist/cli.js +329 -200
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +242 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +244 -133
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/docs/jsdoc.ts
|
|
2
|
-
import { Project } from "ts-morph";
|
|
2
|
+
import { Project, ts } from "ts-morph";
|
|
3
3
|
function loadJsDoc(filePaths, onWarn) {
|
|
4
4
|
const entities = /* @__PURE__ */ new Map();
|
|
5
5
|
const props = /* @__PURE__ */ new Map();
|
|
@@ -38,17 +38,7 @@ function loadJsDoc(filePaths, onWarn) {
|
|
|
38
38
|
if (classDocs.length > 0) {
|
|
39
39
|
entities.set(className, parseEntityJsDoc(classDocs));
|
|
40
40
|
}
|
|
41
|
-
const propMap =
|
|
42
|
-
for (const prop of cls.getProperties()) {
|
|
43
|
-
const propDocs = prop.getJsDocs();
|
|
44
|
-
if (propDocs.length === 0) {
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
const info = parsePropJsDoc(propDocs);
|
|
48
|
-
if (info.description !== void 0 || info.atLeastOne) {
|
|
49
|
-
propMap.set(prop.getName(), info);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
41
|
+
const propMap = collectPropJsDocs(cls);
|
|
52
42
|
if (propMap.size > 0) {
|
|
53
43
|
props.set(className, propMap);
|
|
54
44
|
}
|
|
@@ -65,6 +55,28 @@ function formatUnknownError(err) {
|
|
|
65
55
|
function hasGlobPattern(filePath) {
|
|
66
56
|
return /[*?[\]{}]/.test(filePath);
|
|
67
57
|
}
|
|
58
|
+
function collectPropJsDocs(cls) {
|
|
59
|
+
const propMap = /* @__PURE__ */ new Map();
|
|
60
|
+
for (const prop of [...cls.getProperties(), ...cls.getGetAccessors()]) {
|
|
61
|
+
const info = parsePropJsDoc(prop.getJsDocs());
|
|
62
|
+
addPropInfo(propMap, prop.getName(), info);
|
|
63
|
+
}
|
|
64
|
+
for (const prop of getConstructorParameterProperties(cls)) {
|
|
65
|
+
const info = parseCompilerPropJsDoc(ts.getJSDocCommentsAndTags(prop.compilerNode));
|
|
66
|
+
addPropInfo(propMap, prop.getName(), info);
|
|
67
|
+
}
|
|
68
|
+
return propMap;
|
|
69
|
+
}
|
|
70
|
+
function getConstructorParameterProperties(cls) {
|
|
71
|
+
return cls.getConstructors().flatMap(
|
|
72
|
+
(constructorDeclaration) => constructorDeclaration.getParameters().filter((param) => param.isParameterProperty())
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
function addPropInfo(propMap, propName, info) {
|
|
76
|
+
if (info.description !== void 0 || info.atLeastOne) {
|
|
77
|
+
propMap.set(propName, info);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
68
80
|
function parseEntityJsDoc(jsDocs) {
|
|
69
81
|
const namespaces = [];
|
|
70
82
|
const erdNamespaces = [];
|
|
@@ -114,10 +126,39 @@ function parsePropJsDoc(jsDocs) {
|
|
|
114
126
|
}
|
|
115
127
|
return { ...description !== void 0 && { description }, atLeastOne };
|
|
116
128
|
}
|
|
129
|
+
function parseCompilerPropJsDoc(jsDocs) {
|
|
130
|
+
let description;
|
|
131
|
+
let atLeastOne = false;
|
|
132
|
+
for (const doc of jsDocs) {
|
|
133
|
+
if (ts.isJSDoc(doc)) {
|
|
134
|
+
const desc = formatCompilerJsDocComment(doc.comment);
|
|
135
|
+
if (desc && description === void 0) {
|
|
136
|
+
description = desc;
|
|
137
|
+
}
|
|
138
|
+
for (const tag of doc.tags ?? []) {
|
|
139
|
+
if (tag.tagName.getText() === "atLeastOne") {
|
|
140
|
+
atLeastOne = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (doc.tagName.getText() === "atLeastOne") {
|
|
146
|
+
atLeastOne = true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { ...description !== void 0 && { description }, atLeastOne };
|
|
150
|
+
}
|
|
151
|
+
function formatCompilerJsDocComment(comment) {
|
|
152
|
+
if (comment === void 0) {
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
155
|
+
const trimmed = ts.getTextOfJSDocComment(comment)?.trim();
|
|
156
|
+
return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
|
|
157
|
+
}
|
|
117
158
|
|
|
118
159
|
// src/metadata/load.ts
|
|
119
160
|
import * as path from "path";
|
|
120
|
-
import { EntitySchema, MikroORM } from "@mikro-orm/core";
|
|
161
|
+
import { EntitySchema, MetadataStorage, MikroORM } from "@mikro-orm/core";
|
|
121
162
|
var MetadataLoadError = class extends Error {
|
|
122
163
|
constructor(message, cause) {
|
|
123
164
|
super(message);
|
|
@@ -153,6 +194,44 @@ function assertNoEntitySchemaEntities(options) {
|
|
|
153
194
|
Use decorator-based @Entity() classes instead.`
|
|
154
195
|
);
|
|
155
196
|
}
|
|
197
|
+
function hasDecoratorMarker(target) {
|
|
198
|
+
const pathSymbol = MetadataStorage.PATH_SYMBOL;
|
|
199
|
+
if (typeof pathSymbol === "symbol" && pathSymbol in target) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
return "__path" in target;
|
|
203
|
+
}
|
|
204
|
+
function isRenderableMeta(meta) {
|
|
205
|
+
return !meta.pivotTable && !meta.embeddable;
|
|
206
|
+
}
|
|
207
|
+
function assertDiscoveredEntitiesAreSupported(metas) {
|
|
208
|
+
const confirmed = [];
|
|
209
|
+
const unconfirmed = [];
|
|
210
|
+
for (const meta of metas) {
|
|
211
|
+
if (!isRenderableMeta(meta)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (EntitySchema.REGISTRY.has(meta.class)) {
|
|
215
|
+
confirmed.push(meta.className);
|
|
216
|
+
} else if (!hasDecoratorMarker(meta.class)) {
|
|
217
|
+
unconfirmed.push(meta.className);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (confirmed.length === 0 && unconfirmed.length === 0) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const lines = [];
|
|
224
|
+
if (confirmed.length > 0) {
|
|
225
|
+
lines.push(`EntitySchema-defined entities are not currently supported: ${confirmed.join(", ")}.`);
|
|
226
|
+
}
|
|
227
|
+
if (unconfirmed.length > 0) {
|
|
228
|
+
lines.push(
|
|
229
|
+
`Could not confirm these entities are decorator-based @Entity() classes: ${unconfirmed.join(", ")}. This usually means they are EntitySchema-defined entities (also not currently supported). If you're certain these are valid @Entity() classes, this may be a detection false positive in mikro-orm-markdown \u2014 please open an issue: https://github.com/iamkanguk97/mikro-orm-markdown/issues`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
lines.push("Use decorator-based @Entity() classes instead.");
|
|
233
|
+
throw new MetadataLoadError(lines.join("\n"));
|
|
234
|
+
}
|
|
156
235
|
async function loadEntityMetadata(options) {
|
|
157
236
|
assertNoEntitySchemaEntities(options);
|
|
158
237
|
let orm;
|
|
@@ -179,6 +258,7 @@ async function loadEntityMetadata(options) {
|
|
|
179
258
|
"No entities were discovered. Check that your config specifies at least one entity path or class."
|
|
180
259
|
);
|
|
181
260
|
}
|
|
261
|
+
assertDiscoveredEntitiesAreSupported(all);
|
|
182
262
|
const baseDir = orm.config.get("baseDir");
|
|
183
263
|
const sourcePaths = [...new Set(all.filter((m) => m.path).map((m) => path.resolve(baseDir, m.path)))];
|
|
184
264
|
return { metas: all, sourcePaths };
|
|
@@ -190,55 +270,8 @@ async function loadEntityMetadata(options) {
|
|
|
190
270
|
// src/model/build.ts
|
|
191
271
|
import { ReferenceKind as ReferenceKind2 } from "@mikro-orm/core";
|
|
192
272
|
|
|
193
|
-
// src/
|
|
273
|
+
// src/model/diagram.ts
|
|
194
274
|
import { ReferenceKind } from "@mikro-orm/core";
|
|
195
|
-
|
|
196
|
-
// src/render/escape.ts
|
|
197
|
-
var MARKDOWN_INLINE_SPECIAL_CHARS = /[\\`|*#]/g;
|
|
198
|
-
var MERMAID_IDENTIFIER_INVALID_CHARS = /[^a-zA-Z0-9_]/g;
|
|
199
|
-
function normalizeInlineText(value) {
|
|
200
|
-
return value.replace(/\r\n?/g, "\n").replace(/[\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
201
|
-
}
|
|
202
|
-
function splitNormalizedLines(value) {
|
|
203
|
-
return value.replace(/\r\n?/g, "\n").split("\n");
|
|
204
|
-
}
|
|
205
|
-
function escapeHtmlText(value) {
|
|
206
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
207
|
-
}
|
|
208
|
-
function escapeMarkdownInline(value) {
|
|
209
|
-
return escapeHtmlText(normalizeInlineText(value)).replace(MARKDOWN_INLINE_SPECIAL_CHARS, "\\$&");
|
|
210
|
-
}
|
|
211
|
-
function escapeMarkdownTableCell(value) {
|
|
212
|
-
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join("<br>");
|
|
213
|
-
}
|
|
214
|
-
function escapeMarkdownParagraph(value) {
|
|
215
|
-
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join(" \n");
|
|
216
|
-
}
|
|
217
|
-
function renderMarkdownBlockQuote(value) {
|
|
218
|
-
return splitNormalizedLines(value).map((line) => `> ${escapeMarkdownInline(line)}`).join("\n");
|
|
219
|
-
}
|
|
220
|
-
function renderMarkdownInlineCode(value) {
|
|
221
|
-
const normalized = normalizeInlineText(value);
|
|
222
|
-
const backtickRuns = normalized.match(/`+/g) ?? [];
|
|
223
|
-
const longestRun = Math.max(0, ...backtickRuns.map((run) => run.length));
|
|
224
|
-
const fence = "`".repeat(longestRun + 1);
|
|
225
|
-
const needsPadding = normalized.startsWith("`") || normalized.endsWith("`");
|
|
226
|
-
const content = needsPadding ? ` ${normalized} ` : normalized;
|
|
227
|
-
return `${fence}${content}${fence}`;
|
|
228
|
-
}
|
|
229
|
-
function toMermaidIdentifier(value) {
|
|
230
|
-
const normalized = normalizeInlineText(value).replace(MERMAID_IDENTIFIER_INVALID_CHARS, "_").replace(/_+/g, "_");
|
|
231
|
-
const identifier = normalized === "" ? "_" : normalized;
|
|
232
|
-
return /^[a-zA-Z_]/.test(identifier) ? identifier : `_${identifier}`;
|
|
233
|
-
}
|
|
234
|
-
function escapeMermaidQuotedText(value) {
|
|
235
|
-
return normalizeInlineText(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
236
|
-
}
|
|
237
|
-
function toMarkdownAnchor(value) {
|
|
238
|
-
return normalizeInlineText(value).toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-");
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// src/render/mermaid.ts
|
|
242
275
|
var FORMULA_DUMMY_TABLE = {
|
|
243
276
|
alias: "e0",
|
|
244
277
|
name: "",
|
|
@@ -509,75 +542,6 @@ function buildEdge(fromEntity, prop) {
|
|
|
509
542
|
}
|
|
510
543
|
return null;
|
|
511
544
|
}
|
|
512
|
-
function normalizeType(type) {
|
|
513
|
-
const t = type.toLowerCase().trim();
|
|
514
|
-
if (t === "uuid" || t === "text" || t === "string" || t.startsWith("varchar")) {
|
|
515
|
-
return "string";
|
|
516
|
-
}
|
|
517
|
-
if (t === "timestamptz" || t === "timestamp" || t === "datetime") {
|
|
518
|
-
return "datetime";
|
|
519
|
-
}
|
|
520
|
-
if (t === "integer" || t === "int" || t === "bigint" || t === "smallint") {
|
|
521
|
-
return "integer";
|
|
522
|
-
}
|
|
523
|
-
if (t === "doubletype" || t === "double precision" || t === "double" || t === "float" || t === "decimal") {
|
|
524
|
-
return "float";
|
|
525
|
-
}
|
|
526
|
-
if (t === "boolean" || t === "bool") {
|
|
527
|
-
return "boolean";
|
|
528
|
-
}
|
|
529
|
-
if (t === "jsonb") {
|
|
530
|
-
return "json";
|
|
531
|
-
}
|
|
532
|
-
return type;
|
|
533
|
-
}
|
|
534
|
-
function renderErDiagram(model, mermaid) {
|
|
535
|
-
const lines = [];
|
|
536
|
-
if (mermaid?.layout !== void 0 || mermaid?.theme !== void 0) {
|
|
537
|
-
lines.push("---", "config:");
|
|
538
|
-
if (mermaid.layout !== void 0) {
|
|
539
|
-
lines.push(` layout: ${mermaid.layout}`);
|
|
540
|
-
}
|
|
541
|
-
if (mermaid.theme !== void 0) {
|
|
542
|
-
lines.push(` theme: ${mermaid.theme}`);
|
|
543
|
-
}
|
|
544
|
-
lines.push("---");
|
|
545
|
-
}
|
|
546
|
-
lines.push("erDiagram");
|
|
547
|
-
for (const entity of model.entities) {
|
|
548
|
-
lines.push(` ${toMermaidIdentifier(entity.className)} {`);
|
|
549
|
-
for (const col of entity.columns) {
|
|
550
|
-
lines.push(` ${renderColumnLine(col)}`);
|
|
551
|
-
}
|
|
552
|
-
lines.push(" }");
|
|
553
|
-
}
|
|
554
|
-
for (const rel of model.relations) {
|
|
555
|
-
lines.push(
|
|
556
|
-
` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
|
|
557
|
-
);
|
|
558
|
-
}
|
|
559
|
-
return lines.join("\n");
|
|
560
|
-
}
|
|
561
|
-
function renderColumnLine(col) {
|
|
562
|
-
let qualifier = "";
|
|
563
|
-
if (col.isPrimary) {
|
|
564
|
-
qualifier = " PK";
|
|
565
|
-
} else if (col.isUnique) {
|
|
566
|
-
qualifier = " UK";
|
|
567
|
-
}
|
|
568
|
-
let comment;
|
|
569
|
-
if (col.formula !== void 0) {
|
|
570
|
-
comment = col.formula ? `formula: ${col.formula}` : "formula";
|
|
571
|
-
} else if (col.isDiscriminator) {
|
|
572
|
-
comment = "discriminator";
|
|
573
|
-
} else if (col.embeddedIn !== void 0) {
|
|
574
|
-
comment = `[${col.embeddedIn}]`;
|
|
575
|
-
} else if (col.isSelfReference) {
|
|
576
|
-
comment = "self-ref";
|
|
577
|
-
}
|
|
578
|
-
const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
|
|
579
|
-
return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
|
|
580
|
-
}
|
|
581
545
|
|
|
582
546
|
// src/model/build.ts
|
|
583
547
|
var FROM_ONE_OR_MORE = "}|";
|
|
@@ -600,7 +564,10 @@ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
|
|
|
600
564
|
const columns = model.columns.filter(
|
|
601
565
|
(col) => !(col.isForeignKey && col.referencedEntity !== void 0 && hiddenClasses.has(col.referencedEntity))
|
|
602
566
|
);
|
|
603
|
-
const visibleModel =
|
|
567
|
+
const visibleModel = removeHiddenEntityReferences(
|
|
568
|
+
columns.length === model.columns.length ? model : { ...model, columns },
|
|
569
|
+
hiddenClasses
|
|
570
|
+
);
|
|
604
571
|
const ownPropDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
|
|
605
572
|
const propDocs = withEmbeddedPropDocs(ownPropDocs, visibleModel.columns, jsDocResult.props);
|
|
606
573
|
enrichedByClass.set(model.className, { model: visibleModel, jsDoc, propDocs });
|
|
@@ -651,6 +618,14 @@ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
|
|
|
651
618
|
});
|
|
652
619
|
return { title, groups, ...description !== void 0 && { description } };
|
|
653
620
|
}
|
|
621
|
+
function removeHiddenEntityReferences(model, hiddenClasses) {
|
|
622
|
+
if (model.extendsEntity === void 0 || !hiddenClasses.has(model.extendsEntity)) {
|
|
623
|
+
return model;
|
|
624
|
+
}
|
|
625
|
+
const visibleModel = { ...model };
|
|
626
|
+
delete visibleModel.extendsEntity;
|
|
627
|
+
return visibleModel;
|
|
628
|
+
}
|
|
654
629
|
function withEmbeddedPropDocs(ownPropDocs, columns, allPropDocs) {
|
|
655
630
|
const merged = new Map(ownPropDocs);
|
|
656
631
|
for (const col of columns) {
|
|
@@ -762,6 +737,142 @@ async function withTsMorphMetadataProvider(options, onWarn) {
|
|
|
762
737
|
}
|
|
763
738
|
}
|
|
764
739
|
|
|
740
|
+
// src/render/escape.ts
|
|
741
|
+
var MARKDOWN_INLINE_SPECIAL_CHARS = /[\\`|*#\[\]]/g;
|
|
742
|
+
var MARKDOWN_EMPHASIS_UNDERSCORE = /(?<![a-zA-Z0-9])_|_(?![a-zA-Z0-9])/g;
|
|
743
|
+
var MERMAID_IDENTIFIER_INVALID_CHARS = /[^a-zA-Z0-9_]/g;
|
|
744
|
+
function normalizeInlineText(value) {
|
|
745
|
+
return value.replace(/\r\n?/g, "\n").replace(/[\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
746
|
+
}
|
|
747
|
+
function splitNormalizedLines(value) {
|
|
748
|
+
return value.replace(/\r\n?/g, "\n").split("\n");
|
|
749
|
+
}
|
|
750
|
+
function escapeHtmlText(value) {
|
|
751
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
752
|
+
}
|
|
753
|
+
function escapeMarkdownInline(value) {
|
|
754
|
+
return escapeHtmlText(normalizeInlineText(value)).replace(MARKDOWN_INLINE_SPECIAL_CHARS, "\\$&").replace(MARKDOWN_EMPHASIS_UNDERSCORE, "\\_");
|
|
755
|
+
}
|
|
756
|
+
function escapeMarkdownTableCell(value) {
|
|
757
|
+
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join("<br>");
|
|
758
|
+
}
|
|
759
|
+
function escapeMarkdownParagraph(value) {
|
|
760
|
+
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join(" \n");
|
|
761
|
+
}
|
|
762
|
+
function renderMarkdownBlockQuote(value) {
|
|
763
|
+
return splitNormalizedLines(value).map((line) => `> ${escapeMarkdownInline(line)}`).join("\n");
|
|
764
|
+
}
|
|
765
|
+
function renderMarkdownInlineCode(value) {
|
|
766
|
+
const normalized = normalizeInlineText(value);
|
|
767
|
+
const backtickRuns = normalized.match(/`+/g) ?? [];
|
|
768
|
+
const longestRun = Math.max(0, ...backtickRuns.map((run) => run.length));
|
|
769
|
+
const fence = "`".repeat(longestRun + 1);
|
|
770
|
+
const needsPadding = normalized.startsWith("`") || normalized.endsWith("`");
|
|
771
|
+
const content = needsPadding ? ` ${normalized} ` : normalized;
|
|
772
|
+
return `${fence}${content}${fence}`;
|
|
773
|
+
}
|
|
774
|
+
function toMermaidIdentifier(value) {
|
|
775
|
+
const normalized = normalizeInlineText(value).replace(MERMAID_IDENTIFIER_INVALID_CHARS, "_").replace(/_+/g, "_");
|
|
776
|
+
const identifier = normalized === "" ? "_" : normalized;
|
|
777
|
+
return /^[a-zA-Z_]/.test(identifier) ? identifier : `_${identifier}`;
|
|
778
|
+
}
|
|
779
|
+
function escapeMermaidQuotedText(value) {
|
|
780
|
+
return normalizeInlineText(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
781
|
+
}
|
|
782
|
+
function toMarkdownAnchor(value) {
|
|
783
|
+
return normalizeInlineText(value).toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-");
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/render/mermaid.ts
|
|
787
|
+
var GENERIC_TYPE_BY_BASE_NAME = /* @__PURE__ */ new Map([
|
|
788
|
+
["uuid", "string"],
|
|
789
|
+
["text", "string"],
|
|
790
|
+
["string", "string"],
|
|
791
|
+
["varchar", "string"],
|
|
792
|
+
["character varying", "string"],
|
|
793
|
+
["character", "string"],
|
|
794
|
+
["char", "string"],
|
|
795
|
+
["tinytext", "string"],
|
|
796
|
+
["mediumtext", "string"],
|
|
797
|
+
["longtext", "string"],
|
|
798
|
+
["timestamptz", "datetime"],
|
|
799
|
+
["timestamp", "datetime"],
|
|
800
|
+
["datetime", "datetime"],
|
|
801
|
+
["integer", "integer"],
|
|
802
|
+
["int", "integer"],
|
|
803
|
+
["bigint", "integer"],
|
|
804
|
+
["smallint", "integer"],
|
|
805
|
+
["tinyint", "integer"],
|
|
806
|
+
["mediumint", "integer"],
|
|
807
|
+
["serial", "integer"],
|
|
808
|
+
["bigserial", "integer"],
|
|
809
|
+
["doubletype", "float"],
|
|
810
|
+
["double precision", "float"],
|
|
811
|
+
["double", "float"],
|
|
812
|
+
["float", "float"],
|
|
813
|
+
["decimal", "float"],
|
|
814
|
+
["numeric", "float"],
|
|
815
|
+
["real", "float"],
|
|
816
|
+
["boolean", "boolean"],
|
|
817
|
+
["bool", "boolean"],
|
|
818
|
+
["jsonb", "json"]
|
|
819
|
+
]);
|
|
820
|
+
function normalizeType(type) {
|
|
821
|
+
const t = type.toLowerCase().replace(/\s+/g, " ").trim();
|
|
822
|
+
if (/^tinyint\s*\(\s*1\s*\)$/.test(t)) {
|
|
823
|
+
return "boolean";
|
|
824
|
+
}
|
|
825
|
+
const baseName = t.replace(/\(.*\)/, "").replace(/\b(un)?signed\b/, "").trim();
|
|
826
|
+
return GENERIC_TYPE_BY_BASE_NAME.get(baseName) ?? type;
|
|
827
|
+
}
|
|
828
|
+
function renderErDiagram(model, mermaid) {
|
|
829
|
+
const lines = [];
|
|
830
|
+
if (mermaid?.layout !== void 0 || mermaid?.theme !== void 0) {
|
|
831
|
+
lines.push("---", "config:");
|
|
832
|
+
if (mermaid.layout !== void 0) {
|
|
833
|
+
lines.push(` layout: ${mermaid.layout}`);
|
|
834
|
+
}
|
|
835
|
+
if (mermaid.theme !== void 0) {
|
|
836
|
+
lines.push(` theme: ${mermaid.theme}`);
|
|
837
|
+
}
|
|
838
|
+
lines.push("---");
|
|
839
|
+
}
|
|
840
|
+
lines.push("erDiagram");
|
|
841
|
+
for (const entity of model.entities) {
|
|
842
|
+
lines.push(` ${toMermaidIdentifier(entity.className)} {`);
|
|
843
|
+
for (const col of entity.columns) {
|
|
844
|
+
lines.push(` ${renderColumnLine(col)}`);
|
|
845
|
+
}
|
|
846
|
+
lines.push(" }");
|
|
847
|
+
}
|
|
848
|
+
for (const rel of model.relations) {
|
|
849
|
+
lines.push(
|
|
850
|
+
` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
return lines.join("\n");
|
|
854
|
+
}
|
|
855
|
+
function renderColumnLine(col) {
|
|
856
|
+
let qualifier = "";
|
|
857
|
+
if (col.isPrimary) {
|
|
858
|
+
qualifier = " PK";
|
|
859
|
+
} else if (col.isUnique) {
|
|
860
|
+
qualifier = " UK";
|
|
861
|
+
}
|
|
862
|
+
let comment;
|
|
863
|
+
if (col.formula !== void 0) {
|
|
864
|
+
comment = col.formula ? `formula: ${col.formula}` : "formula";
|
|
865
|
+
} else if (col.isDiscriminator) {
|
|
866
|
+
comment = "discriminator";
|
|
867
|
+
} else if (col.embeddedIn !== void 0) {
|
|
868
|
+
comment = `[${col.embeddedIn}]`;
|
|
869
|
+
} else if (col.isSelfReference) {
|
|
870
|
+
comment = "self-ref";
|
|
871
|
+
}
|
|
872
|
+
const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
|
|
873
|
+
return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
|
|
874
|
+
}
|
|
875
|
+
|
|
765
876
|
// src/render/markdown.ts
|
|
766
877
|
function renderMarkdown(docModel, mermaid) {
|
|
767
878
|
const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
|
|
@@ -785,7 +896,7 @@ function renderBulletSection(header, items, renderItem) {
|
|
|
785
896
|
}
|
|
786
897
|
function renderTableOfContents(groups, anchors) {
|
|
787
898
|
return renderBulletSection("## Contents", groups, (group) => {
|
|
788
|
-
const label = escapeMarkdownInline(group.name)
|
|
899
|
+
const label = escapeMarkdownInline(group.name);
|
|
789
900
|
return `- [${label}](#${anchors.get(group) ?? toMarkdownAnchor(group.name)})`;
|
|
790
901
|
});
|
|
791
902
|
}
|
|
@@ -870,7 +981,7 @@ function resolveColumnKey(col) {
|
|
|
870
981
|
return "PK";
|
|
871
982
|
}
|
|
872
983
|
if (col.isForeignKey) {
|
|
873
|
-
return fkKey;
|
|
984
|
+
return col.isUnique ? `${fkKey}, UK` : fkKey;
|
|
874
985
|
}
|
|
875
986
|
if (col.isUnique) {
|
|
876
987
|
return "UK";
|